当前位置 博文首页 > 俺叫啥好嘞的博客:Springboot整合JWT实现鉴权

    俺叫啥好嘞的博客:Springboot整合JWT实现鉴权

    作者:[db:作者] 时间:2021-09-14 16:29

    在实现Springboot集成JWT之前,我们需要先明确JWT是如何实现身份鉴别的:

    JWT鉴别身份的流程大概为:用户登录成功后Api服务器将会生成一串包含用户信息的token,将token发送至客户端机器,由客户端进行保存,当客户端机器每次访问JWT所保护的API时都需要进行验证,服务器端便会获取JWT信息,在通过服务器对JWT中Payload部分的信息进行加密,对比实际加密出来的结果是否一致,如果一致,则通过验证。

    1.生成token:
    用户登录——>成功(如果失败,则返回登录)——>生成token——>将某些用户属性进行加密存放在token中——>通过http响应response将token返回给客户端进行保存

    2.身份鉴别:
    访问JWT保护的URL——>获取请求头中携带的Authorization属性值——>进行token有效验证(失效则重新登录)——>成功访问资源

    因为每次访问资源都需要进行鉴别,所以每次请求中都需要携带Authorization属性

    功能实现

    1.定义注解

    @AdminLoginToken
    
    admin登录认证注解,如果URL含有@AdminLoginToken,则受到@AdminLoginToken保护,需要进行JWT验证
    
    /**
     * admin登录检查注解
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AdminLoginToken {
        boolean required() default true;
    }
    
    @PassToken
    
    跳过检查注解,如果URL含有@PassToken,则跳过,不需要执行认证
    
    /**
     * 跳过认证注解
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PassToken {
        boolean required() default true;
    }
    

    2.创建实体类

    创建用于认证的实体Admin,属性包括adminId,adminName,adminPassword
    
    @Data
    public class Admin implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private Integer id;
    
        private String username;
    
        private String password;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    

    3.实现拦截

    InterceptorConfig类
    
    用于拦截所有的客户端请求,将拦截下的请求转至AuthenticationInterceptor类,进行下一步鉴别,查看请求是否公开,或是受到JWT保护。
    
    /**
     * 拦截配置
     */
    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(authenticationInterceptor())
                    .addPathPatterns("/**");    // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
        }
        @Bean
        public AuthenticationInterceptor authenticationInterceptor() {
            return new AuthenticationInterceptor();
        }
    }
    
    AuthenticationInterceptor类
    
    用于鉴别API是否受JWT保护,如果不受则释放对应API的拦截,如果受保护则执行JWT验证,验证通过则释放API,验证不通过则返回401/**
     * 身份验证拦截
     */
    public class AuthenticationInterceptor implements HandlerInterceptor {
    
        @Autowired
        IAdminService adminService;
    
        @Override
        public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
            String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 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;
                }
            }
            //检查有没有需要用户权限的注解
            if (method.isAnnotationPresent(AdminLoginToken.class)) {
                AdminLoginToken adminLoginToken = method.getAnnotation(AdminLoginToken.class);
                if (adminLoginToken.required()) {
                    // 执行认证
                    if (token == null) {
                        throw new RuntimeException("无token,请重新登录");
                    }
                    // 获取 token 中的 adminId
                    String adminId;
                    try {
                        adminId = JWT.decode(token).getAudience().get(0);
                    } catch (JWTDecodeException j) {
                        throw new RuntimeException("401");
                    }
                    QueryWrapper queryWrapper=new QueryWrapper<>();
                    queryWrapper.eq("adminId",adminId);
                    Admin admin = adminService.getOne(queryWrapper);
                    if (admin == null) {
                        throw new RuntimeException("用户不存在,请重新登录");
                    }
                    // 验证 token
                    JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(admin.getPassWord())).build();
                    try {
                        jwtVerifier.verify(token);
                    } catch (JWTVerificationException e) {
                        throw new RuntimeException("401");
                    }
                    return true;
                }
            }
            return true;
        }
    

    }

    4.异常处理

    由于token验证不通过,拦截后是抛出对应异常,为了防止程序crash,所以需要进行异常处理
    
    /**
     * 全局异常处理类
     */
    @ControllerAdvice
    public class GloablExceptionHandler {
        @ResponseBody
        @ExceptionHandler(Exception.class)
        public Object handleException(Exception e) {
            String msg = e.getMessage();
            if (msg == null || msg.equals("")) {
                msg = "服务器出错";
            }
            ObjectRestResponse object = new ObjectRestResponse();
            object.data(msg);
            return object;
        }
    }
    

    5.生成token

    TokenService类
    
    用于生成以adminPassword作为密钥的token
    
    @Service("TokenService")
    public class TokenService {
        public String getToken(Admin admin) {
            String token="";
            token= JWT.create().withAudience(admin.getAdminName())// 将 adminId 保存到 token 里面
                    .sign(Algorithm.HMAC256(admin.getAdminPassword()));// 以 adminPassword 作为 token 的密钥
            return token;
        }
    }
    

    6.控制层

    AdminController类
    
    编写AdminController,实现admin登录,以及admin身份验证
    
    @RestController
    @RequestMapping("/admin")
    public class AdminController {
    
        @Autowired
        IAdminService adminService;
        @Autowired
        TokenService tokenService;
    
        @PostMapping("/login")
        public ObjectRestResponse login(String username, String password){