Spring security customization guide

  spring-security

Order

This article focuses on several ways to customize spring security.

Main mode

  • Custom UserDetailsService
  • Custom passwordEncoder
  • Custom filter
  • Custom AuthenticationProvider
  • Customize AccessDecisionManager
  • Custom securityMetadataSource
  • Customize access access control
  • Custom authenticationEntryPoint
  • Customize multiple WebSecurityConfigurerAdapter

Custom 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;
    }
}

Customize userDetailsService by overriding the userDetailsService () method. Here is InMemoryUserDetailsManager.
Spring security has built-in JdbcUserDetailsManager and can extend itself.

Custom passwordEncoder

An example of how to encrypt a custom password is as follows

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //......

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(encoder());
        return authProvider;
    }

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

Custom filter

Custom filters cannot be separated from understanding the order in which spring security built-in filters:

Standard Filter Aliases and Ordering

The various filter built into spring security are listed in the following order:

Alias Filter Class Namespace Element or Attribute
CHANNEL_FILTER ChannelProcessingFilter http/intercept-url@requires-channel
SECURITY_CONTEXT_FILTER SecurityContextPersistenceFilter http
CONCURRENT_SESSION_FILTER ConcurrentSessionFilter session-management/concurrency-control
HEADERS_FILTER HeaderWriterFilter http/headers
CSRF_FILTER CsrfFilter http/csrf
LOGOUT_FILTER LogoutFilter http/logout
X509_FILTER X509AuthenticationFilter http/x509
PRE_AUTH_FILTER AbstractPreAuthenticatedProcessingFilter Subclasses N/A
CAS_FILTER CasAuthenticationFilter N/A
FORM_LOGIN_FILTER UsernamePasswordAuthenticationFilter http/form-login
BASIC_AUTH_FILTER BasicAuthenticationFilter http/http-basic
SERVLET_API_SUPPORT_FILTER SecurityContextHolderAwareRequestFilter http/@servlet-api-provision
JAAS_API_SUPPORT_FILTER JaasApiIntegrationFilter http/@jaas-api-provision
REMEMBER_ME_FILTER RememberMeAuthenticationFilter http/remember-me
ANONYMOUS_FILTER AnonymousAuthenticationFilter http/anonymous
SESSION_MANAGEMENT_FILTER SessionManagementFilter session-management
EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter http
FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor http
SWITCH_USER_FILTER SwitchUserFilter N/A

Built-in authentication filter

  • UsernamePasswordAuthenticationFilter

If the parameters are username,password, go to usernamepasswordAuthenticationfilter, extract the parameters to construct usernamepasswordauthenticationtoken for authentication, and if successful, fill in the authentication of SecurityContextHolder.

  • BasicAuthenticationFilter

The header contains Authorization, and the value starts with Basic, then go to BasicAuthenticationFilter, extract parameters to construct usernamepasswordAuthenticationtoken for authentication, and if successful, fill in SecurityContextHolder authentication.

  • AnonymousAuthenticationFilter

For users who have not logged in, fill Authentication from AnonymousAuthenticationToken to SecurityContextHolder.

Define your own filter

You can inherit GenericFilterBean like UserNamePasswordAuthenticationFilter or AnonymousAuthenticationFilter, or OncePerRequestFilter like BasicAuthenticationFilter.
The difference between GenericFilterBean and OncePerRequestFilter can be seen in this article.Comparison of Several Interceptors in spring mvc

The main functions of the custom filter are as follows:

  • Extract authentication parameters
  • Call Authentication, success populates authentication of SecurityContextHolder, failure throws exception.

Example

public class DemoAuthFilter extends GenericFilterBean {

    private final AuthenticationManager authenticationManager;

    public DemoAuthFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

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

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

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

Set filter order

After defining the filter above, you will then place it in the 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();
    }
}

Here add him before BasicAuthenticationFilter, of course you can directly replace usernamepasswordauthenticationfilter according to the situation.

http.addFilterAt(new DemoAuthFilter(authenticationManager()),UsernamePasswordAuthenticationFilter.class);

Custom AuthenticationProvider

The AuthenticationManager interface has an implementation. ProviderManager is equivalent to a provider chain, which has a list < authenticationprovider > providers in it. authentication is implemented through providers.

public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();

        for (AuthenticationProvider provider : getProviders()) {
            if (!provider.supports(toTest)) {
                continue;
            }

            //......
            try {
                result = provider.authenticate(authentication);

                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
            catch (AccountStatusException e) {
                prepareException(e, authentication);
                // SEC-546: Avoid polling additional providers if auth failure is due to
                // invalid account status
                throw e;
            }
            catch (InternalAuthenticationServiceException e) {
                prepareException(e, authentication);
                throw e;
            }
            catch (AuthenticationException e) {
                lastException = e;
            }
        }

        //......
    }

AuthenticationProvider uses the supports method to identify whether it can handle this type of Authentication.
AnonymousAuthenticationFilter constructs AnonymousAuthenticationToken, which is handled by Anonymous Sauthentication Provider.

