1.首先需要導入maven依賴
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.16</version>
</dependency>
2.編寫jwt工具類
package com.dqw.util;
import java.security.Key;
import java.util.Date;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* jwt工具類
* @ClassName: JWTUtil
* @Description:TODO
* @author: 丁乾文
* @date: 2019年5月13日 下午7:48:21
*/
public class JWTUtil {
/**
* token加密時使用的密鑰
* 一旦得到該密鑰也就可以僞造token了
*/
public static String sercetKey = "InMySchoolOnline";
/**
* 代表token的有效時間
*/
public final static long keeptime = 1800000;
/**
* JWT由3個部分組成,分別是 頭部Header,載荷Payload一般是用戶信息和聲明,簽證Signature一般是密鑰和簽名
* 當頭部用base64進行編碼後一般都會呈現eyJ...形式,而載荷爲非強制使用,簽證則包含了哈希算法加密後的數據,包括轉碼後的header,payload和sercetKey
* 而payload又包含幾個部分,issuer簽發者,subject面向用戶,iat簽發時間,exp過期時間,aud接收方。
* @Title: generToken
* @Description: TODO
* @param: @param id 用戶id
* @param: @param issuer 簽發者
* @param: @param subject 一般用戶名
* @param: @return
* @return: String
* @throws
*/
public static String generToken(String id, String issuer, String subject) {
long ttlMillis = keeptime;
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//使用Hash256算法進行加密
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//獲取系統時間以便設置token有效時間
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(sercetKey);
//將密鑰轉碼爲base64形式,再轉爲字節碼
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//對其使用Hash256進行加密
JwtBuilder builder = Jwts.builder().setId(id).setIssuedAt(now);
//JWT生成類,此時設置iat,以及根據傳入的id設置token
if (subject != null) {
builder.setSubject(subject);
}
if (issuer != null) {
builder.setIssuer(issuer);
}
//由於Payload是非必須加入的,所以這時候要加入檢測
builder.signWith(signatureAlgorithm, signingKey);
//進行簽名,生成Signature
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
//返回最終的token結果
return builder.compact();
}
/**
* 該函數用於更新token
* @Title: updateToken
* @Description: TODO
* @param: @param token
* @param: @return
* @return: String
* @throws
*/
public static String updateToken(String token) {
//Claims就是包含了我們的Payload信息類
Claims claims = verifyToken(token);
String id = claims.getId();
String subject = claims.getSubject();
String issuer = claims.getIssuer();
//生成新的token,根據現在的時間
return generToken(id, issuer, subject);
}
/**
* 將token解密出來,將payload信息包裝成Claims類返回
* @Title: verifyToken
* @Description: TODO
* @param: @param token
* @param: @return
* @return: Claims
* @throws
*/
private static Claims verifyToken(String token) {
Claims claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(sercetKey))
.parseClaimsJws(token).getBody();
return claims;
}
}
3.編輯token鑑定spring攔截器
package com.dqw.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.alibaba.fastjson.JSON;
import com.dqw.util.JWTUtil;
import com.dqw.util.ResponseData;
/**
* token鑑定
* @ClassName: HeaderTokenInterceptor
* @Description:TODO
* @author: 丁乾文
* @date: 2019年5月13日 下午8:52:40
*/
public class HeaderTokenInterceptor implements HandlerInterceptor {
private static final Logger LOG = Logger.getLogger(HeaderTokenInterceptor.class);
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object handler) throws Exception {
ResponseData responseData = null;
// 獲取我們請求頭中的token驗證字符
String headerToken = httpServletRequest.getHeader("token");
// 檢測當前頁面,我們設置當頁面不是登錄頁面時對其進行攔截
// 具體方法就是檢測URL中有沒有login字符串
if (!httpServletRequest.getRequestURI().contains("login")) {
if (headerToken == null) {
// 如果token不存在的話,返回錯誤信息。
responseData=ResponseData.customerError();
}
try {
// 對token進行更新與驗證
headerToken = JWTUtil.updateToken(headerToken);
LOG.debug("token驗證通過,並續期了");
} catch (Exception e) {
LOG.debug("token驗證出現異常!");
// 當token驗證出現異常返回錯誤信息,token不匹配
responseData=ResponseData.customerError();
}
}
if(responseData!=null) {//如果有錯誤信息
httpServletResponse.getWriter().write(JSON.toJSONString(responseData));
return false;
}else {
// 將token加入返回頁面的header
httpServletResponse.setHeader("token", headerToken);
return true;
}
}
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o,
ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object o, Exception e) throws Exception {
}
}
4.項目中用到的其他類
package com.dqw.util;
import java.util.HashMap;
import java.util.Map;
/**
* 響應實體
* @ClassName: ResponseData
* @Description:TODO
* @author: 丁乾文
* @date: 2019年5月13日 下午8:53:10
*/
public class ResponseData {
private final String message;
private final int code;
private final Map<String, Object> data = new HashMap<String, Object>();
public String getMessage() {
return message;
}
public int getCode() {
return code;
}
public Map<String, Object> getData() {
return data;
}
public ResponseData putDataValue(String key, Object value) {
data.put(key, value);
return this;
}
private ResponseData(int code, String message) {
this.code = code;
this.message = message;
}
public static ResponseData ok() {
return new ResponseData(200, "Ok");
}
public static ResponseData notFound() {
return new ResponseData(404, "Not Found");
}
public static ResponseData badRequest() {
return new ResponseData(400, "Bad Request");
}
public static ResponseData forbidden() {
return new ResponseData(403, "Forbidden");
}
public static ResponseData unauthorized() {
return new ResponseData(401, "unauthorized");
}
public static ResponseData serverInternalError() {
return new ResponseData(500, "Server Internal Error");
}
public static ResponseData customerError() {
return new ResponseData(1001, "customer Error");
}
}
package com.dqw.po;
/**
* 用戶實體類
* @ClassName: User
* @Description:TODO
* @author: 丁乾文
* @date: 2019年5月13日 下午8:43:38
*/
public class User {
private Integer id;
private String email;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
package com.dqw.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* 主要解決請求跨域問題
* @ClassName: HttpInterceptor
* @Description:TODO
* @author: 丁乾文
* @date: 2019年5月13日 下午8:46:41
*/
public class HttpInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 允許跨域
response.setHeader("Access-Control-Allow-Origin", "*");
// 允許自定義請求頭token(允許head跨域)
response.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
package com.dqw.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.dqw.util.ResponseData;
/**
* 首頁
* @ClassName: IndexController
* @Description:TODO
* @author: 丁乾文
* @date: 2019年5月13日 下午9:09:35
*/
@RestController
@RequestMapping("index")
public class IndexController {
@GetMapping("index")
public ResponseData toLogin() {
ResponseData responseData = ResponseData.ok();
return responseData;
}
}
package com.dqw.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.dqw.po.User;
import com.dqw.util.JWTUtil;
import com.dqw.util.ResponseData;
/**
* @ClassName: LoginController
* @Description:TODO
* @author: 丁乾文
* @date: 2019年5月13日 下午8:41:47
*/
@RestController
@RequestMapping("login")
public class LoginController {
/**
* 用戶登錄
* @Title: login
* @Description: TODO
* @param: @param user
* @param: @return
* @return: ResponseData
* @throws
*/
@PostMapping(value="login")
public @ResponseBody ResponseData login(User user) {
//模擬去查詢數據庫,看是否存在此用戶
boolean login = toLogin(user);
ResponseData responseData = ResponseData.ok();
if(login) {
//生成token
String token = JWTUtil.generToken("1", "Jersey-Security-Basic", user.getEmail());
//向瀏覽器返回token,客戶端受到此token後存入cookie中,或者h5的本地存儲中
responseData.putDataValue("token", token);
//以及用戶
responseData.putDataValue("user", user);
}else {
//用戶或者密碼錯誤
responseData=ResponseData.customerError();
}
return responseData;
}
public boolean toLogin(User user) {
if(user.getEmail()!=null&&user.getEmail().trim().length()>0) {
if(user.getEmail().equals("root")) {
if(user.getPassword().equals("123456")) {
return true;
}
}
}
return false;
}
}
}
5.附springmvc配置文件
<context:component-scan base-package="com"></context:component-scan>
<!-- 啓動註解驅動 -->
<mvc:annotation-driven/>
<context:annotation-config></context:annotation-config>
<mvc:interceptors>
<!-- 允許跨域 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.dqw.interceptor.HttpInterceptor"></bean>
</mvc:interceptor>
<!-- 檢驗Token -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.dqw.interceptor.HeaderTokenInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
6.login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="jquery.cookie.js"></script>
</head>
<body>
郵箱:<input type="text" name="email"><br>
密碼:<input type="text" name="password"><br>
<input type="submit" οnclick="signIn()" value="登錄">
<script type="text/javascript">
/**
* 登錄ajax,登錄成功後獲取後臺返回的token,並把token保存到cookie中
* 以後用戶每次請求後臺數據需要攜帶此token
*/
function signIn() {
let email = $("input[name='email']").val();
let password = $("input[name='password']").val();
$.ajax({
url: "http://localhost:8080/jwt/login/login.action",
type: "POST",
dataType: "json",
data: {email: email, password: password},
success: function (result) {
if(result.code==200){
//保存token用來判斷用戶是否登錄,和身份是否屬實
$.cookie('token', result.data.token);
//$.cookie('user', result.data.user);
//轉向主頁面
location="index.html";
}else{
alert("用戶名或者密碼錯誤!");
}
}
})
}
</script>
</body>
</html>
7.index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>歡迎</title>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="jquery.cookie.js"></script>
</head>
<body>
歡迎你(可以從本地存取獲取cookie中獲取)
<br>
<button οnclick="logout()">註銷</button>
<script type="text/javascript">
/**
* 請求數據的ajax,需要從cookie讀取token放入head傳給後臺。
*/
loadDeptTree();
function loadDeptTree() {
$.ajax({
// 自定義的headers字段,會出現option請求,在GET請求之前,後臺要記得做檢驗。
headers: {
token: $.cookie('token')
},
url: "http://localhost:8080/jwt/index/index.action",
type: 'GET',
dataType: 'json',
success : function (result) {
if(result.code==200){
alert("加載到的數據:"+result+",並進行渲染頁面.....");
}else{
alert("異常,非法token,這裏不直接判斷是否token不對,實際開發需要各種判斷返回碼。");
}
}
})
}
/**
* 註銷,清空所有cookie(或者只清空保存着token的Cookie就行)
*/
function logout() {
var keys = document.cookie.match(/[^ =;]+(?=\=)/g);
if(keys) {
for(var i = keys.length; i--;)
document.cookie = keys[i] + '=0;expires=' + new Date(0).toUTCString()
}
//返回登錄頁面或者主頁
window.location.href = "login.html";
}
</script>
</body>
</html>