JWT 全名 JSON WEB Token 主要做用爲用戶身份驗證, 普遍應用與先後端分離項目當中.java
JWT 的優缺點 : https://www.jianshu.com/p/af8360b83a9fweb
1, 引入Maven:spring
<!-- JWT驗證 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
2, application.properties 文件中自定義 校驗 配置數據庫
## JWT # header:憑證(校驗的變量名), expire:有效期1天(單位:s), secret:祕鑰(普通字符串) app.jwt.header=token app.jwt.expire=5184000 app.jwt.secret=aHR0cHM6Ly9teS5vc2NoaW5hLm5ldC91LzM2ODE4Njg=
3, JWT Bean 文件:apache
package com.gy.fast.common.config.jwt; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; /** * JWT類 * * @author geYang * @date 2018-05-18 */ @Component @ConfigurationProperties(prefix = "app.jwt") public class JWT { private Logger logger = LoggerFactory.getLogger(getClass()); /** * 加密祕鑰 */ private String secret; /** * 有效時間 */ private long expire; /** * 用戶憑證 */ private String header; /** * 獲取:加密祕鑰 */ public String getSecret() { return secret; } /** * 設置:加密祕鑰 */ public void setSecret(String secret) { this.secret = secret; } /** * 獲取:有效期(s) * */ public long getExpire() { return expire; } /** * 設置:有效期(s) * */ public void setExpire(long expire) { this.expire = expire; } /** * 獲取:憑證 * */ public String getHeader() { return header; } /** * 設置:憑證 * */ public void setHeader(String header) { this.header = header; } /** * 生成Token簽名 * @param userId 用戶ID * @return 簽名字符串 * @author geYang * @date 2018-05-18 16:35 */ public String generateToken(long userId) { System.out.println("header=" + getHeader() + ", expire=" + getExpire() + ", secret=" + getSecret()); Date nowDate = new Date(); // 過時時間 Date expireDate = new Date(nowDate.getTime() + expire * 1000); return Jwts.builder().setHeaderParam("typ", "JWT").setSubject(String.valueOf(userId)).setIssuedAt(nowDate) .setExpiration(expireDate).signWith(SignatureAlgorithm.HS512, getSecret()).compact(); // 注意: JDK版本高於1.8, 缺乏 javax.xml.bind.DatatypeConverter jar包,編譯出錯 } /** * 獲取簽名信息 * @param token * @author geYang * @date 2018-05-18 16:47 */ public Claims getClaimByToken(String token) { try { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } catch (Exception e) { logger.debug("validate is token error ", e); return null; } } /** * 判斷Token是否過時 * @param expiration * @return true 過時 * @author geYang * @date 2018-05-18 16:41 */ public boolean isTokenExpired(Date expiration) { return expiration.before(new Date()); } }
4, 編寫攔截器 JwtInterceptor.java :json
package com.gy.fast.common.config.jwt; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import com.gy.fast.common.exception.SysException; import io.jsonwebtoken.Claims; /** * Token驗證攔截器 * @author geYang * @date 2018-05-18 */ @Component public class JwtInterceptor extends HandlerInterceptorAdapter { @Autowired private JWT jwt; public static final String USER_KEY = "userId"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String servletPath = request.getServletPath(); System.out.println("ServletPath: " + servletPath); // 不須要驗證,直接放行 boolean isNotCheck = isNotCheck(servletPath); if (isNotCheck) { return true; } // 須要驗證 String token = getToken(request); if (StringUtils.isBlank(token)) { throw new SysException(jwt.getHeader() + "失效,請從新登陸", 401); } // 獲取簽名信息 Claims claims = jwt.getClaimByToken(token); System.out.println("TOKEN: " + claims); // 判斷簽名是否存在或過時 boolean b = claims==null || claims.isEmpty() || jwt.isTokenExpired(claims.getExpiration()); if (b) { throw new SysException(jwt.getHeader() + "失效,請從新登陸", 401); } // 將簽名中獲取的用戶信息放入request中; request.setAttribute(USER_KEY, claims.getSubject()); return true; } /** * 根據URL判斷當前請求是否不須要校驗, true:須要校驗 */ public boolean isNotCheck(String servletPath) { // 若 請求接口 以 / 結尾, 則去掉 / servletPath = servletPath.endsWith("/") ? servletPath.substring(0,servletPath.lastIndexOf("/")) : servletPath; System.out.println("servletPath = " + servletPath); for (String path : NOT_CHECK_URL) { System.out.println("path = " + path); // path 以 /** 結尾, servletPath 以 path 前綴開頭 if (path.endsWith("/**")) { String pathStart = path.substring(0, path.lastIndexOf("/")+1); System.out.println("pathStart = " + pathStart); if (servletPath.startsWith(pathStart)) { return true; } String pathStart2 = path.substring(0, path.lastIndexOf("/")); System.out.println("pathStart2 = " + pathStart2); if (servletPath.equals(pathStart2)) { return true; } } // servletPath == path if (servletPath.equals(path)) { return true; } } return false; } /** * 獲取請求Token */ private String getToken(HttpServletRequest request) { String token = request.getHeader(jwt.getHeader()); if (StringUtils.isBlank(token)) { token = request.getParameter(jwt.getHeader()); } return token; } /** * 不用攔截的頁面路徑(也可存入數據庫中), 不要以 / 結尾 */ private static final String[] NOT_CHECK_URL = {"/test/**", "/login/**"}; }
5, 配置攔截器: 後端
package com.gy.fast.common.config; import com.gy.fast.common.config.jwt.JwtInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; /** * WebMvc配置 * @author geYang * @date 2018-05-14 */ @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private JwtInterceptor jwtInterceptor; /** * APP接口攔截器 * */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor).addPathPatterns("/app/**"); } }
6, 基礎控制器:app
package com.gy.fast.module.work.controller; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gy.fast.common.config.jwt.JwtInterceptor; import com.gy.fast.common.util.HttpContextUtils; /** * 基礎Controller * @author geYang * @date 2018-05-15 */ public abstract class BaseController { protected Logger logger = LoggerFactory.getLogger(getClass()); /** * 獲取當前登陸用戶ID * @author geYang * @date 2018-05-18 19:46 */ protected Long getUserId() { HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); return Long.parseLong(request.getAttribute(JwtInterceptor.USER_KEY).toString()); } }
7, 編寫用戶控制器:前後端分離
package com.gy.fast.module.work.controller.app; import java.util.HashMap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.gy.fast.common.config.jwt.JWT; import com.gy.fast.common.util.R; import com.gy.fast.module.work.controller.BaseController; /** * 用戶 * @author geYang * @date 2018-05-18 */ @RestController @RequestMapping("app/user") public class AppUserController extends BaseController{ @Autowired private JWT jwt; /** * 獲取用戶信息 * @return * @author geYang * @date 2018-05-18 19:49 */ @GetMapping() public R info() { return R.ok(getUserId()); } /** * 用戶登陸 * @return * @author geYang * @date 2018-05-18 19:55 */ @PostMapping("login") public R login() { //生成token String token = jwt.generateToken(10); HashMap<String,Object> map = new HashMap<String,Object>(); map.put("expire", jwt.getExpire()); map.put("token", token); return R.ok(map); } }
8, 到此處就配置完成了, 經過 postman 測試成功.ide
在集成 jjwt 時,惟一注意的就是 JDK版本了, JDK版本高於1.8, 在生成token的過程當中因爲base64加密的類: javax.xml.bind.DatatypeConverter 找不到,會產生編譯錯誤.