Talk about the use of SwitchUserFilter

  Safety

Order

This article will introduce how to use SwitchUserFilter to switch accounts.

Filter order

Various filter built into spring security:

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

You can see that SwitchUserFilter is the last in the filter provided by spring security.

As mentioned earlier, FilterSecurityInterceptor is mainly used for authentication processing, while SwitchUserFilter is used for account switching. Putting it after FilterSecurityInterceptor requires authentication of the function of switching users, otherwise anyone can switch users at will, which is a security failure.

config

@EnableWebSecurity
@EnableGlobalMethodSecurity(
        securedEnabled = true,
        jsr250Enabled = true,
        prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public SwitchUserFilter switchUserFilter(UserDetailsService userDetailsService) throws Exception {
        SwitchUserFilter switchUserFilter = new SwitchUserFilter();
        switchUserFilter.setUserDetailsService(userDetailsService);
        switchUserFilter.setTargetUrl("/session");
        return switchUserFilter;
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //Each <http> namespace block always creates an SecurityContextPersistenceFilter, an ExceptionTranslationFilter and a FilterSecurityInterceptor. These are fixed and cannot be replaced with alternatives.
        http
                .addFilterAfter(switchUserFilter(userDetailsService()),FilterSecurityInterceptor.class)
                .exceptionHandling().authenticationEntryPoint(new UnauthorizedEntryPoint())
                .and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/login","/css/**", "/js/**","/fonts/**").permitAll()
                .antMatchers("/session").authenticated()
                .antMatchers("/login/impersonate").hasAuthority("ROLE_ADMIN")
                .antMatchers("/logout/impersonate").hasAuthority(SwitchUserFilter.ROLE_PREVIOUS_ADMINISTRATOR)
                .and()
                .formLogin()
                .permitAll()
                .and()
                .logout()
                .deleteCookies("JSESSIONID")
                .permitAll();
    }

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

SwitchUserFilter’s default url for switching accounts is /login/impersonate, the default url for canceling switching accounts is/login/impersonate, and the default account parameter is username

Use

In order to facilitate verification, the above configuration sets the targetUrl of the user after switching to /session with the following code

@RestController
@RequestMapping("/session")
public class SessionController {

    @GetMapping("")
    public Object getCurrentUser(){
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

First of all, use ordinary users to log in and accesshttp://localhost:8080/login/impersonate? Username=admin, 403 found

Log off, log on using administrator, accesshttp://localhost:8080/login/impersonate? Username=demoUser1, found successful and jump to session

{
  "authorities": [
    {
      "authority": "ROLE_USER"
    },
    {
      "authority": "read_x"
    },
    {
      "source": {
        "authorities": [
          {
            "authority": "ROLE_ADMIN"
          }
        ],
        "details": {
          "remoteAddress": "0:0:0:0:0:0:0:1",
          "sessionId": null
        },
        "authenticated": true,
        "principal": {
          "password": null,
          "username": "admin",
          "authorities": [
            {
              "authority": "ROLE_ADMIN"
            }
          ],
          "accountNonExpired": true,
          "accountNonLocked": true,
          "credentialsNonExpired": true,
          "enabled": true
        },
        "credentials": null,
        "name": "admin"
      },
      "authority": "ROLE_PREVIOUS_ADMINISTRATOR"
    }
  ],
  "details": {
    "remoteAddress": "0:0:0:0:0:0:0:1",
    "sessionId": "1BF3D6F40A6F488EFD3ABE8F80E52872"
  },
  "authenticated": true,
  "principal": {
    "password": "123456",
    "username": "demoUser1",
    "authorities": [
      {
        "authority": "ROLE_USER"
      },
      {
        "authority": "read_x"
      }
    ],
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "enabled": true
  },
  "credentials": "123456",
  "name": "demoUser1"
}

A successful handover can be found

Then switch back
http://localhost:8080/logout/impersonate? username=demoUser1

{
  "authorities": [
    {
      "authority": "ROLE_ADMIN"
    }
  ],
  "details": {
    "remoteAddress": "0:0:0:0:0:0:0:1",
    "sessionId": null
  },
  "authenticated": true,
  "principal": {
    "password": null,
    "username": "admin",
    "authorities": [
      {
        "authority": "ROLE_ADMIN"
      }
    ],
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "enabled": true
  },
  "credentials": null,
  "name": "admin"
}

It can be found that the switch is back, is it amazing and too powerful, and it is very convenient and neat to check problems online in the future

  • Abnormal situation

If you switch users that do not exist, report

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Sat Dec 16 14:36:28 CST 2017
There was an unexpected error (type=Unauthorized, status=401).
Authentication Failed: demoUser2

Summary

SwitchUserFilter is a powerful filter, which is very convenient for debugging and testing in the testing environment, and can even be used to troubleshoot online problems.