Add filter before OAuth2AuthenticationProcessingFilter - spring-security

I am using spring-security-oauth2 in IDP mode and spring-boot. I need to do some work before the oauth token is extracted from the request. How do I add a filter before OAuth2AuthenticationProcessingFilter?
I have tried:
#Configuration
#EnableResourceServer
public class OAuth2ResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and()
.addFilterBefore(new MyFilter(), OAuth2AuthenticationProcessingFilter.class);
}
}
But I get the following exception:
java.lang.IllegalArgumentException: Cannot register after unregistered Filter class org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter
I guess this might be because #EnableResourceServer is executed after configure(HttpSecurity http).

I achieved desired functional by doing this
.addFilterBefore(new MyTokenFilter(), AbstractPreAuthenticatedProcessingFilter.class)

The following worked for me
#Override
public void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(new MyFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.authorizeRequests().anyRequest().fullyAuthenticated()
;
}
Result
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
MyFilter
OAuth2AuthenticationProcessingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]

Related

Configuring Multiple Spring Security [duplicate]

This question already has answers here:
Spring Security : Multiple HTTP Config not working
(2 answers)
Closed 1 year ago.
I have the following configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class SamlConfig extends WebSecurityConfigurerAdapter {
#Value("${enable_csrf}")
private Boolean enableCsrf;
#Autowired
private SamlUserService samlUserService;
public SamlWebSecurityConfig() {
super();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/sso").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest().authenticated()
.and()
.apply(saml())
.userDetailsService(samlUserService)
.serviceProvider()
.keyStore()
.storeFilePath("path")
.password("password")
.keyname("alias")
.keyPassword("password")
.and()
.protocol("https")
.hostname(String.format("%s:%s","localhost", "8080"))
.basePath("/")
.and()
.identityProvider()
.metadataFilePath("metadata");
if (!enableCsrf) {
http.csrf().disable();
}
}
}
#Configuration
#Order(2)
public static class BasicConfig extends WebSecurityConfigurerAdapter {
public BasicWebSecurityConfig() {
super();
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.anyRequest().authenticated();
if (!enableCsrf) {
http.csrf().disable();
}
}
}
This works for the SAML, but the basic login returns an error: 403 forbidden.
I modified the BasicConfig with this, and SAML doesn't work anymore but basic authentication works. All the endpoints are for both SAML and basic authentication, just different login page.
public static class BasicConfig extends WebSecurityConfigurerAdapter {
public BasicWebSecurityConfig() {
super();
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
if (!enableCsrf) {
http.csrf().disable();
}
}
}
For some reasons sometimes it works, sometimes not. I also tried to modify the #Order and still not working.
In Spring Security, there are two things that are alike but do things completely differently, requestMatchers().antMatchers() and authorizeRequests().antMatchers().
The requestMatchers tells HttpSecurity to only invoke the SecurityFilterChain if the provided RequestMatcher was matched.
The authorizeRequests allows restricting access based upon the HttpServletRequest using RequestMatcher implementations.
In your case, you have two SecurityFilterChains. But only the one with the highest priority is being invoked, this happens because you did not give any requestMatchers to it, therefore it will match every request. And only one SecurityFilterChain is called per request, thus it will not invoke the next one.
So, you should inform the requestMatchers for your configurations, like so:
http
.requestMatchers((requests) -> requests
.antMatchers("/secure/sso", "/saml/**")
)
.authorizeRequests()
.antMatchers("/secure/sso").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest().authenticated()
...
http
.requestMatchers((requests) -> requests
.antMatchers("/secure/basic", "/**")
)
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.anyRequest().authenticated();

Which one is the correct OAuth2 flow

