• 【SpringSecurity】三更草堂项目案例分析2 - 认证主体业务编写


    认证主体业务

    配置 mybatisplus

    为 User 创建 mapper

    代码清单:/mapper/UserMapper.java

    @Mapper
    public interface UserMapper extends BaseMapper<User> {
    }
    
    • 1
    • 2
    • 3

    在入口类中对该 mapper 执行扫描

    @SpringBootApplication
    @MapperScan("com.zhiller.sangengsecurity.mapper")
    public class SanGengSecurityApplication {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext run = SpringApplication.run(SanGengSecurityApplication.class, args);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    UserDetailsServiceImpl

    该实现类实现了 UserDetailsService ,通过条件查询从 mysql 中找到对应用户
    如果用户不存在,抛出异常
    如果用户存在,返回一个 UserDetails 对象

    代码清单:/service/UserDetailsServiceImpl.java

    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //根据用户名查询用户信息
            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(User::getUserName,username);
            User user = userMapper.selectOne(wrapper);
            //如果查询不到数据就通过抛出异常来给出提示
            if(Objects.isNull(user)){
                throw new RuntimeException("用户名或密码错误");
            }
            //TODO 根据用户查询权限信息 添加到LoginUser中
    
            //封装成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

    LoginUser

    该实体类继承了 UserDetails

    @AllArgsConstructor 注解可以自动生成带参数的构造函数,因为我们已经在该类中设置了私有变量 User,所以生成的构造函数就会自带形参 user,然后对应 UserDetailsServiceImpl 中的末尾返回的就是一个 UserDetials 对象

    默认的 LoginUser 对象仅需要两个参数:userid 和 password

    代码清单:/domain/LoginUser.java

    @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.getUserName();
        }
    
    
        // 下面的这四个玩意必须设置成true,否则不给你验证!!!
        @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

    LoginService

    设置登录服务接口

    代码清单:/service/LoginService.java

    public interface LoginService {
        ResponseResult login(User user);
    
        ResponseResult logout();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    LoginServiceImpl

    实现登录与登出服务

    由于内容过多,具体代码功能查看下方注释,十分详细

    代码清单:/service/LoginServiceImpl.java

    @Service
    public class LoginServiceImpl implements LoginService {
    
        @Autowired
        private AuthenticationManager authenticationManager;
        @Autowired
        private RedisCache redisCache;
    
        @Override
        public ResponseResult login(User user) {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
    
            // AuthenticationManager对象,用于处理认证操作
            // authenticate()方法会触发Spring Security进行认证,返回一个Authentication对象authenticate,表示认证成功
            // 如果认证失败,即authenticate为null,则抛出RuntimeException异常
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            if(Objects.isNull(authenticate)){
                throw new RuntimeException("用户名或密码错误");
            }
    
            //使用userid生成token
            LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
            String userId = loginUser.getUser().getId().toString();
            String jwt = JwtUtil.createJWT(userId);
            //authenticate存入redis
            redisCache.setCacheObject("login:"+userId,loginUser);
            //把token响应给前端
            HashMap<String,String> map = new HashMap<>();
            map.put("token",jwt);
            return new ResponseResult(200,"登陆成功",map);
        }
    
        @Override
        public ResponseResult logout() {
            // 通过SecurityContextHolder获取当前登录用户的认证信息Authentication
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    
            // 认证信息中的主体对象转换为LoginUser对象
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
            Long userid = loginUser.getUser().getId();
            // 删除redis数据库中的对应对象
            redisCache.deleteObject("login:"+userid);
            return new ResponseResult(200,"退出成功");
        }
    }
    
    • 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

    LoginController

    代码清单:/controller/LoginController.java

    @RestController
    public class LoginController {
    
        @Autowired
        private LoginService loginService;
    
        @PostMapping("/user/login")
        public ResponseResult login(@RequestBody User user) {
            return loginService.login(user);
        }
    
        @RequestMapping("/user/logout")
        public ResponseResult logout() {
            return loginService.logout();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    JWT 过滤器

    配置 JWT 过滤器,实现用户 JWT 校验

    代码清单:/filter/JwtAuthenticationTokenFilter.java

    // 继承自OncePerRequestFilter,它是Spring提供的一个过滤器基类,确保每个请求只被过滤一次
    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
        @Autowired
        private RedisCache redisCache;
    
        // 实现了具体的过滤逻辑
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            //获取token
            String token = request.getHeader("token");
            if (!StringUtils.hasText(token)) {
                //放行
                filterChain.doFilter(request, response);
                return;
            }
    
            // 解析token
            // 因为JWT中的Subject存储的就是userid,JWT解析后可以取出来放入redis进行比对
            String userid;
            try {
                Claims claims = JwtUtil.parseJWT(token);
                userid = claims.getSubject();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("token非法");
            }
    
            //从redis中获取用户信息
            String redisKey = "login:" + userid;
            LoginUser loginUser = redisCache.getCacheObject(redisKey);
            if(Objects.isNull(loginUser)){
                throw new RuntimeException("用户未登录");
            }
    
            // 存入SecurityContextHolder,便于后续logout方法直接从这里面取出当前登录的用户信息
            //TODO 获取权限信息封装到Authentication中
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(loginUser,null,null);
            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

    SecurityConfig

    配置 security 基本属性

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        // security自带的BCryptPasswordEncoder来对用户密码进行加密
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        // 自动装配JWT过滤器
        @Autowired
        JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    //关闭csrf
                    .csrf().disable()
                    //不通过Session获取SecurityContext
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // 对于登录接口 允许匿名访问
                    .antMatchers("/user/login").anonymous()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated();
    
            //把token校验过滤器添加到过滤器链中
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    
    • 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

  • 相关阅读:
    23、线程
    C++ 中的 string::npos 示例
    java毕业设计儿童教育系统Mybatis+系统+数据库+调试部署
    Thinkphp6 配置并使用redis图文详解 小皮面板
    【Redis,Java】Redis的两种序列化方式—nosql数据库
    用代谢组学解密纳米颗粒缓解烟草重金属中毒机制
    国际腾讯云直播推流配置教程!
    树、二叉树概念(+堆的实现)
    什么是接口
    CentOS7.9 Nginx + EMQX集群组建 MQTTS平台
  • 原文地址:https://blog.csdn.net/delete_you/article/details/132941411