SpringBoot 2.1.4集成JWT實現token驗證

JSON Java 算法 wRitchie 2019-07-11

SpringBoot 2.1.4集成JWT實現token驗證

編者: wRitchie(吳理琪) 來源:http://www.bj9420.com

什麼是JWT:Json web token (JWT) 是為了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519)。定義了一種簡潔的,自包含的方法用於通信雙方之間以JSON對象的形式安全的傳遞信息。因為數字簽名的存在,這些信息是可信的,JWT可以使用HMAC算法或者是RSA或ECDSA的公私祕鑰對進行簽名。

JWT如何獲取訪問令牌(token)並用於訪問資源(API)流程:1、應用端向權限服務器請求授權;2、權限服務器授權成功嚮應用端返回一個訪問令牌(token);3、應用端使用訪問令牌(token)訪問受保護的資源(如API)。

SpringBoot 2.1.4集成JWT實現token驗證

JWT是由三段信息構成,將這三段信息文本用“.“連接一起就構成了JWT字符串,Header.Payload.Signature,例如:eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE1NTgwNjI2OTYsInVzZXJJZCI6IjEifQ.XiI0xjX0izVeJRmhbXN1w1fXKdHB0wsc9teFKq84pclpJt6yS2k0BVXAklHrke_nz6XtcCyi1hgvpn8bf95gwg。

第一步:pom.xml添加依賴,引入jar包:

版本聲明

<properties>
<!--jjwt -->
<jjwt.version>0.9.1</jjwt.version>
</properties>

版本依賴:

<dependencies>

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt</artifactId>

<version>${jjwt.version}</version>

</dependency>

</dependencies>

第二步:實現JWT的token驗證共3個Java類。首先註冊一個檢驗JWT的過濾器jwtFilter, 通過jwtFilter過濾器實現對每個Rest API請求都驗證JWT的功能。 其中JwtAuthenticationFilter繼承了OncePerRequestFilter,任何請求都會先經過jwtFilter過濾器, 然後選擇讓合法JWT請求通過jwtFilter。 具體在SpringBoot的Application啟動類中增加@Bean註解,代碼如下:

@Bean

public FilterRegistrationBean jwtFilter() {

logger.info("JWT Filter 運行中...");

final FilterRegistrationBean registrationBean = new FilterRegistrationBean();

JwtAuthenticationFilter filter = new JwtAuthenticationFilter();

registrationBean.setFilter(filter);

return registrationBean;

}

第三步:再看JWT權限過濾器JwtAuthenticationFilter.java,JwtAuthenticationFilter類繼承了OncePerRequestFilter抽象類, 確保任何用戶請求資源都會運行doFilterInternal方法。此處將從HTTP Header裡面截取JWT, 並且驗證JWT的簽名和過期時間,若有問題,會返回HTTP 401錯誤。

package com.bj9420.jwt;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.util.AntPathMatcher;

import org.springframework.util.PathMatcher;

import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

/**

* @Author: wRitchie

* @Description: JWT權限過濾器

* @Param:

* @return:

* @Date: 2019/1/14 14:32

*/

public class JwtAuthenticationFilter extends OncePerRequestFilter {

private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);

private static final PathMatcher pathMatcher = new AntPathMatcher();

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

log.info("JWT權限過濾器 JwtAuthenticationFilter開始...");

try {

String servletPath = request.getServletPath();

if(isProtectedUrl(request)) {

log.info("私密API請求:"+servletPath);

request = JwtUtil.validateTokenAndAddUserIdToHeader(request);

}else{

log.info("開放API請求:"+servletPath);

}

} catch (Exception e) {

log.info("JWT權限過濾器異常:"+e);

response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());

return;

}

filterChain.doFilter(request, response);

}

/**

* @Author: wRitchie

* @Description: 是否需要token驗證 路徑中url含api,則返回true,不含則返回false

* @Param: [request]

* @return: boolean

* @Date: 2019/1/14 14:34

*/

private boolean isProtectedUrl(HttpServletRequest request) {

/** 對路徑中url含有api的請求的返回true */

boolean matchFlag = pathMatcher.match("/**/api/**", request.getServletPath());

return matchFlag;

}

}

第四步: JwtUtil.java工具類,主要是生成令牌方法和驗證令牌是否有效,具體如下:

package com.bj9420.jwt;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletRequestWrapper;

import java.util.*;

/**

* @Author: wRitchie

* @Description: JwtUtil工具類

* @Param:

* @return:

* @Date: 2019/1/14 15:14

*/

