SpringSecurity

一 、SpringSecurity过滤器链

过滤器链
spring security本质上是一个过滤器链,我们的工作就是负责自定义实现其中的一些过滤器,然后加入一些配置类。

二、Spring Security认证完整过程

SpringSecurity 代码过程
我们的实现过程

登录

1.controller

@Autowired
    private LoginService loginService;

    @PostMapping("/user/login")
    public ResponseResult login(@RequestBody User user){
        // 登录
       return loginService.login(user);
    }

2.service

注:这里对应认证流程中的第一步,封装用户名和密码到一个Authentication对象中,但是Authentication是一个接口,所以我们new一个他的实现类。

authenticationManager.authenticate(authenticationToken) 来验证和数据库中的用户名和密码是否正确。如果认证通过的话,这里会返回一个UserDetails对象。

authenticate 会调用 UserDetailsService接口中的 loadUserByUsername(String username) throws UsernameNotFoundException;

但是我们不能使用jdk中的实现方法,因为他是去内存中查询,我们需要自定义实现类,来实现 loadUserByUsername(String username); 见下面。

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    @Override
    public ResponseResult login(User user){
        // AuthenticationManager authenticate进行用户认证
        // 将登录时的用户名和密码封装成 authentication
//        Authentication 是一个接口 需要使用 UsernamePasswordAuthenticationToken实现类 来封装用户名和密码
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken); // 这个地方经过一些列流程 到达 自己实现的 userDetailService 实现类 去查数据库,在这一步也是用到了SecurityConfig 中的PasswordEncoder
        System.out.println(authenticate);
        // 如果认证不通过,给出提示
        if (Objects.isNull(authenticate)){
            throw new RuntimeException("登录失败!");
        }
        //认证通过,使用userId 生成 jwt jwt存入ResponseResult
        LoginUser loginUser = (LoginUser)authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();

        String jwt = JwtUtils.createJWT(userId);
        HashMap<String,String> jwtMap = new HashMap<>();
        jwtMap.put("token",jwt);
        //用户信息 jwt 存入 redis

//        System.out.println("loginService ====" + loginUser);
        redisCache.setCacheObject("login:"+userId,loginUser);

        return new ResponseResult(200,"success",jwtMap);
    }
自定义UserDetailsService实现类

注:这里返回的LoginUser是自定义实体类,实现了UserDetails,看下面;
最终返回登录结果。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    public UserMapper userMapper;

    @Autowired
    public MenuMapper menuMapper;

//    @Autowired
//    public PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

//        System.out.println(username);

        // 查询用户信息
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserName,username);
        User user = userMapper.selectOne(queryWrapper);
//        System.out.println(user)
//        如果没有查询到用户,就抛出异常

        if(Objects.isNull(user)){
            throw new RuntimeException("用户名不存在,或者密码错误!!!");
        }

        // TODO 查询权限信息
        List<String> list = menuMapper.selectPermsByUserId(user.getId());

        // 封装用户信息 到 userdetails
        return new LoginUser(user,list); // 这里返回了userdetails对象 里面包含密码 要和登陆密码进行对比 ,不对则登陆失败
    }
}

LoginUser实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {

    private User user;

    private List<String> permissions;

    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    @JSONField(serialize = false)
    private List<GrantedAuthority> authorities;

    // 返回权限信息
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authorities != null){
            return authorities;
        }
        authorities = new ArrayList<>();
        for (String permission : permissions) {
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
            authorities.add(simpleGrantedAuthority);
        }

        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    // 是否过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 是否超时
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
JwtAuthenticationTokenFilter

当用户登陆后访问其他资源时,首先要通过这个过滤器来验证是否登录过,然后通过了就可以放行下面的过程。

	@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
        String userId;
        try {
            Claims claims = JwtUtils.parseJWT(token);
            userId = claims.getSubject(); // 因为之前时使用userId 生成的 token ,所以这里可以解析到 userId
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token 非法");
        }

        String redisKey = "login:" + userId;
        System.out.println("redisKey =============== " + redisKey);
        //从redis中获取用户信息
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        System.out.println("filterloginUser ====== " + loginUser);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
        // 存入SecurityContexHolder
        // 获取权限信息
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        // 放行
        filterChain.doFilter(request,response);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值