spring-security的流程总结简易版(个人总结)

总结来源:b站up三更

整个security的原理其实就是一个过滤器链,内部包含了各种功能的过滤器,其中比较核心的就是关于认证的过滤器、异常处理的过滤器和关于授权的过滤器

1.关于认证

       图源自:b站up主三更

        首先,当请求带着身份信息进来,用户名通常被UsernamePasswordAuthenticationToken这个实现类封装成authentication

UsernamePasswordAuthenticationToken authentication 
= new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());

        然后,注入AuthenticationManager对象调用authenticate方法去认证并返回一个充满权限信息身份信息和细节信息的Authentication实例

Authentication authenticate =
                authenticationManager.authenticate(authenticationToken);

        这个时候,SecurityContextHolder的上下文容器就已经有个充满信息的Authentication,而调用authenticate方法的实际是AuthenticationManager的实现类ProviderManager的方法,然后在后面会自动去调用AbstractUserDetailsAuthenticationProvider的实现类的DaoAuthenticationProvider的authenticate()方法,这里所需要数据库的信息,框架会再次自动调用UserDetailsService的子类InMemoryUserDetailsManager的loadUserByUsername方法去数据库查询,但是为了个性化定制查询规则我们会自定义一个UserDetailsServiceImpl实现类去实现UserDtailsService接口的loadUserByUsername方法,然后根据自己想要的规则重写该方法去查询数据库什么的,再将查询到的用户信息和权限信息封装成一个UserDtails对象返回上去,而这个我们往往自定义一个UserDetails的实现类,成员变量往往是用户信息和权限信息,还要重写getAuthorities的方法,方法里面其中要将string类型的权限信息封装成SimpleGrantedAuthority对象并且要后面所有的重写的方法放行

@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authorities!=null){
            return authorities;
        }
        //把permission中String类型的权限信息封装成SimpleGrantedAuthority对象
        /*ArrayList<GrantedAuthority> newList = new ArrayList<>();
        for (String permission : permissions) {
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
            newList.add(authority);
        }*/
        //把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authority中
        authorities = permissions.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return authorities;
    }

返回UserDetails对象给了DaoAuthenticationProvider的authenticate方法去认证,这里会通过PasswordEncoder对比UserDetails方法中的密码和Authtication中的密码,如果正确就会把UserDetails中的权限信息设置到Authentication对象中,再将authentication返回,返回之后将authentication设置到SecurityContextHolder的context里面

        但是如果我们每次请求都去查询数据库,会造成比较大的压力,我们需要设置一个认证过滤器。我们第一次登录成功时,可以根据userid用JWT生成一个token,并将token返回给前端存入请求头里面,同时,我们也会将authentication的信息存入到reids中键设置为"login"+userid,值设置为我们返回的userDetail对象

        下次请求发过来再让我们过滤器先去过滤,过滤器中会去获取token,解析token从中取出userid,然后再用userid去redis中查询对应的用户信息,也就是userDetails对象,这里也就是说不需要再去数据库查询权限信息,直接将userDetails封装成一个authentication即可,然后再将authentication设置到SecurityContextHolder的context里面

上述就是大致的认证流程

关于数据库的密码加密

        实际项目中我们不会把密码明文存储在数据库中。 默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的 加密方式。但是我们一般不会采用这种方式。所以就需要替换PasswordEncoder。 我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。 我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该 PasswordEncoder来进行密码校验。 我们可以定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承 WebSecurityConfigurerAdapter。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Bean
    public PasswordEncoder passwordEncoder(){
        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("/user/login").anonymous()
        
        // 除上面外的所有请求全部需要鉴权认证
        .anyRequest().authenticated();
        }
}

注意:上面的所需要用到的AuthenticationManager也要注入在配置类中,这个配置类继承了WebSecurityConfigurerAdapter,需要重写configure方法对发来的请求做出处理

关于授权:

我们往往会用FilterSecurityInterceptor进行权限校验,从SecurityContextHolder中取出封装好的Authentication,然后获取权限信息,再根据判断用户是否拥有该资源的访问权限

大致的操作顺序为:

  1. 在配置类开启配置
    @EnableGlobalMethodSecurity(prePostEnabled = true)
  2. 在需要访问的资源上加上注解
    @PreAuthorize("hasAuthority('test')")
    注意:这个hasAuthority其实是可以自己定义的 可以自己写一个类然后写一个hasAuthority方法 并在@conponent里面写一个别名 然后再@PreAuthority用SPEL表达式 比如
    @PreAuthorize("@ex.hasAuthority('权限名字符串')")
    
    
    
    
    
    
    @Component("ex")
    public class SGExpressionRoot {
        public boolean hasAuthority(String authority){
            //获取当前用户的权限
            Authentication authentication =
                                    SecurityContextHolder.getContext().getAuthentication();
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    
            List<String> permissions = loginUser.getPermissions();
    
            //判断用户权限集合中是否存在authority
             return permissions.contains(authority);
        }
    }
  3. 建立RBAC模型 : 其实也就是五张表 用户表 用户角色表 角色表 角色权限表 权限表
  • sql查询大致就是通过userid查询用户对应的角色,再一个左外连根据userid对应的角色id对应的权限id查询到权限id,再根据权限id查询到权限
<select id="selectPermsByUserId" resultType="java.lang.String">
        SELECT
            DISTINCT m.`perms`
        FROM
            sys_user_role ur
                LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
                LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
                LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
        WHERE
            user_id = #{userid}
          AND r.`status` = 0
          AND m.`status` = 0
    </select>

        4.然后在配置类的config方法中添加这过滤器

//添加过滤器
http.
  addFilterBefore(jwtAuthenticationTokenFilter,
                    UsernamePasswordAuthenticationFilter.class);

关于异常处理

        在SpringSecurity 中,如果我们在认证或者授权的过程中出现了异常会被 ExceptionTranslationFilter 捕 获到。
        在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。
        如果是认证过程中出现的异常会被封装成AuthenticationException然后调用 AuthenticationEntryPoint 对象的方法去进行异常处理。
        如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用 AccessDeniedHandler 对象的方法去进行异常处理。 所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint 和 AccessDeniedHandler然后配置给 SpringSecurity 即可。

我们可以自定义实现类去自定义处理方法比如

        授权过程异常处理

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                        AccessDeniedException accessDeniedException) 
                                                                throws IOException,
                                                                 ServletException {
                ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(),"权限不足");
                String json = JSON.toJSONString(result);
                WebUtils.renderString(response,json);
    }
}

        认证过程异常处理

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponseresponse, AuthenticationException authException) throws IOException,ServletException {
        
        ResponseResult result = new
        
        ResponseResult(HttpStatus.UNAUTHORIZED.value(), "认证失败请重新登录");
        
        String json = JSON.toJSONString(result);
        
        WebUtils.renderString(response,json);
    }
}

        再配置给处理器

@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private AccessDeniedHandler accessDeniedHandler;

        再给config对象添加处理器

http.exceptionHandling().
            authenticationEntryPoint(authenticationEntryPoint).
            accessDeniedHandler(accessDeniedHandler);

其他问题详情请参考b站up三更

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值