public class JwtUtil {

private static final Logger log = LoggerFactory.getLogger(JwtUtil.class);

/**

* 失效時間: 1 天 =24*3600*1000 ms

*/

public static final long EXPIRATION_TIME = 1 * 24 * 3600 * 1000;

/**

* 私鑰

*/

public static final String SECRET = "http://www.bj9420.com/jwt";

/**

* token 前綴

*/

public static final String TOKEN_PREFIX = "Bearer ";

/**

* Authorization header

*/

public static final String HEADER_STRING = "Authorization";

/**

* 保存的數據字段名稱

*/

public static final String USER_NAME = "userId";

public static String generateToken(String userId) {

HashMap<String, Object> map = new HashMap<>();

//可以將自定義相關的數據放入Map中

map.put(USER_NAME, userId);

String jwt = Jwts.builder()

.setClaims(map)

.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))

.signWith(SignatureAlgorithm.HS512, SECRET)

.compact();

//jwt前面一般都會加Bearer

return TOKEN_PREFIX + jwt;

}

public static HttpServletRequest validateTokenAndAddUserIdToHeader(HttpServletRequest request) {

String token = request.getHeader(HEADER_STRING);

log.info("請求token:" + token);

if (token != null) {

// 解析令牌token.

try {

Map<String, Object> body = Jwts.parser()

.setSigningKey(SECRET)

.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))

.getBody();

//遍歷自定的相關數據,可以刪除

for (Map.Entry<String, Object> entry : body.entrySet()) {

log.info("****JWT paser Key = " + entry.getKey() + ", Value = " + entry.getValue());

}

return new CustomHttpServletRequest(request, body);

} catch (Exception e) {

log.info(e.getMessage());

throw new TokenValidationException(e.getMessage());

}

} else {

throw new TokenValidationException("The token is invalid!");

}

}

public static class CustomHttpServletRequest extends HttpServletRequestWrapper {

private Map<String, String> claims;

public CustomHttpServletRequest(HttpServletRequest request, Map<String, ?> claims) {

super(request);

this.claims = new HashMap<>();

claims.forEach((k, v) -> this.claims.put(k, String.valueOf(v)));

}

@Override

public Enumeration<String> getHeaders(String name) {

if (claims != null && claims.containsKey(name)) {

return Collections.enumeration(Arrays.asList(claims.get(name)));

}

return super.getHeaders(name);

}

public Map<String, String> getClaims() {

return claims;

}

}

static class TokenValidationException extends RuntimeException {

public TokenValidationException(String msg) {

super(msg);

}

}

}

第五步:在控制類中編寫接口,如LoginController.java,對於需要令牌token驗證的,可以相應的請求路徑中加JwtAuthenticationFilter 類中定義的匹配規則標識符,如“api“,例如在LoginController類中,登錄方法public Result<Map<String, Object>> login(@RequestBody User user) ,註解路徑映射@PostMapping("/login")中,不含“api”,故該方法無需令牌token驗證;根據用戶ID獲取用戶信息方法public Result<Map<String, Object>> getUserInfo(@RequestHeader(value = JwtUtil.USER_NAME) String userId),註解路徑映射@GetMapping("/api/getUserInfo")中含有“api”,故該方法令牌token驗證;如要總個控制類中的方法都需令牌token驗證,則在類的註解路徑映射中加“api”,例如:註解路徑映射@RequestMapping("loginController")改為註解路徑映射@RequestMapping("/api/loginController"),具體示例代碼如下:

package com.bj9420.controller.login;

import com.bj9420.controller.common.BaseController;

import com.bj9420.framework.SystemConstant;

import com.bj9420.framework.util.AESUtil;

import com.bj9420.framework.util.MD5Util;

import com.bj9420.framework.util.StringUtil;

import com.bj9420.jwt.JwtUtil;

import com.bj9420.model.Menu;

import com.bj9420.model.Result;

import com.bj9420.model.User;

import com.bj9420.service.menu.IMenuService;

import com.bj9420.service.user.IUserService;

import io.jsonwebtoken.Jwts;

import io.swagger.annotations.Api;

import io.swagger.annotations.ApiOperation;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.*;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

/**

* @Title: LoginController.java

* @Description: 登錄控制類

* @author: wRitchie

* @date: 2018/12/28 15:39

* @version: V1.0

* @Copyright (c): 2018 http://bj9420.com All rights reserved.

*/

@RestController

@RequestMapping("loginController")

@Api(value = "登錄控制類", tags = "登錄控制類")