Tried to implement an OAuth2 in spring. But stuck as to which one would be the correct flow?
One flow I keep #Order(1) in (WebSecurityConfigurerAdapter)
Here on hitting the below I am presented with the default login page and I successfully login. http://localhost:8301/oauth/authorize?client_id=getidfromfacebook&response_type=code&redirect_uri=http://localhost:9191/xyz
Redirected to the authorize page and after acceptance get a code http://localhost:9191/xyz?code=mkuyG4 which helps in getting the access and refresh token by curl http://localhost:8301/oauth/token -H"Content-type: application/x-www-form-urlencoded" -d'grant_type=authorization_code&redirect_uri=http://localhost:9191/xyz&code=LJQef7' -u getidfromfacebook:getit
I am also able to get a fresh access token from the given refresh token via curl --location --request POST 'http://localhost:8301/oauth/token?grant_type=refresh_token&client_id=getidfromfacebook&refresh_token=a045acd6-5d66-4db5-a509-4bdadca065e0' -u getidfromfacebook:getit
The problem I face here is that with the given access token, I am not able to access any of the resources mentioned in
antMatchers("/api/**").authenticated() (ResourceServerConfigurerAdapter).
Like in postman provided a Header with Authorization and value Bearer access-token or like curl -H"Authorization: Bearer 1738520f-9f9c-43ef-8f7f-f5886075a7aa" http://localhost:8301/api/users/all/.
Note, I am able to get access-tokens for other grant_types as well and also refresh it. But no access to resources via the token. Point to note is if I hit the resource url, I am presented with the default login and able to access it.
The other flow I remove #Order(1). When I try to go through the authorization code flow, the system complains about user needs to be logged in for a request of (auth)code. So not able to proceed as am not presented with the default login page.
However, I am able to proceed with the password grant type curl http://localhost:8301/oauth/token -d"grant_type=password&username=username&password=userpassword" -H"Content-type:application/x-www-form-urlencoded; charset=utf-8" -u getidfromfacebook:getit
I am also able to access the resources via the access token.
Which one is the correct approach?
Why am I not able to access the resources in former approach.
#Configuration
#EnableAuthorizationServer
#AllArgsConstructor
public class AuthorizationServerConfigAdapter extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final ClientService clientService;
private final UserService userService;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientService);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(this.authenticationManager)
.userDetailsService(userService)
;
}
/*****************************/
#Configuration
#EnableResourceServer
public class ResourceServerConfigAdapter extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.antMatchers("/").permitAll();
}
}
/*****************************/
#Configuration
#EnableWebSecurity
#AllArgsConstructor
#Order(1) // Since we have this working as N, Z and R sever.
public class WebSecurityConfigAdapter extends WebSecurityConfigurerAdapter {
private final UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
//http.csrf().disable();
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/oauth/authorize**", "/login**", "/error**")
.permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userService)
.passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2A);
}
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(request -> {
String auth = request.getHeader("Authorization");
return (auth != null && auth.startsWith("Bearer"));
})
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.antMatchers("/").permitAll();
}

Decouple Authorisation server and resource server with RemoteTokenServices

I am trying spring security and spring oauth2 in my project and have separated my authorization server and resource server. I didn't want to share a token store between these two servers so I decided to use RemoteTokenServices and the check_token endpoint. Everything was fine except when I used an access token to query the resource server, I got "401 Unauthorized" error as follows:
2015-10-19 11:50:10.291 DEBUG 2590 --- [nio-8080-exec-1] o.s.web.client.RestTemplate : POST request for "http://localhost:9080/uaa/oauth/check_token/" resulted in 401 (Unauthorized); invoking error handler
2015-10-19 11:50:10.293 DEBUG 2590 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2015-10-19 11:50:10.293 DEBUG 2590 --- [nio-8080-exec-1] o.s.web.filter.RequestContextFilter : Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#41f4867a
2015-10-19 11:50:10.297 ERROR 2590 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[jerseyServlet] : Servlet.service() for servlet [jerseyServlet] in context with path [] threw exception
org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
The code for the Authorisation server:
#Configuration
#EnableAuthorizationServer
public class OAuthConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
protected AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
#Bean
public DefaultAccessTokenConverter defaultAccessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(this.tokenStore())
.authenticationManager(authenticationManager)
.accessTokenConverter(defaultAccessTokenConverter());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
And the security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication();
// .withUser("John").roles("ADMIN").password("password")
// .and()
// .withUser("Mary").roles("BASIC").password("password");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").authenticated()
.and().httpBasic().realmName("OAuth Server");
http.csrf().disable();
}
}
The Resource Server is set up as follows:
#Configuration
#EnableResourceServer
public class ResourceConfiguration extends ResourceServerConfigurerAdapter {
private static String RESOURCE_ID = "xn-resource-id";
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated();
}
#Bean
public AccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
#Bean
public RemoteTokenServices remoteTokenServices(final #Value("${auth.server.url}") String checkTokenUrl,
final #Value("${auth.server.client_id}") String clientId,
final #Value("${auth.server.client_secret}") String clientSecret) {
final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl);
remoteTokenServices.setClientId(clientId);
remoteTokenServices.setClientSecret(clientSecret);
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
return remoteTokenServices;
}
}
I tested the security settings with curl and used client_credentials grant type.
Does anyone help me figure out what's the issue with the above code?
Looks like you are using incorrect url. Try to repleace it with:
http://localhost:9080/uaa/oauth/check_token
(notice that url is not ended with /)

Can Spring Boot application have separate security for REST APIs?

We would like to apply Oauth2 based security for the Rest Controllers while the rest of the application will have Spring Security. Will that be possible? Can you provide any examples please?
It seems like WebSecurityConfigurerAdapter and ResourceServerConfigurerAdapter conflicting when both configured.
Thank you in advance.
Yes it's possible. Here the example template configuration code is given. Please change the required configs to your need. The key is to define Sub static classes of configuration with different order. Here i have considered any requests which is orginating from \api as a REST API call.
I have not checked the code by compiling it.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Order(1)
#Configuration
public static class ApiWebSecurityConfig extends OAuth2ServerConfigurerAdapter{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//Write the AuthenticationManagerBuilder codes for the OAuth
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.apply(new OAuth2ServerConfigurer())
.tokenStore(new InMemoryTokenStore())
.resourceId(applicationName);
}
}
}
#Order(2)
#Configuration
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//Write the AuthenticationManagerBuilder codes for the Normal authentication
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //HTTP with Disable CSRF
.authorizeRequests() //Authorize Request Configuration
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login").permitAll()
.and() //Logout Form configuration
.logout().permitAll();
}
}
}

