本文主要研究一下几种自定义spring security的方式

主要方式

  • 自定义UserDetailsService
  • 自定义passwordEncoder
  • 自定义filter
  • 自定义AuthenticationProvider
  • 自定义AccessDecisionManager
  • 自定义securityMetadataSource
  • 自定义access访问控制
  • 自定义authenticationEntryPoint
  • 自定义多个WebSecurityConfigurerAdapter

自定义UserDetailsService

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //......
    @Bean
    @Override
    protected UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("demoUser1").password("123456")
                .authorities("ROLE_USER","read_x").build());
        manager.createUser(User.withUsername("admin").password("123456")
                .authorities("ROLE_ADMIN").build());
        return manager;
    }
}
通过重写userDetailsService()方法自定义userDetailsService。这里展示的是InMemoryUserDetailsManager。
spring security内置了JdbcUserDetailsManager,可以自行扩展

自定义passwordEncoder

自定义密码的加密方式,实例如下
public class DemoAuthFilter extends GenericFilterBean {

<span >private</span> <span >final</span> <span >AuthenticationManager</span> authenticationManager;

public <span >DemoAuthFilter</span>(<span >AuthenticationManager</span> authenticationManager) {
    <span >this</span>.authenticationManager = authenticationManager;
}

<span >@Override</span>
public void doFilter(<span >ServletRequest</span> servletRequest, <span >ServletResponse</span> servletResponse, <span >FilterChain</span> filterChain) <span >throws</span> <span >IOException</span>, <span >ServletException</span> {
    <span >HttpServletRequest</span> httpServletRequest = (<span >HttpServletRequest</span>) servletRequest;
    <span >HttpServletResponse</span> httpServletResponse = (<span >HttpServletResponse</span>) servletResponse;

    <span >String</span> token = httpServletRequest.getHeader(<span >"app_token"</span>);
    <span >if</span>(<span >StringUtils</span>.isEmpty(token)){
        httpServletResponse.sendError(<span >HttpServletResponse</span>.<span >SC_UNAUTHORIZED</span>, <span >"invalid token"</span>);
        <span >return</span> ;
    }

    <span >try</span> {
        <span >Authentication</span> auth = authenticationManager.authenticate(<span >new</span> <span >WebToken</span>(token));
        <span >SecurityContextHolder</span>.getContext().setAuthentication(auth);
        filterChain.doFilter(servletRequest, servletResponse);
    } <span >catch</span> (<span >AuthenticationException</span> e) {
        httpServletResponse.sendError(<span >HttpServletResponse</span>.<span >SC_UNAUTHORIZED</span>, e.getMessage());
    }
}

}

设置filter顺序

上面定义完filter之后,然后就要将它放置到filterChain中
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //......
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(new DemoAuthFilter(authenticationManager()), BasicAuthenticationFilter.class);
        http.csrf().disable();
        http.logout().disable();
        http.sessionManagement().disable();
    }
}
这里把他添加在BasicAuthenticationFilter之前,当然可以根据情况直接替换UsernamePasswordAuthenticationFilter
http.addFilterAt(new DemoAuthFilter(authenticationManager()),UsernamePasswordAuthenticationFilter.class);

自定义AuthenticationProvider

AuthenticationManager接口有个实现ProviderManager相当于一个provider chain,它里头有个List<AuthenticationProvider> providers,通过provider来实现认证。

public class WebToken extends AbstractAuthenticationToken {

<span >private</span> <span >final</span> <span >String</span> token;

public <span >WebToken</span>(<span >String</span> token) {
    <span >super</span>(<span >null</span>);
    <span >this</span>.token = token;
}

<span >@Override</span>
public <span >Object</span> getCredentials() {
    <span >return</span> <span >this</span>.token;
}

<span >@Override</span>
public <span >Object</span> getPrincipal() {
    <span >return</span> <span >null</span>;
}

}

这里就自定义一下支持这类WebToken的AuthenticationProvider

AuthenticationProvider要实现的功能就是根据参数来校验是否可以登录通过,不通过则抛出异常;通过则获取其GrantedAuthority填充到authentication中
如果是继承了AbstractAuthenticationToken,则是填充其authorities属性
前面自定义的DemoAuthFilter会在登陆成功之后,将authentication写入到SecurityContextHolder的context中
可以实现AuthenticationProvider接口,或者继承AbstractUserDetailsAuthenticationProvider(默认集成了preAuthenticationChecks以及postAuthenticationChecks)
@Service
public class MyAuthProvider implements AuthenticationProvider {
    //...
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //......
    }
    @Override
    public boolean supports(Class<?> authenticationClass) {
        return return (WebToken.class
                .isAssignableFrom(authenticationClass));
    }
}

自定义AccessDecisionManager

前面有filter处理了登录问题,接下来是否可访问指定资源的问题就由FilterSecurityInterceptor来处理了。而FilterSecurityInterceptor是用了AccessDecisionManager来进行鉴权。

AccessDecisionManager的几个实现:

  • AffirmativeBased(spring security默认使用)
只要有投通过(ACCESS_GRANTED)票,则直接判为通过。如果没有投通过票且反对(ACCESS_DENIED)票在1个及其以上的,则直接判为不通过。
  • ConsensusBased(少数服从多数)
通过的票数大于反对的票数则判为通过;通过的票数小于反对的票数则判为不通过;通过的票数和反对的票数相等,则可根据配置allowIfEqualGrantedDeniedDecisions(默认为true)进行判断是否通过。
  • UnanimousBased(反对票优先)
无论多少投票者投了多少通过(ACCESS_GRANTED)票,只要有反对票(ACCESS_DENIED),那都判为不通过;如果没有反对票且有投票者投了通过票,那么就判为通过.

实例

其自定义方式之一可以参考聊聊spring security的role hierarchy,展示了如何自定义AccessDecisionVoter。

自定义securityMetadataSource

主要是通过ObjectPostProcessor来实现自定义,具体实例可参考spring security动态配置url权限

自定义access访问控制

对authorizeRequests的控制,可以使用permitAll,anonymous,authenticated,hasAuthority,hasRole等等

                .antMatchers("/login","/css/**", "/js/**","/fonts/**","/file/**").permitAll()
                .antMatchers("/anonymous*").anonymous()
                .antMatchers("/session").authenticated()
                .antMatchers("/login/impersonate").hasAuthority("ROLE_ADMIN")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/auth/*").hasAnyRole("ADMIN","USER")
这些都是利用spring security内置的表达式。像hasAuthority等,他们内部还是使用access方法来实现的。因此我们也可以直接使用access,来实现最大限度的自定义。

实例

@Component
public class AuthService {

<span >public</span> <span class="hljs-built_in">boolean</span> canAccess(HttpServletRequest request, Authentication authentication) {
    <span class="hljs-built_in">Object</span> principal = authentication.getPrincipal();
    <span >if</span>(principal == <span >null</span>){
        <span >return</span> <span >false</span>;
    }

    <span >if</span>(authentication <span >instanceof</span> AnonymousAuthenticationToken){
        <span >//check if this uri can be access by anonymous</span>
        <span >//return</span>
    }

    Set&lt;<span class="hljs-built_in">String</span>&gt; roles = authentication.getAuthorities()
            .stream()
            .map(e -&gt; e.getAuthority())
            .collect(Collectors.toSet());
    <span class="hljs-built_in">String</span> uri = request.getRequestURI();
    <span >//check this uri can be access by this role</span>

    <span >return</span> <span >true</span>;

}

}

自定义authenticationEntryPoint

比如你想给basic认证换个realmName,除了再spring security配置中指定

security.basic.realm=myrealm

也可以这样