public class LoginController extends BaseController {

@Autowired

private IUserService userService;

@Autowired

private IMenuService menuService;

@GetMapping("sign")

@ApiOperation(value = "登錄測試", notes = "無權限驗證的登錄測試")

public Result<Map<String, Object>> sign(String loginName) {

logger.info("#######LoginController#######");

Map<String, Object> userMap = new HashMap<String, Object>();

userMap.put("loginName", loginName);

List<Map> userList = userService.selectByPager(userMap);

if (userList.size() > 0) {

userMap.put("password", "");

User user = userService.selectByPrimaryKey(1);

userMap.put("user", user);

return Result.success(userMap);

} else {

return Result.failure("用戶不存在,請先註冊。", null);

}

}

@PostMapping("/login")

@ApiOperation(value = "登錄接口", notes = "公開權限,用戶登錄後返回token")

public Result<Map<String, Object>> login(@RequestBody User user) {

logger.info("####### login #######" + user.getLoginName() + " password:" + user.getPassword());

String jwt="";

String pwdTmp = MD5Util.encode(user.getPassword());

//logger.info("####登錄名:"+loginName+"\t密碼:" + password);

String sKey = MD5Util.md5(SystemConstant.SYSTEM_SKEY).substring(0, 16);

String pwdEncrypt = AESUtil.encrypt(pwdTmp, sKey).substring(0, 16);

logger.info("密碼加密:" + pwdEncrypt);

Map<String, Object> userMap = new HashMap<String, Object>();

userMap.put("loginName", user.getLoginName());

List<Map> userList = userService.selectByPager(userMap);

if (userList.size() > 0) {

userMap = userList.get(0);

String pwdDb = userMap.get("password") + "";

if (pwdDb.equals(pwdEncrypt)) {

userMap.put("password", "");

jwt = JwtUtil.generateToken(userMap.get("userId") + "");

userMap.put("token", jwt);

Map<String, Object> param=new HashMap<String, Object>();

param.put("userId", userMap.get("userId"));

List<Menu> menuList=menuService.selectBySelectiveByUserId(param);

userMap.put("menuList",menuList);

return Result.success(userMap);

} else {

logger.info("密碼錯誤。");

userMap.put("authorized", new ResponseEntity(HttpStatus.UNAUTHORIZED));

userMap.put("token", jwt);

return Result.failure("密碼錯誤,登錄失敗。", userMap);

}

} else {

logger.info("用戶不存在,請先註冊。");

userMap.put("authorized", new ResponseEntity(HttpStatus.UNAUTHORIZED));

userMap.put("token", jwt);

return Result.failure("用戶不存在,請先註冊,登錄失敗。", userMap);

}

}

@GetMapping("/api/getUserInfo")

@ApiOperation(value = "獲取用戶信息", notes = "根token獲取用戶的信息")

public Result<Map<String, Object>> getUserInfo(@RequestHeader(value = JwtUtil.USER_NAME) String userId) {

logger.info("#######LoginController getUserInfo#######");

Map<String, Object> userMap = new HashMap<String, Object>();

User user = userService.selectByPrimaryKey(Integer.valueOf(userId));

userMap.put("user", user);

return Result.success("授權成功。", userMap);

}

@GetMapping("/getUserByToken")

@ApiOperation(value = "查詢用戶信息", notes = "根據token獲取用戶的信息")

public Result<Map<String, Object>> getUserByToken(String token) {

logger.info("#######LoginController getUserByToken#######");

Map<String, Object> userMap = new HashMap<String, Object>();

String userId = "";

try {

if (token != null) {

Map<String, Object> body = Jwts.parser()

.setSigningKey(JwtUtil.SECRET)

.parseClaimsJws(token.replace(JwtUtil.TOKEN_PREFIX, ""))

.getBody();

for (Map.Entry<String, Object> entry : body.entrySet()) {

logger.info("****getUserByToken JWT paser Key = " + entry.getKey() + ", Value = " + entry.getValue());

if ("userId".equals(entry.getKey())) {

userId = (String) entry.getValue();

break;

}

}

} else {

logger.info("The token is null!");

return Result.failure("The token is null!",userMap);

}

if (!StringUtil.isEmpty(userId)) {

User user = userService.selectByPrimaryKey(Integer.valueOf(userId));

userMap.put("user", user);

return Result.success("根據token獲取用戶的信息成功。", userMap);

}

return Result.failure();

} catch (Exception e) {

return Result.exception("根據token獲取用戶的信息異常:",userMap);

}

}

}

第六步:測試API,具體結果如下圖所示:

1、登錄,請求服務授權,返回令牌token,如下圖所示

SpringBoot 2.1.4集成JWT實現token驗證

2、根據返回的令牌token,請求根據用戶ID獲取用戶信息API,正確的token請求API返回如下圖:

SpringBoot 2.1.4集成JWT實現token驗證

3、修改返回的令牌token,使用錯誤的令牌token請求根據用戶ID獲取用戶信息API,錯誤的token請求API返回如下圖:

SpringBoot 2.1.4集成JWT實現token驗證

正確錯誤令牌token請求時,IDEA控制檯日誌信息如下圖所示:

SpringBoot 2.1.4集成JWT實現token驗證

至止,SpringBoot集成JWT實驗token驗證完畢,根據具體的實際應用項目,適當修改即可以使用。

相關推薦

推薦中...