spring security mapping for wildcards

Using Spring-Boot 1.1.17, Spring-MVC with Spring-Security:
I have several subdomains that I want to to allow unauthenticated users (Visitors) access to. For example:
mysite.com/customerA
mysite.com/customerB
If a invalid customer site is attempted, then my controller would either throw an exception or redirect back to / (mysite.com/) Naturally other parts of the domain (mysite.com/customerA/myaccount) will require login.
I haven't really figured out how to do this with spring security and spring-mvc. Here is what I am attempting so far:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new CSRFTokenGeneratorFilter(), CsrfFilter.class)
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers( "/**/" ).permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/wizard").permitAll()
.antMatchers("/menu").permitAll()
.antMatchers("/error").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.antMatchers("/libs/**").permitAll();
http
.formLogin()
.loginPage("/loginPage")
.permitAll()
.loginProcessingUrl("/login")
.failureUrl("/login?error")
.defaultSuccessUrl("/?tab=success")
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
.permitAll()
.and()
.csrf();
http
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired")
.maxSessionsPreventsLogin(true)
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/");
http
.authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService( customUserDetailsService ).passwordEncoder( encoder );
}
#Override
public void configure(WebSecurity security){
security.ignoring().antMatchers("/css/**","/fonts/**","/libs/**");
}
}
And my homepage controller:
#Controller
#RequestMapping("/{officeName}/")
public class HomeController {
private AuthenticatedUser getVisitor(#PathVariable String officeName) {
.. do something with the office if found, redirect otherwise
if (!StringUtils.isEmpty(officeName)) {
Office office = officeService.findByName( officeName );
return office.getUrl();
}
return "/";
}
When I try to access that url, I get the following errors:
o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/customerA/]
s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /customerA/
s.w.s.m.m.a.RequestMappingHandlerMapping : Did not find handler method for [/customerA/]
o.s.w.s.handler.SimpleUrlHandlerMapping : Matching patterns for request [/customerA/] are [/**]
o.s.w.s.handler.SimpleUrlHandlerMapping : URI Template variables for request [/customerA/] are {}
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapping [/customerA/] to HandlerExecutionChain with handler [org.springframework.web.servlet.resource.ResourceHttpRequestHandler#2f295527] and 1 interceptor
o.s.web.servlet.DispatcherServlet : Last-Modified value for [/customerA/] is: -1
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: ServletContext resource [/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [META-INF/resources/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [resources/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [static/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [public/]
o.s.w.s.r.ResourceHttpRequestHandler : No matching resource found - returning 404
I tried adding this ServletRegistrationBean:
#Bean
public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean( dispatcherServlet );
registration.addUrlMappings("/", "/testCustomer/*" );
for ( Office office : officeService.findAllActiveOffices() ) {
registration.addUrlMappings( office.getUrl() + "/*" );
}
return registration;
}
But this would seem to only work if the application knows of the customer at startup, not dynamically in the case of customer signup.
Is there a way to configure this to handle these types of wildcards?
You can try with a configuration like the following:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService _userService;
#Autowired
private PasswordEncoder _passwordEncoder;
/**
* Defines the password encoder used by Spring security during the
* authentication procedure.
*/
#Bean
public PasswordEncoder passwordEncoder() {
// default strength = 10
return new BCryptPasswordEncoder();
}
/**
* Sets security configurations for the authentication manager
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth
.userDetailsService(_userService)
.passwordEncoder(_passwordEncoder);
return;
}
/**
* Configures where Spring Security will be disabled (security = none).
* From spring reference: "Typically the requests that are registered [here]
* should be that of only static resources. For requests that are dynamic,
* consider mapping the request to allow all users instead."
*/
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/css/**",
"/js/**",
"/fonts/**",
"/resources/**",
"/libs/**");
return;
}
/**
* Sets security configurations in the HttpSecurity object.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
// Set security configurations
http
.authorizeRequests()
// the following urls are allowed for any user (no authentication)
.antMatchers(
"/",
"/login",
"/menu")
.permitAll()
// any other url must be authenticated
.anyRequest().authenticated()
.and()
// define the login page url
.formLogin()
.loginPage("/login")
.permitAll()
.and()
// define the logout url
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
return;
} // method configure
} // class WebSecurityConfig
Adding your personal configurations... You can try to add the following controller:
#Controller
public class HomeController {
#RequestMapping("/{officeName}/")
public AuthenticatedUser getVisitor(#PathVariable String officeName) {
// .. do something with the office if found, redirect otherwise
if (!StringUtils.isEmpty(officeName)) {
Office office = officeService.findByName( officeName );
return office.getUrl();
}
return "/";
}
}
If the user is correctly authenticated he should access the url at the officeName.

Resources