java實作基于JWT的token認證
- 1.引入依賴
- 2.建立JWT工具類
- 3.建立JWT注解類
- 4.建立用于JWT驗證的攔截器
- 5.在SpringMVC.xml中配置攔截器
- 6.使用剛才建立的注解類
- 7.測試
-
- 1.測試登入
-
- 1.使用postman發送登入請求
- 2.登入成功,傳回token
- 2.擷取使用者清單
-
1.引入依賴
<!--引入JWT依賴,由于是基于Java,是以需要的是java-jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2.建立JWT工具類
public class JwtUtil {
//jwt過期時間
private final static Long ttlMillis = 1000 * 60 * 60l;
/**
* 使用者登入成功後生成Jwt
* 使用Hs256算法 私匙使用使用者密碼
*
* @param user 登入成功的user對象
* @return
*/
public static String createJWT(User user) {
//指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經将這部分内容封裝好了。
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成JWT的時間
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//建立payload的私有聲明(根據特定的業務需要添加,如果要拿這個做驗證,一般是需要和jwt的接收方提前溝通好驗證方式的)
Map<String, Object> claims = new HashMap<String, Object>();
claims.put("id", user.getId());
claims.put("username", user.getUsername());
claims.put("password", user.getPassword());
//生成簽名的時候使用的秘鑰secret,這個方法本地封裝了的,一般可以從本地配置檔案中讀取,切記這個秘鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦用戶端得知這個secret, 那就意味着用戶端是可以自我簽發jwt了。
String key = user.getPassword();
//生成簽發人
String subject = user.getUsername();
//下面就是在為payload添加各種标準聲明和私有聲明了
//這裡其實就是new一個JwtBuilder,設定jwt的body
JwtBuilder builder = Jwts.builder()
//如果有私有聲明,一定要先設定這個自己建立的私有的聲明,這個是給builder的claim指派,一旦寫在标準的聲明指派之後,就是覆寫了那些标準的聲明的
.setClaims(claims)
//設定jti(JWT ID):是JWT的唯一辨別,根據業務需要,這個可以設定為一個不重複的值,主要用來作為一次性token,進而回避重播攻擊。
.setId(UUID.randomUUID().toString())
//iat: jwt的簽發時間
.setIssuedAt(now)
//代表這個JWT的主體,即它的所有人,這個是一個json格式的字元串,可以存放什麼userid,roldid之類的,作為什麼使用者的唯一标志。
.setSubject(subject)
//設定簽名使用的簽名算法和簽名使用的秘鑰
.signWith(signatureAlgorithm, key);
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
//設定過期時間
builder.setExpiration(exp);
}
return "Bearer "+builder.compact();
}
/**
* Token的解密
* @param token 加密後的token
* @param user 使用者的對象
* @return
*/
public static Claims parseJWT(String token, User user) {
//簽名秘鑰,和生成的簽名的秘鑰一模一樣
String key = user.getPassword();
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//設定簽名的秘鑰
.setSigningKey(key)
//設定需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
/**
* 校驗token
* 在這裡可以使用官方的校驗,我這裡校驗的是token中攜帶的密碼于資料庫一緻的話就校驗通過
* @param token
* @param user
* @return
*/
public static Boolean isVerify(String token, User user) {
//簽名秘鑰,和生成的簽名的秘鑰一模一樣
String key = user.getPassword();
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//設定簽名的秘鑰
.setSigningKey(key)
//設定需要解析的jwt
.parseClaimsJws(token).getBody();
if (claims.get("password").equals(user.getPassword())) {
return true;
}
return false;
}
}
3.建立JWT注解類
/**
* 使用此注解的方法不進行token校驗
*/
//@Target注解(注解解釋:這個注解标注我們定義的注解是可以作用在類上還是方法上還是屬性上面)
@Target({ElementType.METHOD, ElementType.TYPE})
//@Retention注解(注解解釋:作用是定義被它所注解的注解保留多久,一共有三種政策,
// 1. SOURCE 被編譯器忽略,
// 2.CLASS 注解将會被保留在Class檔案中,但在運作時并不會被VM保留。這是預設行為,所有沒有用Retention注解的注解,都會采用這種政策。
// 3.RUNTIME 保留至運作時。是以我們可以通過反射去擷取注解資訊。
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
//boolean required() default true; 預設required() 屬性為true
boolean required() default true;
}
/**
* 使用此注解的方法會進行token校驗
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckToken {
boolean required() default true;
}
4.建立用于JWT驗證的攔截器
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json; charset=utf-8");
// 如果不是映射到方法直接通過
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//檢查是否有PassToken注釋,有則跳過認證
if (method.isAnnotationPresent(PassToken.class)) {
PassToken loginToken = method.getAnnotation(PassToken.class);
if (loginToken.required()) {
return true;
}
}
//檢查有沒有需要使用者權限的注解
if (method.isAnnotationPresent(CheckToken.class)) {
// 從 http 請求頭中取出 token,前端發送求時需在header中添加Authorization頭資訊,值為token字元串
String token = request.getHeader("Authorization").replace("Bearer ","");
CheckToken checkToken = method.getAnnotation(CheckToken.class);
if (checkToken.required()) {
// 執行認證
if (token == null) {
returnJsonResult(response, 401, "無token,請重新登入");
}
// 擷取 token 中的 user id
Integer userId;
try {
userId = JWT.decode(token).getClaim("id").asInt();
} catch (JWTDecodeException j) {
returnJsonResult(response, 401, "通路異常");
throw new RuntimeException("通路異常!");
}
User user = userService.findById(userId);
if (user == null) {
returnJsonResult(response, 401, "使用者不存在,請重新登入");
throw new RuntimeException("使用者不存在,請重新登入");
}
Boolean verify = JwtUtil.isVerify(token, user);
if (!verify) {
returnJsonResult(response, 400, "非法通路");
throw new RuntimeException("非法通路!");
}
return true;
}
}
return true;
}
/**
* 用于向前端發送驗證結果
* @param response
* @param code
* @param msg
* @throws IOException
*/
void returnJsonResult(HttpServletResponse response, Integer code, String msg) throws IOException {
PrintWriter out = response.getWriter();
ResponseResult result = new ResponseResult(null, code, msg);
ObjectMapper mapper = new ObjectMapper();//轉換器
String jsonResult = mapper.writeValueAsString(result);//将對象轉換成json
out.print(jsonResult);
out.flush();
out.close();
}
}
5.在SpringMVC.xml中配置攔截器
<!-- 配置攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.baixingyuan.interceptor.AuthenticationInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
6.使用剛才建立的注解類
@PostMapping("/login")
@PassToken //使用此注解将會跳過驗證
public ResponseResult login(@RequestBody User user){
@DeleteMapping("/{newId}")
@CheckToken //使用此注解将會驗證token
public ResponseResult delete(@PathVariable("newId") Integer newId){
7.測試
1.測試登入
1.使用postman發送登入請求
2.登入成功,傳回token
2.擷取使用者清單
1.發送請求
2.驗證成功,傳回資料