JWT理論部分
摘要
是爲了在網絡應用環境中傳遞聲明而執行的一種基於json的開放標準。特別適合分佈式站點的單點登陸場景。jwt的聲明通常被用來在身份提供者和服務提供者之間傳遞被認證的用戶身份信息,以便於從服務器獲取資源,也能夠增長一些額外的其餘業務邏輯所用到的生命信息。
html
組成
JWT由三部分組成,詳情以下圖: java
- header:頭部
alg表示簽名的生成算法。 typ表示這個令牌(token)的類型(type),JWT 令牌統一寫爲JWT。
web
{ "alg":"HS256", "typ":"JWT" }
- payload:有效載荷
可選參數: iss: 簽發者 sub: 面向用戶 aud: 接收者 iat(issued at): 簽發時間 exp(expires): 過時時間 nbf(not before):不能被接收處理時間,在此以前不能被接收處理 jti:JWT ID爲web token提供惟一標識
算法
{ "sub":"123456", "exp":"1564641412" }
- signature:簽名
以header和payload生成的簽名,防止header和payload被篡改。這裏須要指定一個密碼(secret)。該密碼僅僅爲保存在服務器中,而且不能向用戶公開, 解析時須要匹配密碼(secret)才能解析成功。
數據庫
String signature = HMACSHA512(base64UrlEncode(header)+"."+ base64UrlEncode(payload), secret);
JWT 實踐
pom文件引入相關依賴
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
配置攔截器
需攔截除登陸外全部接口,並開放靜態路徑
json
@Configuration public class WebConfig extends WebMvcConfigurationSupport { @Autowired private JwtInterceptor jwtInterceptor; /** * 配置靜態資源 * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**") .addResourceLocations("classpath:/static/"); registry.addResourceHandler("/swagger-ui.html") .addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); super.addResourceHandlers(registry); } /** * 註冊攔截器 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { // 添加攔截的請求,並排除幾個不攔截的請求 registry.addInterceptor(jwtInterceptor).addPathPatterns("/**") .excludePathPatterns("/loginController/loginSystem","/swagger-ui.html", "/static/**","/swagger-resources/**","/webjars/**","); }
添加配置請求攔截器
@Component public class JwtInterceptor extends HandlerInterceptorAdapter { @Autowired private JwtTokenConfig jwtTokenConfig; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String header = Constant.getContentProperties().getStringProperty("header"); //獲取請求token String reqToken = getJwt(request); if (jwtTokenConfig.validateJwtToken(reqToken)) { return true; } return false; } /** * 獲取並返回token信息 * @param request * @return */ private String getJwt(HttpServletRequest request) { //從header中獲取 String authHeader = request.getHeader("Authorization"); String header = Constant.getContentProperties().getStringProperty("header"); if (StringUtils.startsWith(authHeader, header)) { authHeader = authHeader.replace(header+" ", ""); } return authHeader; }
添加JWT配置類
配置類用於生成、解析、校驗Token。因爲業務需求,token過時後將派發新的token。
服務器
@Configuration public class JwtTokenConfig { private static final Logger logger = LoggerFactory.getLogger(JwtTokenConfig.class); @Autowired private JwtTokenMapper jwtTokenMapper; //讀取配置文件信息 private String expire = Constant.getContentProperties().getStringProperty("expire"); private String secret = Constant.getContentProperties().getStringProperty("secret"); private String header = Constant.getContentProperties().getStringProperty("header"); /** * 生成token * @param subject * @return */ public String generateToken(String subject) { //設置過時時間 Date expireDate = new Date(System.currentTimeMillis() + 1000 * Long.parseLong(expire)); //組建header Map<String, Object> headMap = new HashMap<>(); headMap.put("alg", SignatureAlgorithm.HS256.getValue()); headMap.put("typ", "JWT"); //自定義組件 Map<String, Object> claims = new HashMap<>(); claims.put("created", DateUtil.formatDate(new Date(),DateUtil.YEAR_TO_SECOND)); String access_token = Jwts.builder().setHeader(headMap) .signWith(SignatureAlgorithm.HS512, secret) .setClaims(claims) //面向用戶 .setSubject(subject) //過時時間 .setExpiration(expireDate) //生成token .compact(); //刷新數據庫中token信息 refreshToken(subject, access_token, expireDate); logger.info(subject+" 生成後的token:"+access_token); return access_token; } /** * 得到簽名信息 * @param token * @return */ public Claims getClaimsFromToken(String token) { Claims claims = null; try { claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); logger.info("解析token結果:"+claims); } catch (ExpiredJwtException e) { //token過時建立並返回一個新token logger.info("token過時,正在建立新token\n"); JwtToken jwtToken = jwtTokenMapper.selectByToken(token); if(jwtToken != null){ String newToken = generateToken(jwtToken.getUserCode()); claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(newToken) .getBody(); logger.info("解析token結果:"+claims); } else{ logger.info("token無效"); } } catch (Exception e){ logger.info("解析異常"); } return claims; } /** * token校驗 * @param token * @return */ public boolean validateJwtToken(String token){ if(StringUtils.isNotBlank(token)){ //得到token中signature信息,並驗證過時時間 Claims claims = getClaimsFromToken(token); JwtToken jwtToken = jwtTokenMapper.selectByUserCode(claims.getSubject()); HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); if(jwtToken == null || jwtToken.getJwtId() <= 0){ logger.info("token無效"); } //返回信息中將token添加至header中 response.setHeader("Authorization",header+" "+jwtToken.getToken()); return true; } else{ logger.info("token爲空"); } } /** * 刷新數據庫中token信息 * @param subject * @param access_token * @param expireDate */ public void refreshToken(String subject, String access_token, Date expireDate){ //數據庫中存儲數據 jwtTokenMapper.deleteByUserCode(subject); JwtToken jwtToken = new JwtToken(); jwtToken.setUserCode(subject); jwtToken.setToken(access_token); jwtToken.setCreateTime(new Date()); jwtToken.setExpireTime(expireDate); jwtTokenMapper.insertSelective(jwtToken); } }
統一處理攔截器中異常
攔截器中出現異常後,一般會使用系統默認方式返回,若想自定義返回json,則須要使用異常攔截器。
網絡
@ControllerAdvice public class ExceptionInterceptor { @ExceptionHandler(value = Exception.class) @ResponseBody public Map<String, Object> allExceptionHandler(Exception e){ Map<String, Object> resultMap = new HashMap<>(2); if(e instanceof Exception){ resultMap.put("code", "500"); resultMap.put("msg", e.getMessage()); } return resultMap; } }