• 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

  • 相关阅读:
    DSP 数学工具回顾:从无穷级数 到 快速傅立叶变换
    用c++设计音效插件 : 10 基础DSP理论 (第一部分)
    用c++设计音效插件 : 10 基础DSP理论 (第二部分)
    用c++设计音效插件 :6 ASPiK Programming Guide
    python 如何在多层循环中使用break/continue
    李沐 如何判断(你自己的)研究工作的价值
    SpringBoot 2.x集成Elasticsearch
    SpringBoot 2.x集成AWS S3对象存储
    关于pycharm一直index这件事
    docker 安装 nacos
  • 原文地址:https://www.cnblogs.com/ChromeT/p/10932202.html
Copyright © 2020-2023  润新知