JWT
全称Json Web Token,是为了在应用间传递声明的基于json的开放式标准,用于双方之间以json的形式传递安全信息,因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。
JWT结构
jwt由三段信息构成,以.
号隔开,连接在一起就构成了jwt字符串,如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.eyJzdWIiOiIxIiwiaXNzIjoiaHMiLCJleHAiOjE1NjAzOTM3NzB9
.ooOZAXtruR_aWKbQofolC8TXaKU65I9nPubhBW3LRq4
如上.
号隔开的三段信息,分别代表着头部,负载,签证
-
Header
JWT的头部承载两部分信息:token类型和采用的加密算法{ "alg": "HS256", "typ": "JWT" }
-
Payload
iss
: jwt签发者
sub
: 面向的用户(jwt所面向的用户)
aud
: 接收jwt的一方
exp
: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间)
nbf
: 定义在什么时间之前,该jwt都是不可用的.
iat
: jwt的签发时间
jti
: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。 -
Signature
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt的第三部分。
SpringBoot集成JWT
-
JwtUtils :
import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import static org.apache.commons.lang3.math.NumberUtils.toInt; import static org.apache.commons.lang3.math.NumberUtils.toLong; /** * @author: hs * @Date: 2019/6/11 20:58 * @Description: */ @Data @Component @ConfigurationProperties(prefix = "api.jwt") public class JwtUtils { private static final long EXPIRE_TIME = 30 * 60; private static final String SECRET = "HYBI%&J)(UJJ&TG&"; private Long expire; private String secret; /** * @return 加密的token */ public String sign(UserInfo userInfo) { Date date = new Date(System.currentTimeMillis() + toLong(this.expire.toString(), EXPIRE_TIME) * 1000); Algorithm algorithm = Algorithm.HMAC256(defaultIfBlank(this.secret, SECRET)); // 附带username信息 return JWT.create() .withSubject(userInfo.getId().toString()) .withIssuer(userInfo.getUsername()) .withExpiresAt(date) .sign(algorithm); } /** * 校验token是否正确 * * @param token 密钥 * @return 是否正确 */ public Map<String, Claim> verify(String token, String username) { try { Algorithm algorithm = Algorithm.HMAC256(defaultIfBlank(this.secret, SECRET)); JWTVerifier verifier = JWT.require(algorithm).withIssuer(username).build(); DecodedJWT jwt = verifier.verify(token); return jwt.getClaims(); } catch (Exception e) { throw AppointException.errorMessage("鉴权失败,无效的用户"); } } /** * 获得token中的信息无需secret解密也能获得 * * @return token中包含的用户名 */ public String getUsername(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getIssuer(); } catch (JWTDecodeException e) { throw AppointException.errorMessage("无效的token,请重新登录"); } } /** * 获得token中的信息无需secret解密也能获得 * * @return token中包含的id */ public Integer getUserId(String token) { try { DecodedJWT jwt = JWT.decode(token); return toInt(jwt.getSubject()); } catch (JWTDecodeException e) { throw AppointException.errorMessage("无效的token,请重新登录"); } } }
-
登录认证拦截器
@Component public class AuthenticationInterceptor implements HandlerInterceptor { private static final String TOKEN = "token"; private final JwtUtils jwtUtils; @Autowired public AuthenticationInterceptor(JwtUtils jwtUtils) { this.jwtUtils = jwtUtils; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 从 http 请求头中取出 token String token = request.getHeader(TOKEN); if (StringUtils.isBlank(token)) { token = request.getParameter(TOKEN); } // 如果不是映射到方法直接通过 if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); //LoginSkip,有则跳过认证 if (method.isAnnotationPresent(LoginSkip.class)) { LoginSkip loginSkip = method.getAnnotation(LoginSkip.class); if (loginSkip.required()) { return true; } } //检查有没有需要用户权限的注解 if (method.isAnnotationPresent(LoginAuth.class)) { LoginAuth userLogin = method.getAnnotation(LoginAuth.class); if (userLogin.required()) { // 执行认证 if (StringUtils.isBlank(token)) { throw new RuntimeException("无效token,请重新登录"); } String username = jwtUtils.getUsername(token); jwtUtils.verify(token, username); return true; } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { } }
-
拦截器注册配置
@Configuration public class InterceptorConfig implements WebMvcConfigurer { private final AuthenticationInterceptor authenticationInterceptor; @Autowired public InterceptorConfig(AuthenticationInterceptor authenticationInterceptor) { this.authenticationInterceptor = authenticationInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authenticationInterceptor).addPathPatterns("/**"); } }
-
api接口
@RestController @RequestMapping("/api") @Api(description = "登录") public class LoginController { private final LoginService loginService; private final JwtUtils jwtUtils; @Autowired public LoginController(LoginService loginService, JwtUtils jwtUtils) { this.loginService = loginService; this.jwtUtils = jwtUtils; } @PostMapping("/login") @ApiOperation(value = "login接口", notes = "jwt接口权限验证") public AbstractApiResult login(@RequestBody UserInfo userInfo) { UserInfo user = loginService.getUserInfo(userInfo.getUsername()); if (user == null) { throw AppointException.errorMessage(100, "用户名不存在"); } String token = jwtUtils.sign(user); return AbstractApiResult.success(token); } @LoginAuth @GetMapping("testToken") @ApiOperation(value = "token", notes = "token测试") public String getMessage(@RequestHeader String token) { return "success"; } }
-
测试
访问接口curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{ \ "id": 0, \ "username": "hs", \ "password": "string" \ }' 'http://localhost:9999/api/login'
带着token令牌eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHMiLCJleHAiOjE1NjAzOTM3NzB9.ooOZAXtruR_aWKbQofolC8TXaKU65I9nPubhBW3LRq4
访问接口:curl -X GET --header 'Accept: text/plain' --header 'token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHMiLCJleHAiOjE1NjAzOTM3NzB9.ooOZAXtruR_aWKbQofolC8TXaKU65I9nPubhBW3LRq4' 'http://localhost:9999/api/testToken'