序
本文就来介绍一下如何使用SwitchUserFilter进行账户切换
filter顺序
spring security内置的各种filter:
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 |
可以看到SwitchUserFilter是spring security提供的filter里头order顺序在最后的一个。
前面讲到了FilterSecurityInterceptor主要用来进行鉴权处理,而SwitchUserFilter是用来做账户切换的,把它放在FilterSecurityInterceptor之后,是要求对切换用户的功能进行鉴权,否则任何人都可以随意切换用户,那就安全故障了。
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默认的切换账号的url为/login/impersonate,默认注销切换账号的url为/logout/impersonate,默认的账号参数为username
使用
上面的配置为了方便验证,把切换完用户的targetUrl设置为/session,其代码如下
@RestController
@RequestMapping("/session")
public class SessionController {
@GetMapping("")
public Object getCurrentUser(){
return SecurityContextHolder.getContext().getAuthentication();
}
}
首先用普通用户登录,访问http://localhost:8080/login/impersonate?username=admin,发现返回403
注销,使用管理员登录,访问http://localhost:8080/login/impersonate?username=demoUser1,发现成功并跳转到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"
}
可以发现有成功切换
之后再切换回来
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"
}
可以发现切换回来了,是不是非常神奇,太强大了,以后线上排查问题之类的,非常方便,爽歪歪了简直
- 异常情况
如果你切换了不存在的用户,则报
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
小结
SwitchUserFilter是个强大的filter,非常方便测试环境进行调试、测试,甚至可以用来进行上线问题排查。