spring mvc security 的使用

在spring项目中,我们做认证功能的时候 ,基本都是使用spring security 或者 shiro。由于考虑到与spring的集成,我们更多的会去选择spring security。这个比shiro要费事点,当时第一次用的时候,也是好一顿折腾,这里就把spring security 怎么工作的做一个总结。

版本:spring boot 2.4.7
spring 的http 服务分为spring mvc 和spring webflux,这我们是用的spring mvc


spring security 的本质是一组的过滤器,但是并不是基于servlet的过滤器,而是过滤器的表现形式。我们根据官方文档可以知道,使用spring security 需要加@EnableWebSecurity 注解(实际上,不加也可以,引入了spring-boot-starter-security包就可以了)。既然你有配置项,那就好办了,我们就可以找到他的配置入口(所有的应用,必定是有一个主线程的,启动的时候会把相关的配置项加载好,或者是bean初始化好,schedule job除外,但是schedule 这种基本就是我们自己定义的了)。


org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain
这里断点可以看到,securityFilterChains就是我们的过滤器的调用链,这里有配置的匹配方式,还有过滤器,从截图可以看出,这么有13个过滤器。

把securityFilterChain给到WebSecurity

最后的 this.webSecurity.build() 会去调用 
org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild
也就是说实际的过滤器是这个FilterChainProxy,而上面显示的那13个过滤器,都是由这个过滤器去调用的。


org.springframework.security.web.FilterChainProxy#doFilterInternal


上面我们已经知道了spring security是怎么去调用的,那么具体的认证是怎么个步骤呢?
首先,我们要知道的是spring mvc的secrity是基于threadlocal来实现功能的。拿用户名密码登录来说,请求过来之后,先进行一系列的初始化,比如SecurityContextHolder,然后,进行一系列的逻辑判断。基本流程如下:

现在的项目中,我们更多是使用token去做登录认证,这里我们以jwt为例
自定义一个UserDetails

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class JWTUserDetails {
    private Long operateId;
    private String token;
    private String role;
}


写一个Authentication

public class JWTAuthenticationToken extends AbstractAuthenticationToken {
    private Object principal;
    private Object credentials;

    public JWTAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true); // must use super, as we override
    }
    
    public JWTAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
    }

    public JWTAuthenticationToken(Object principal, Object credentials,
                                  Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true); // must use super, as we override
    }

    @Override
    public Object getCredentials() {
        return credentials;
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }
}

自定义一个过滤器

@Component
@RequiredArgsConstructor
public class JWTAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
//        Claims claims = ...; //解析token
//        if(Objects.nonNull(claims)){
//            JWTUserDetails userDetails =  JWTUserDetails.builder().token("").operateId(1L).build();
//            Authentication authentication = new JWTAuthenticationToken(userDetails.getOperateId(),userDetails);
//            SecurityContextHolder.getContext().setAuthentication(authentication);
//        }
        JWTUserDetails userDetails =  JWTUserDetails.builder().token("").operateId(1L).build();
                    Authentication authentication = new JWTAuthenticationToken(userDetails.getOperateId(),userDetails);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request,response);
    }
}


配置

@RequiredArgsConstructor
@EnableWebSecurity(debug = true)
@Configuration
public class SecurityConfiguration {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/webjars/**");
    }

    @Bean
    SecurityFilterChain securityWebFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
                .addFilterAt(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
        ;
        return http.build();
    }

}


另外,很多地方讲到用AuthenticationProvider来做认证校验,这个看自己的需求吧,如果要用的话,会发现想要向BasicAuthenticationFilter中一样调用AuthenticationManager来校验的话是拿不到的

Authentication authResult = this.authenticationManager.authenticate(authRequest);


这里的解决办法是需要创建一个Configurer,会由httpSecurity调用configure方法去创建filter,这样的话就可以获得AuthenticationManager了。

spring security

关于作者

落雁沙
吹牛逼大王
获得点赞
文章被阅读