• 用户登录和退出,以及访问请求的token校验


    1、功能描述

    在项目的实际开发中,肯定会遇到token的生成和校验。一般来说,应该是登录的时候,将登录的请求设置成白名单,登录成功后端生成token返回给前端,然后每次前端发送请求的时候都要带上token,后端会进行token校验,通过校验才可以调用接口,退出登录后token失效

    2、代码

    2.1、controller层

    @RestController
    @RequestMapping("/xxx/xxx/xxx/user")
    @Api(tags = "用户管理")
    @Slf4j
    @Validated
    public class UserController {
    
        @Autowired
        private UserService userService;
        
    	@ApiOperation(value = "用户登录")
        @WebLog(info = "用户登录")
        @SecurityParameter
        @PostMapping("/login")
        public ResponseEntity<String> login(@RequestBody @Validated UserVO userVO) {
            Map<String, Object> rm = userService.login(userVO);
            boolean flag = (boolean) rm.get("flag");
            if (flag) {
                return new ResponseEntity<>((String) rm.get("token"), HttpStatus.OK);
            } else {
                return new ResponseEntity<>((String) rm.get("msg"), HttpStatus.UNAUTHORIZED);
            }
        }
    
        @ApiOperation(value = "移动端用户登录")
        @WebLog(info = "移动端用户登录")
        @SecurityParameter
        @PostMapping("/mobileLogin")
        public ResponseEntity<LoginVO> mobileLogin(@RequestBody @Validated UserVO userVO) {
            LoginVO loginVO = new LoginVO();
            Map<String, Object> rm = userService.mobileLogin(userVO);
            boolean flag = (boolean) rm.get("flag");
            if (flag) {
                loginVO .setJwt((String) rm.get("token"));
                loginVO .setName((String) rm.get("name"));
                //为了表示可以传输多个信息,所以这里特地用了对象
                return new ResponseEntity<>(loginVO, HttpStatus.OK);
            } else {
                doctorLoginVO.setJwt((String) rm.get("msg"));
                return new ResponseEntity<>(loginVO, HttpStatus.UNAUTHORIZED);
            }
        }
    
        @ApiOperation(value = "移动端退出登录")
        @WebLog(info = "移动端退出登录")
        @SecurityParameter
        @GetMapping("/mobileLogout")
        public ResponseEntity<String> mobileLogout() {
            userService.mobileLogout();
            return new ResponseEntity<>("已成功退出", HttpStatus.OK);
        }
    
        @ApiOperation(value = "退出登录")
        @WebLog(info = "退出登录")
        @SecurityParameter
        @GetMapping("/logout")
        public ResponseEntity<String> logout() {
            userService.logout();
            return new ResponseEntity<>("已成功退出", HttpStatus.OK);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    2.2、service层(此处省略service接口,直接用serviceImpl实现类)

    @Slf4j
    @Service
    public class UserServiceImpl implements UserService {
    
    	@Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private RedisCache redisCache;
        
    	//用户登录
    	@Override
        public Map<String, Object> login(UserVO userVO) {
            Map<String, Object> rm = new HashMap<>();
            rm.put("flag", true);
            //authenticate进行用户认证
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(userVO.getName(), userVO.getPassword());
            try {
                //此处调用LoginServiceImpl(实现了security接口UserDetailsService的自定义实现类)完成登录的业务拦截
                Authentication authenticate = authenticationManager.authenticate(authenticationToken);
                if (Objects.isNull(authenticate)) {
                    //认证未通过
                    rm.put("flag", false);
                    rm.put("msg", "登录失败,请检查用户名/密码");
                    return rm;
                }
                //认证通过,使用OA账号生成一个jwt
                LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
                String name= loginUser.getUser().getName();
                rm.put("name", name);
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("name",name);
                //这个标识是为了区分是pc端还是移动端
                jsonObject.put("type","0");
                String jwt = JwtUtil.createJWT(jsonObject.toString());
                //把完整的用户信息存入redis,name作为key
                String redisKey = "login:" + name;
                redisCache.setCacheObject(redisKey, loginUser);
                //设置缓存过期时间,这边随便设置为半个月,15天
                redisCache.expire(redisKey, 15 * 60 * 60);
                //将jwt返回前端
                rm.put("token", jwt);
            } catch (BadCredentialsException e) {
                rm.put("flag", false);
                rm.put("msg", "登录失败,请检查用户名/密码");
                log.warn("登录失败:{}", e.getMessage());
            }
            return rm;
        }
    
    	//移动端用户登录
    	@Override
        public Map<String, Object> mobileLogin(UserVO userVO) {
            Map<String, Object> rm = new HashMap<>();
            rm.put("flag", true);
            //authenticate进行用户认证
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(sysUserVO.getName(), sysUserVO.getPassword());
            try {
                //此处调用LoginServiceImpl(实现了security接口UserDetailsService的自定义实现类)完成登录的业务拦截
                Authentication authenticate = authenticationManager.authenticate(authenticationToken);
                if (Objects.isNull(authenticate)) {
                    //认证未通过
                    rm.put("flag", false);
                    rm.put("msg", "登录失败,请检查用户名/密码");
                    return rm;
                }
                //认证通过,使用OA账号生成一个jwt
                LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
                String oaNumber = loginUser.getUser().getName();
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("name",name);
                jsonObject.put("type","1");
                String jwt = JwtUtil.createJWT(jsonObject.toString());
                //把完整的用户信息存入redis,name作为key
                String redisKey = "mobileLogin:" + name;
                redisCache.setCacheObject(redisKey, loginUser);
                //设置缓存过期时间,这边随便设置为半个月,15天
                redisCache.expire(redisKey, 12 * 60 * 60);
                //将jwt返回前端
                rm.put("token", jwt);
            } catch (BadCredentialsException e) {
                rm.put("flag", false);
                rm.put("msg", "登录失败,请检查用户名/密码");
                log.warn("登录失败:{}", e.getMessage());
            }
            return rm;
        }
    
    	//退出登录
     	@Override
        public void logout() {
            //从SecurityContextHolder中获取 已登录用户的信息
            UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
            //取出name
            String name= loginUser.getUser().getName();
            //删除redis中指定的值
            boolean del = redisCache.deleteObject("login:" + name);
            log.info("退出登录:redis中 {} 的用户信息已被删除: {}", name, del);
        }
    
    	//移动端退出登录
     	@Override
        public void mobileLogout() {
            //从SecurityContextHolder中获取 已登录用户的信息
            UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
            //取出name
            String name= loginUser.getUser().getName();
            //删除redis中指定的值
            boolean del = redisCache.deleteObject("mobileLogin:" + name);
            log.info("退出登录:redis中 {} 的用户信息已被删除: {}", name, del);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    2.3、用户名密码校验

    @Slf4j
    @Service
    public class LoginServiceImpl implements UserDetailsService {
    
        @Autowired
        private UserMapper userMapper;
    
    	//这里没有进行复杂的校验,有复杂的逻辑校验都可以加在这里
    	//我的User对象里面有name和password,在生成对象入库做新增操作前进行了加密
    	//.setPassword(new BCryptPasswordEncoder().encode(password))
        @Override
        public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getName,name);
            User user = userMapper.selectOne(queryWrapper);
            //如果没有查询到用户就抛出异常
            if(Objects.isNull(user)){
                throw new RuntimeException("登录失败,请检查用户名/密码");
            }
            //把数据封装成UserDetails返回
            return new LoginUser(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.4、LoginUser

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class LoginUser implements UserDetails {
    
        private User user;
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;
        }
    
        @Override
        public String getPassword() {
            return user.getPassword();
        }
    
        @Override
        public String getUsername() {
            return user.getName();
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    2.5、toekn校验拦截器

    /**
     * token过滤器,需要将该过滤器添加到Security功能中去(SecurityConfig)
     */
    @Slf4j
    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
        @Autowired
        private RedisCache redisCache;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            //登录请求不做处理,直接放行
            if ("/xxx/xxx/xxx/login".equals(request.getRequestURI())) {
                filterChain.doFilter(request, response);
                return;
            }
            if ("/xxx/xxx/xxx/dmobileLogin".equals(request.getRequestURI())) {
                filterChain.doFilter(request, response);
                return;
            }
            //可以设置请求路径中含有xxx就不需要进行token校验
            if (request.getRequestURI().contains("/xxx/")) {
                filterChain.doFilter(request, response);
                return;
            }
            //从除登录请求外的每个请求中获取token
            String token = request.getHeader("token");
            if (!StringUtils.hasText(token)) {
                //放行:即使这里因为没有token而放行,也会被后续的过滤器拦截抛出对应的异常,故不在此处拦截
    //            filterChain.doFilter(request, response);
    
                //检查每个请求都是否携带了Token(除登录请求外)
                log.warn("该请求未携带token");
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.setContentType("text/plain");
                response.setCharacterEncoding("utf-8");
                response.getWriter().print("token非法,请登录");
                return;
            }
            //解析token,取出token中的oa账号
            JSONObject jsonObject = null;
            try {
                Claims claims = JwtUtil.parseJWT(token);
                String object = claims.getSubject();
                jsonObject = JSONObject.parseObject(object);
            } catch (Exception e) {
                log.warn("token非法:{}", e.getMessage());
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.setContentType("text/plain");
                response.setCharacterEncoding("utf-8");
                response.getWriter().print("token非法,请登录");
                return;
            }
            String type = jsonObject.getString("type");
            String name= jsonObject.getString("name");
            if ("0".equals(type)) {
                //从redis中获取用户信息对象
                String redisKey = "login:" + name;
                LoginUser loginUser = redisCache.getCacheObject(redisKey);
                if (Objects.isNull(loginUser)) {
                    //redis中并没有这个用户信息
                    log.warn("用户未登录");
                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
                    response.setContentType("text/plain");
                    response.setCharacterEncoding("utf-8");
                    response.getWriter().print("用户未登录");
                    return;
                }
                //!!!把 登录用户的信息 存入SecurityContextHolder!!!
                //TODO 获取权限信息封装到Authentication中
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            } else {
                //从redis中获取用户信息对象
                String redisKey = "mobileLogin:" + name;
                LoginUser loginUser = redisCache.getCacheObject(redisKey);
                if (Objects.isNull(loginUser)) {
                    //redis中并没有这个用户信息
                    log.warn("用户未登录");
                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
                    response.setContentType("text/plain");
                    response.setCharacterEncoding("utf-8");
                    response.getWriter().print("用户未登录");
                    return;
                }
                //!!!把 登录用户的信息 存入SecurityContextHolder!!!
                //TODO 获取权限信息封装到Authentication中
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
    
            //放行
            filterChain.doFilter(request, response);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    2.6、SecurityConfig

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    
        @Autowired
        private AuthenticationEntryPoint authenticationEntryPoint;
    
        //创建BCryptPasswordEncoder注入容器,Security会自动做密码加解密的处理
        @Bean
        public PasswordEncoder passwordEncoder() {
        	//就是在这里设置的密码加密方式,我只是以此为例,可以根据需要自行修改
        	//常用的加密方式有很多,比如AES、RSA、国密加密算法等等
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    //关闭csrf
                    .csrf().disable()
                    //不通过Session获取SecurityContext
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    //对于登录接口 允许匿名访问
                    .antMatchers("/xxx/xxx/xxx/login").anonymous()
                    .antMatchers("/doc.html", "/webjars/**", "/v2/**", "/swagger-resources").permitAll()
                    .anyRequest().permitAll();
    
            //添加过滤器
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    
            //配置异常处理器
            http.exceptionHandling()
                    //配置认证失败处理器
                    .authenticationEntryPoint(authenticationEntryPoint);
    //                .accessDeniedHandler(accessDeniedHandler);
    
            //允许跨域
            http.cors();
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
  • 相关阅读:
    面试:谈一下你对Nginx的理解
    YOLO目标检测——棉花病虫害数据集+已标注txt格式标签下载分享
    React UI组件库——如何快速实现antd的按需引入和自定义主题
    IDEA中使用Tomcat Maven 插件
    RK3568-tftp更新设备树和内核&nfs挂载文件系统
    Spring Cloud(五):Spring Cloud Alibaba Nacos 1.4.X 注册中心AP & CP架构Raft源码分析
    UKP3d的excel汇总表
    并发中级(第一篇)
    为了带你们搞懂RPC,我手写了一个RPC框架
    PPT制作小技巧分享
  • 原文地址:https://blog.csdn.net/NewBeeMu/article/details/127722194