使用 oAuth 和基于表单的身份验证配置 spring 安全性
Configuring spring security using oAuth and form based authentication
我有一个包含两个组件的服务器应用程序:
a) 一组使用 oAuth 保护的 REST API(Spring security oAuth)
b) 基于角色的管理仪表板 UI
出于业务原因,这两个组件需要共同托管,即作为单个组件部署 war。
到目前为止,我们只有 REST API 的 oAuth,一切都很好。
当我们尝试对仪表板使用基于表单的身份验证时,问题就开始了。现在,当我们在没有 oAuth 令牌的情况下访问 REST API 时,它只是重定向到登录页面,而不是给出 401 未经授权的错误。
我们的配置是这样的:
基于表单的身份验证(WebSecurityConfigurerAdapter):
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**","/img/**","/login/**","/oauth/**).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.defaultSuccessUrl("/delegate/success", true)
.failureUrl("/login/fail")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll();
oAuth:
资源提供者配置:
http
.authorizeRequests()
.antMatchers("/abc").access("#oauth2.hasScope('read')and hasRole('ROLE_USER')")
.antMatchers("/xyz").access("#oauth2.hasScope('read') and hasRole('ROLE_ADMIN')")
我们基本上希望配置不同的 API 以进行不同的保护。客户端应用程序使用的 REST API 必须由 oAuth 和 Spring MVC API 使用基于表单的身份验证呈现仪表板页面来保护。
这可能吗?
编辑:
添加排序并能够在访问 oauth 保护的 REST APIs 时获得 401 未经授权的消息。表单登录虽然不起作用。我无需登录即可访问所有仪表板页面。
更多代码片段:
@Configuration
@EnableResourceServer
public class ResourceProviderConfiguration extends ResourceServerConfigurerAdapter {
.....
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/abc").access("#oauth2.hasScope('read')and hasRole('ROLE_USER')")
.antMatchers("/xyz").access("#oauth2.hasScope('read') and hasRole('ROLE_ADMIN')").
.and()
.requestMatchers()
.antMatchers("/abc","/xyz","/others");
}
}
@Configuration
@EnableAuthorizationServer
public class AuthorizationProviderConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Autowired
private UserApprovalHandler userApprovalHandler;
@Autowired
ClientDetailsService webClientDetailsService;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
.......
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
......
}
@Configuration
@EnableWebSecurity
@Order(5)
public class UserAuthenticationConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**","/img/**","/login/**",
"/oauth/**).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.defaultSuccessUrl("/delegate/success", true)
.failureUrl("/login/fail")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll();
}
}
Spring 安全日志:
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/oauth/token']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/token'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/oauth/token_key']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/token_key'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/oauth/check_token']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/check_token'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - No matches found
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/abc/**']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/abc/**'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/xyz/**']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/xyz/**'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - No matches found
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - HttpSession returned null object for SPRING_SECURITY_CONTEXT
DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@7f8059. A new one will be created.
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@cebda04
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/logout'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 5 of 11 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request 'GET '/def'' doesn't match 'POST /login/new
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 6 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 7 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@905571d8: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 127.0.0.1; SessionId: 794828541EF505314237BBC81C2ACAF4; Granted Authorities: ROLE_ANONYMOUS'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/css/**'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/**'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/img/**'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/login/**'
DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Public object - authentication not attempted
DEBUG o.s.security.web.FilterChainProxy - '/def' reached end of additional filter chain; proceeding with original chain
DEBUG o.s.web.servlet.DispatcherServlet - DispatcherServlet with name 'webservice' processing GET request for ['/handler/def']
有效的最终配置:
1.在默认为3的资源提供者配置之后,将@Order添加到网络安全配置器。
确保 WebSecurityConfigurerAdapter 具有 .anyRequest().authenticated() 配置
@Configuration
@EnableResourceServer
public class ResourceProviderConfiguration extends ResourceServerConfigurerAdapter {
.....
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/abc").access("#oauth2.hasScope('read')and hasRole('ROLE_USER')")
.antMatchers("/xyz").access("#oauth2.hasScope('read') and hasRole('ROLE_ADMIN')").
.and()
.requestMatchers()
.antMatchers("/abc","/xyz","/others");
}
}
@Configuration
@EnableAuthorizationServer
public class AuthorizationProviderConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Autowired
private UserApprovalHandler userApprovalHandler;
@Autowired
ClientDetailsService webClientDetailsService;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
.......
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
......
}
@Configuration
@EnableWebSecurity
@Order(5)
public class UserAuthenticationConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**","/img/**","/login/**",
"/oauth/**).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.defaultSuccessUrl("/delegate/success", true)
.failureUrl("/login/fail")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll();
}
}
我可以看到它正在检查日志中的 permitAll()
匹配器,但没有 anyRequest().authenticated()
的迹象。您还需要向 HttpSecurity
添加一个请求匹配器(即 http.requestMatchers().anyRequest()
)。
我有一个包含两个组件的服务器应用程序: a) 一组使用 oAuth 保护的 REST API(Spring security oAuth) b) 基于角色的管理仪表板 UI
出于业务原因,这两个组件需要共同托管,即作为单个组件部署 war。 到目前为止,我们只有 REST API 的 oAuth,一切都很好。 当我们尝试对仪表板使用基于表单的身份验证时,问题就开始了。现在,当我们在没有 oAuth 令牌的情况下访问 REST API 时,它只是重定向到登录页面,而不是给出 401 未经授权的错误。 我们的配置是这样的: 基于表单的身份验证(WebSecurityConfigurerAdapter):
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**","/img/**","/login/**","/oauth/**).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.defaultSuccessUrl("/delegate/success", true)
.failureUrl("/login/fail")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll();
oAuth:
资源提供者配置:
http
.authorizeRequests()
.antMatchers("/abc").access("#oauth2.hasScope('read')and hasRole('ROLE_USER')")
.antMatchers("/xyz").access("#oauth2.hasScope('read') and hasRole('ROLE_ADMIN')")
我们基本上希望配置不同的 API 以进行不同的保护。客户端应用程序使用的 REST API 必须由 oAuth 和 Spring MVC API 使用基于表单的身份验证呈现仪表板页面来保护。 这可能吗?
编辑: 添加排序并能够在访问 oauth 保护的 REST APIs 时获得 401 未经授权的消息。表单登录虽然不起作用。我无需登录即可访问所有仪表板页面。 更多代码片段:
@Configuration
@EnableResourceServer
public class ResourceProviderConfiguration extends ResourceServerConfigurerAdapter {
.....
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/abc").access("#oauth2.hasScope('read')and hasRole('ROLE_USER')")
.antMatchers("/xyz").access("#oauth2.hasScope('read') and hasRole('ROLE_ADMIN')").
.and()
.requestMatchers()
.antMatchers("/abc","/xyz","/others");
}
}
@Configuration
@EnableAuthorizationServer
public class AuthorizationProviderConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Autowired
private UserApprovalHandler userApprovalHandler;
@Autowired
ClientDetailsService webClientDetailsService;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
.......
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
......
}
@Configuration
@EnableWebSecurity
@Order(5)
public class UserAuthenticationConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/css/**","/img/**","/login/**",
"/oauth/**).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.defaultSuccessUrl("/delegate/success", true)
.failureUrl("/login/fail")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll();
}
}
Spring 安全日志:
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/oauth/token']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/token'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/oauth/token_key']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/token_key'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/oauth/check_token']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/check_token'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - No matches found
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/abc/**']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/abc/**'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - Trying to match using Ant [pattern='/xyz/**']
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/xyz/**'
DEBUG o.s.s.w.u.matcher.OrRequestMatcher - No matches found
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - HttpSession returned null object for SPRING_SECURITY_CONTEXT
DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@7f8059. A new one will be created.
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
DEBUG o.s.s.w.h.writers.HstsHeaderWriter - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@cebda04
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/logout'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 5 of 11 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request 'GET '/def'' doesn't match 'POST /login/new
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 6 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 7 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 8 of 11 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@905571d8: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 127.0.0.1; SessionId: 794828541EF505314237BBC81C2ACAF4; Granted Authorities: ROLE_ANONYMOUS'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
DEBUG o.s.security.web.FilterChainProxy - '/def' at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/css/**'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/oauth/**'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/img/**'
DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Checking match of request : '/def'; against '/login/**'
DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Public object - authentication not attempted
DEBUG o.s.security.web.FilterChainProxy - '/def' reached end of additional filter chain; proceeding with original chain
DEBUG o.s.web.servlet.DispatcherServlet - DispatcherServlet with name 'webservice' processing GET request for ['/handler/def']
有效的最终配置: 1.在默认为3的资源提供者配置之后,将@Order添加到网络安全配置器。
确保 WebSecurityConfigurerAdapter 具有 .anyRequest().authenticated() 配置
@Configuration @EnableResourceServer public class ResourceProviderConfiguration extends ResourceServerConfigurerAdapter { ..... @Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/abc").access("#oauth2.hasScope('read')and hasRole('ROLE_USER')") .antMatchers("/xyz").access("#oauth2.hasScope('read') and hasRole('ROLE_ADMIN')"). .and() .requestMatchers() .antMatchers("/abc","/xyz","/others"); } } @Configuration @EnableAuthorizationServer public class AuthorizationProviderConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired private TokenStore tokenStore; @Autowired private UserApprovalHandler userApprovalHandler; @Autowired ClientDetailsService webClientDetailsService; @Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager; ....... @Bean public TokenStore tokenStore() { return new InMemoryTokenStore(); } ...... } @Configuration @EnableWebSecurity @Order(5) public class UserAuthenticationConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/css/**","/img/**","/login/**", "/oauth/**).permitAll() .anyRequest().authenticated() .and() .formLogin().loginPage("/login") .defaultSuccessUrl("/delegate/success", true) .failureUrl("/login/fail") .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login") .permitAll(); } }
我可以看到它正在检查日志中的 permitAll()
匹配器,但没有 anyRequest().authenticated()
的迹象。您还需要向 HttpSecurity
添加一个请求匹配器(即 http.requestMatchers().anyRequest()
)。