Springboot token令牌验证解决方案 在SpringBoot实现基于Token的用户身份验证

1.首先了解一下Token


1、token也称作令牌,由uid+time+sign[+固定参数]组成:

  • uid: 用户唯一身份标识
  • time: 当前时间的时间戳
  • sign: 签名, 使用 hash/encrypt 压缩成定长的十六进制字符串,以防止第三方恶意拼接
  • 固定参数(可选): 将一些常用的固定参数加入到 token 中是为了避免重复查数据库

 

2.token 验证的机制(流程)

  1. 用户登录校验,校验成功后就返回Token给客户端。
  2. 客户端收到数据后保存在客户端
  3. 客户端每次访问API是携带Token到服务器端。
  4. 服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码

3.使用SpringBoot搭建基于token验证

 

3.1 引入 POM 依赖

        <dependency>
              <groupId>com.auth0</groupId>
              <artifactId>java-jwt</artifactId>
              <version>3.4.0</version>
        </dependency>

3.2  新建一个拦截器配置 用于拦截前端请求 实现   WebMvcConfigurer 

 1 /***
 2  * 新建Token拦截器
 3 * @Title: InterceptorConfig.java 
 4 * @author MRC
 5 * @date 2019年5月27日 下午5:33:28 
 6 * @version V1.0
 7  */
 8 @Configuration
 9 public class InterceptorConfig implements WebMvcConfigurer {
10     @Override
11     public void addInterceptors(InterceptorRegistry registry) {
12         registry.addInterceptor(authenticationInterceptor())
13                 .addPathPatterns("/**");    // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
14     }
15     @Bean
16     public AuthenticationInterceptor authenticationInterceptor() {
17         return new AuthenticationInterceptor();// 自己写的拦截器
18     }
    
    //省略其他重写方法
19 20 }

3.3 新建一个 AuthenticationInterceptor  实现HandlerInterceptor接口  实现拦截还是放通的逻辑

 1 public class AuthenticationInterceptor implements HandlerInterceptor {
 2     @Autowired
 3     UserService userService;
 4     @Override
 5     public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
 6         String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token
 7         // 如果不是映射到方法直接通过
 8         if(!(object instanceof HandlerMethod)){
 9             return true;
10         }
11         HandlerMethod handlerMethod=(HandlerMethod)object;
12         Method method=handlerMethod.getMethod();
13         //检查是否有passtoken注释,有则跳过认证
14         if (method.isAnnotationPresent(PassToken.class)) {
15             PassToken passToken = method.getAnnotation(PassToken.class);
16             if (passToken.required()) {
17                 return true;
18             }
19         }
20         //检查有没有需要用户权限的注解
21         if (method.isAnnotationPresent(UserLoginToken.class)) {
22             UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
23             if (userLoginToken.required()) {
24                 // 执行认证
25                 if (token == null) {
26                     throw new RuntimeException("无token,请重新登录");
27                 }
28                 // 获取 token 中的 user id
29                 String userId;
30                 try {
31                     userId = JWT.decode(token).getAudience().get(0);
32                 } catch (JWTDecodeException j) {
33                     throw new RuntimeException("401");
34                 }
35                 User user = userService.findUserById(userId);
36                 if (user == null) {
37                     throw new RuntimeException("用户不存在,请重新登录");
38                 }
39                 // 验证 token
40                 JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
41                 try {
42                     jwtVerifier.verify(token);
43                 } catch (JWTVerificationException e) {
44                     throw new RuntimeException("401");
45                 }
46                 return true;
47             }
48         }
49         return true;
50     }
51 
52     @Override
53     public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
54 
55     }
56     @Override
57     public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
58 
59     }
60 }

3.4 新建两个注解 用于标识请求是否需要进行Token 验证

/***
 * 用来跳过验证的 PassToken
 * @author MRC
 * @date 2019年4月4日 下午7:01:25
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}
/**
 * 用于登录后才能操作
 * @author MRC
 * @date 2019年4月4日 下午7:02:00
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}

3.5 新建一个Server 用于下发Token

/***
 * token 下发
* @Title: TokenService.java 
* @author MRC
* @date 2019年5月27日 下午5:40:25 
* @version V1.0
 */
@Service("TokenService")
public class TokenService {

    public String getToken(User user) {
        Date start = new Date();
        long currentTime = System.currentTimeMillis() + 60* 60 * 1000;//一小时有效时间
        Date end = new Date(currentTime);
        String token = "";
        
        token = JWT.create().withAudience(user.getId()).withIssuedAt(start).withExpiresAt(end)
                .sign(Algorithm.HMAC256(user.getPassword()));
        return token;
    }
}

 

3.6 新建一个工具类 用户从token中取出用户Id

 1 /* 
 2 * @author MRC 
 3 * @date 2019年4月5日 下午1:14:53 
 4 * @version 1.0 
 5 */
 6 public class TokenUtil {
 7 
 8     public static String getTokenUserId() {
 9         String token = getRequest().getHeader("token");// 从 http 请求头中取出 token
10         String userId = JWT.decode(token).getAudience().get(0);
11         return userId;
12     }
13 
14     /**
15      * 获取request
16      * 
17      * @return
18      */
19     public static HttpServletRequest getRequest() {
20         ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
21                 .getRequestAttributes();
22         return requestAttributes == null ? null : requestAttributes.getRequest();
23     }
24 }

3.7 新建一个简单的控制器 用于验证

@RestController
public class UserApi {
    @Autowired
    UserService userService;
    @Autowired
    TokenService tokenService;

    // 登录
    @GetMapping("/login")
    public Object login(User user, HttpServletResponse response) {
        JSONObject jsonObject = new JSONObject();
        User userForBase = new User();
        userForBase.setId("1");
        userForBase.setPassword("123");
        userForBase.setUsername("mrc");

        if (!userForBase.getPassword().equals(user.getPassword())) {
            jsonObject.put("message", "登录失败,密码错误");
            return jsonObject;
        } else {
            String token = tokenService.getToken(userForBase);
            jsonObject.put("token", token);

            Cookie cookie = new Cookie("token", token);
            cookie.setPath("/");
            response.addCookie(cookie);

            return jsonObject;

        }
    }

    /***
     * 这个请求需要验证token才能访问
     * 
     * @author: MRC
     * @date 2019年5月27日 下午5:45:19
     * @return String 返回类型
     */
    @UserLoginToken
    @GetMapping("/getMessage")
    public String getMessage() {

        // 取出token中带的用户id 进行操作
        System.out.println(TokenUtil.getTokenUserId());

        return "你已通过验证";
    }
}

 

3.8 开始测试

## 成功登陆后保存token到前端cookie 以后的请求带上token即可区别是哪个用户的请求!

 

 

 

 

 我们下一个请求在请求的时候带上这个token试试

成功通过验证! 我们看一下后端控制台打印的结果!

打印出带这个token的用户 

 


 

 

DEMO测试版本:https://gitee.com/mrc1999/springbootToken

参考博客:https://www.jianshu.com/p/310d307e44c6

 

posted @ 2019-05-27 18:06  程序猿小码  阅读(29482)  评论(7编辑  收藏  举报