public class AnonymousAuthenticationProvider implements AuthenticationProvider,
        MessageSourceAware {
        //......
        public boolean supports(Class<?> authentication) {
            return (AnonymousAuthenticationToken.class.isAssignableFrom(authentication));
        }
}        

The usernamepasswordauthenticationfilter, which is constructed by BasicAuthenticationFilter, is a usernamepasswordauthenticationtoken that is handled by DaoAuthenticationProvider (its parent class is abstract detailesauthenticationprovider)

public abstract class AbstractUserDetailsAuthenticationProvider implements
        AuthenticationProvider, InitializingBean, MessageSourceAware {
        //......
        public boolean supports(Class<?> authentication) {
            return (UsernamePasswordAuthenticationToken.class
                .isAssignableFrom(authentication));
        }
}            

Like above, we have customized WebToken, in fact, for example:

You can implement Authentication interface or inherit AbstractAuthenticationToken.

public class WebToken extends AbstractAuthenticationToken {

    private final String token;

    public WebToken(String token) {
        super(null);
        this.token = token;
    }

    @Override
    public Object getCredentials() {
        return this.token;
    }

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

Here, customize the AuthenticationProvider that supports this type of WebToken.

The function to be implemented by AuthenticationProvider is to check whether it can log in and pass according to parameters, and throw an exception if it fails. Pass to get its GrantedAuthority and fill it in authentication.
If it inherits AbstractAuthenticationToken, its authorities attribute is filled in
The user-defined DemoAuthFilter will write authentication into the context of SecurityContextHolder after logging in successfully.
You can implement the AuthenticationProvider interface or inherit the AbstractSerialDetailsAuthenticationProvider (Pre-AuthenticationChecks and postAuthenticationChecks are integrated by default)

@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));
    }
}

Customize AccessDecisionManager

The previous filter handled the login problem, and then the question of whether the specified resources can be accessed was handled by FilterSecurityInterceptor. While FilterSecurityInterceptor uses AccessDecisionManager for authentication.

Several Implementations of AccessDecisionManager:

  • AffirmativeBased(Spring security is used by default)

As long as there is an ACCESS_GRANTED vote, it will be directly judged as PASS. If there is no pass vote and there is one or more (ACCESS_DENIED) votes, it is directly judged as no pass.

  • ConsensusBased(The minority is subordinate to the majority.)

If the number of votes passed is greater than the number of votes against, it will be judged as passed. If the number of votes passed is less than the number of votes against, it will be judged not to pass. If the number of votes passed is equal to the number of votes against, then whether to pass or not can be judged according to the configuration of AllowFeQualRankedDeviedIncisions (default is true).

  • UnanimousBased(Negative vote takes precedence)

No matter how many voters cast an ACCESS_GRANTED vote, as long as there is an ACCESS_DENIED vote, it will be judged as no pass. If there is no negative vote and there are voters who have voted for the pass, it will be judged as pass.

Example

One of its customization methods can be referred toTalk about spring security’s role hierarchy., which shows how to customize AccessDecisionVoter.

Custom securityMetadataSource

Mainly through ObjectPostProcessor to achieve custom, specific examples can be referred toSpring security dynamically configures url permissions

Customize access access control

For the control of authorizeRequests, permitAll, anonymous, authenticated, hasAuthority, hasRole, etc. can be used.

                .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")

These are expressions built in using spring security. Like hasAuthority and so on, they still use access method to implement it internally. Therefore, we can also directly use access to achieve maximum customization.

Example

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/login/**","/logout/**")
                .permitAll()
                .anyRequest().access("@authService.canAccess(request,authentication)");
    }
}

This is a bit like using the spring EL expression, and the implementation example is as follows

@Component
public class AuthService {

    public boolean canAccess(HttpServletRequest request, Authentication authentication) {
        Object principal = authentication.getPrincipal();
        if(principal == null){
            return false;
        }

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

        Set<String> roles = authentication.getAuthorities()
                .stream()
                .map(e -> e.getAuthority())
                .collect(Collectors.toSet());
        String uri = request.getRequestURI();
        //check this uri can be access by this role

        return true;

    }
}

Custom authenticationEntryPoint

For example, you want to change the basic authentication to realmName, except as specified in the spring security configuration.

security.basic.realm=myrealm

It can be the same

    httpBasic().authenticationEntryPoint(createBasicAuthEntryPoint("myrealm"))

    public static BasicAuthenticationEntryPoint createBasicAuthEntryPoint(String realmName){
        BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
        entryPoint.setRealmName(realmName);
        return entryPoint;
    }

Customize multiple WebSecurityConfigurerAdapter

Spring security uses antMatchers that do not support NOT, so multiple WebSecurityConfigurerAdapter can be customized to use order priority to achieve matching coverage. Please refer to this article for details.Multiple Entry Points in Spring Security

Summary

There are other ways to customize it, and we will make up for it when we find something later.

doc