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

认证主体业务

配置 mybatisplus

为 User 创建 mapper

代码清单:/mapper/UserMapper.java

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

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

@SpringBootApplication
@MapperScan("com.zhiller.sangengsecurity.mapper")
public class SanGengSecurityApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(SanGengSecurityApplication.class, args);

    }
}

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);
    }
}

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;
    }
}

LoginService

设置登录服务接口

代码清单:/service/LoginService.java

public interface LoginService {
    ResponseResult login(User user);

    ResponseResult logout();
}

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,"退出成功");
    }
}

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();
    }
}

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);
    }
}

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();
    }
}

你可能感兴趣的:(#,中间件,开发语言,java,redis)