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'