Security框架入门(1)

security原理就是一系列过滤器链,1主要负责处理登录请求,2负责处理异常,3负责校验

在这里插入图片描述

过滤链逻辑
  1. 用户提交用户名和密码首先经过UsernamePasswordAuthenticationFilter,这个过滤器主要是封装Authentication对象,此时还没有封装权限,作用就是UserDetails对象中的数据进行校验。注意:这个Authentication对象我们可以在UsernamePasswordAuthenticationFilter前加上一个过滤器进行封装,然后直接在我们的过滤器中调用ProvideManger进行下一步
  2. 进入ProvideManger,然后进入UserDetailsService,默认框架是从内存中查找用户信息封装UserDetails对象这里我们自己实现UserDetailsService从数据查询,然后返回UserDetails对象,在这一步中我们查询权限,然后封装到UserDetails对象中
  3. 返回的UserDetails对象和Authentication中的数据进行校验,通过PasswordEncoder进行密码比对,然后将正确的UserDetails的权限设置到Authentication中,返回Authentication。
  4. 如果上一步返回了Authenticationd对象,我们可以通过SecurityContextHolder.getContext().setAuthentication方法存储该对象。

值得注意的是SecurityContextHolder是各过滤链检查的对象,SecurityContextHolder中封装了Authentication

所以我们只需要实现UserDetails接口,从数据库查询用户名和密码,然后封装对象

登录接口

  • 整体流程,用户输入用户名和密码,会封装到Authentication中,然后经过UserDetailsService(主要是根据用户名从数据库中查询数据,并获取用户的权限信息)中,返回UserDetails对象,经过和Authentication的对比判断是否用户名和密码正确,以及用户拥有的权限

在Authentication和UserDetails的密码比对过程中,默认会用PasswordEncoder进行加密,但是一般我们不会使用默认的加密方式

  • springsecurity给我们提供了BCryptPasswordEncoder加密方式
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //重写,可以注入此对象
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Autowired
    private JwtAuthFilter jwtAuthFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
                //关闭csrf
                csrf().disable()
                //不通过session获取securityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                //对于登录接口,允许匿名访问,还没登录可以访问,登录了就访问不了
                .antMatchers("/admin/login","/admin/logout").anonymous()
                //一直允许
                .antMatchers("/admin/test").permitAll()
                //除上面所有请求都需要认证,认证之后可以访问
                .anyRequest().authenticated();
        //设置过滤器顺序
        http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

  • 这里我们实现它的UserDetails,根据查出来的数据进行Authentication对比
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
    private Admin admin;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }
    @Override
    public String getPassword() {
        return admin.getPassword();
    }
    @Override
    public String getUsername() {
        return admin.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;
    }
}

  • UserDetailsService实现类
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Resource
    private AdminService adminService;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //根据用户名查询用户信息
        LambdaQueryWrapper<Admin> lambdaQueryWrapper =new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Admin::getUsername,s);
        Admin one = adminService.getOne(lambdaQueryWrapper);
        if(Objects.isNull(one)){
            //抛出的异常会交由FilterSecurityInterceptor处理
            throw new RuntimeException("对象为空");
        }

        // TODO 查询权限信息
        return new LoginUser(one);
    }
}

此时输入数据库中的用户名和密码,可以通过,密码是加密后的密码

自己创建controller登录

  • 登录逻辑
  1. 用户第一次登录,对用户进行AuthenticationManager authenticate 进行用户认证,验证完之后UserDetails已经有数据了
  2. 认证通过了就将LoginUser 的信息存入Redis,key为key+id,然后根据用户id和username生成token
  3. 第二次用户访问,进入我们的拦截器,从请求头中获取token,然后解析id,拼接key+id,查询redis,获取LoginUser
  4. 将获取的LoginUser 封装到Authentication中,因为Authentication已经有数据了,所以其他请求可以通过。
    @PostMapping("/login")
    public ResponseResult login(@RequestBody Admin admin){
        return adminService.login(admin);
    }

在这里插入图片描述
经过AuthenticationManager和UserDetails的认证封装到Authentication对象,可以清楚的看到principal的数据和封装信息

@Service("adminService")
public class AdminServiceImpl extends ServiceImpl<AdminDao, Admin> implements AdminService {

    //此对象是封装校验的
    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public ResponseResult login(Admin admin) {
        //AuthenticationManager authenticate 进行用户认证
            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(admin.getUsername(), admin.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authToken);//会调用UseDetailsService
        //如果认证没通过,返回提示
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("登录失败");
        }
        //如果认证通过了就生成jwt,存入redis
        LoginUser principal = (LoginUser) authenticate.getPrincipal();
        String id = principal.getAdmin().getId().toString();
        String username = principal.getAdmin().getUsername();
        String jwtToken = JWTUtils.getJwtToken(id, username);

        //存入redis
        redisTemplate.opsForValue().set("login"+id, JSONUtil.toJsonStr(principal));

        return ResponseResult.ok().data("token",jwtToken);
    }
}

创建拦截器

从token中获取id,然后根据id查询用户信息,还有权限信息,然后存入SecurityContextHolder中,后面的拦截器(如FilterSecurityInterceptor)都是从这个对象中验证

  • 由于我们的过滤器的先后顺序没有设置,框架首先走的是自己的过滤器,发现没有验证,直接会抛出异常
  • 所以我们需要配置拦截器的顺序
 @Autowired
    private JwtAuthFilter jwtAuthFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
   		......
        //设置过滤器顺序
        http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

    }

  • 拦截器
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
    //继承OncePerRequestFilter是为了避免多次过滤
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        //从token中获取id,然后根据id查询用户信息
        String id = JWTUtils.getMemberIdByJwtToken(httpServletRequest);
        if(StrUtil.isBlank(id)){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }

        //根据key获取redis的数据
        String loginUserStr = redisTemplate.opsForValue().get("login" + id);
        LoginUser loginUser = JSONUtil.toBean(loginUserStr, LoginUser.class);
        //封装权限 TODO
        //保存到SecurityContextHolder
        if(BeanUtil.isNotEmpty(loginUser)){
            //授权,后面执行根据loginUser UserDetailsService会进行封装数据
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

总结

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

xJ-1715540779506)]

[外链图片转存中…(img-CqyDgXno-1715540779506)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值