JWT请求的流程:
1.用户输入账号密码登录,前端传输给后端认证
2.认证成功后,后端用jwt创建一个token令牌
3.后端传给前端token令牌,前端把这个令牌保存下来
4.前端每次访问后端资源的时候都要带上之前保存的token令牌给后端认证
5.验证成功后返回资源给前端
JWT组成
JWT有3部分组成:
Header(头部):两部分组成,令牌的元数据,签名和/或加密算法的类型
Payload(载体):JWT中的一些数据比如签发者、过期时间,签发时间之类
Signature(签名):由base64对header部分编码后加上.和base64对Payload编码后组成的字符串,再用一个密钥sercet对组合成的字符串用由头部声明的加密方式加密组成
一个JWT如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIiwiZXhwIjoxNTc4MzA0NzI2LCJpYXQiOjE1NzgzMDExMjZ9.Vvh49ThZlFNsoTVj09QA4Wb51s7-Jk1-LyQHPuVpZCA
Springboot中整合JWT
先在 pom.xml 中引入jar包
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
然后创建两个注解 PassToken(有此注解的接口不需要验证Token)和UserLoginToken(有此注解的接口需要验证Token)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
创建一个token工具类,我在后面的token创建中Payload中只放了用户的ID,TOKEN创建时间,有效时间。所以在这个工具类中写了个获取用户ID的方法。
TokenUtil.java
public class TokenUtil {
//从token中拿到用户的ID
public static String getTokenUserId() {
String token = getRequest().getHeader("token");// 从 http 请求头中取出 token
String userId = JWT.decode(token).getAudience().get(0);
return userId;
}
/**
* 获取request
*
* @return
*/
public static HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
return requestAttributes == null ? null : requestAttributes.getRequest();
}
}
创建一个TokenService 来创建Token,也就是颁发Token。
TokenService.java
@Service
public class TokenService {
//颁发Token
public String getToken(User user){
Date start = new Date();
long current=System.currentTimeMillis()+60*60*1000; //1小时有效期
Date end=new Date(current);
String token="";
token= JWT.create().withAudience(user.getUserid()).withIssuedAt(start).withExpiresAt(end)
//这里用了HMAC加密,密钥是用户的密码
.sign(Algorithm.HMAC256(user.getPassword()));
return token;
}
}
然后创建一个拦截器,这个拦截器拦截所有请求,然后判断请求的接口有没有上面自定义的注解@UserLoginToken 注解,有该注解表示需要Token,若是遇到@PassToken 注解 则表示不需要Token,直接放行。
AuthenticationInterceptor.java
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader("token");
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//判断是否有注解PassToken
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passtoken = method.getAnnotation(PassToken.class);
if (passtoken.required())
return true;
}
//判断是否有注解UserLoginToken
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
if (token == null)
throw new RuntimeException("无token,请重新登录");
String userid;
try {
userid = JWT.decode(token).getAudience().get(0);
boolean time= JWT.decode(token).getExpiresAt().before(new Date());
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
//此处通过用户ID获得用户
User user = userService.selectByPrimaryKey(userid);
if (user == null)
throw new RuntimeException("用户不存在,请重新登录");
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);
} catch (Exception j) {
throw new RuntimeException("401");
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
最后需要把我们刚才创建的拦截器进行注册。
InterceptorConfig.java
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**"); // 拦截所有请求,通过判断是否有 @UserLoginToken注解 决定是否需要登录
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();// 自己写的拦截器
}
}
至此,Springboot整合jwt实现token的过程完成。
接下来要进行测试,创建一个测试用的User Bean
User.java
public class User {
private String userid;
private String account;
private String password;
//省略get和set
}
controller层创建测试api
TestController.java
@Controller
public class TestController {
@Autowired
TokenService tokenService;
@RequestMapping(value = "/login/{account}/{password}" ,method = RequestMethod.GET)
@ResponseBody
public String login(HttpServletResponse response, @PathVariable String account, @PathVariable String password){
User userForBase = new User();
userForBase.setUserid("1");
userForBase.setPassword("123");
userForBase.setAccount("qwe");
if (!userForBase.getPassword().equals(password)) {
return "登录失败,密码错误";
} else {
String token = tokenService.getToken(userForBase);
//创建一个名为token的cookie,供前端调用
Cookie cookie = new Cookie("token", token);
cookie.setPath("/");
response.addCookie(cookie);
return token;
}
}
@UserLoginToken
@RequestMapping("/verify")
@ResponseBody
public String verify() {
// 取出token中带的用户id 进行操作
System.out.println(TokenUtil.getTokenUserId());
return "你已通过验证";
}
}
使用postmen来测试
下面返回的内容就是Token
这是返回的cookie
然后请求 localhost:8080/verify 接口 带上前面返回的token参数
如上图 表示带上的token验证通过,且在控制台输出了用户的ID