Spring security client detail is going as user detail in DaoAuthenticationProvider - spring

Spring Boot, Security OAuth2 implementation, default token endpoint (/oauht/token) is working fine. However, when I send a request to new endpoint at /oauth/http/token it is throws Bad Credentials because of the following reason:
FilterChainProxy triggers around 12 filters and out of which one is BasicAuthenticationFilter. It uses UserDetailsService of DaoAuthenticationProvider class to fetch user data. For client authentication this should be ClientDetailsService but, for some reason this is always UserDetailsService and because of this client credentials goes to UserRepository and fails. This class does initialize properly because default /oauth/token works fine.
I tried to inject existing Authentication Manager in BasicAuthenticationFilter and added that as a filter in ResourceServerConfigurerAdapter but that didn’t make any difference. It does change Authentication Manager provider from AnonymousAuthenticationProvider to DaoAuthenticationProvider but UserDetailsService still remains UserDetails.
Request at /oauth/http/token, this doesn't work. Code is almost same as postAccessToken() of org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
In above screenshot we can see that userDetailsService is UserDetailsServiceImpl and due to this client details present in request header as Basic dGVzdDpwYXNzd29yZA== is going to user repository and checking at user table instead of going to client repository and checking at client table.
Request at /oauth/token, this works

FilterChainProxy maintains not a single filter chain but a list of SecurityFilterChain-s.Each security filter chain contains a request matcher and a list of filters. So you will have several instances of BasicAuthenticationFilter in these different chains.
Which filter chain will be triggered depends on the incoming request and the decisions of the request matchers.
/oauth/token triggers the chain which is created by spring oauth and uses ClientDetailsService at the end.
/oauth/http/token triggers another chain created by your web security configuration and uses user details service.
So... that is the reason. To see how the chains are created on startup you may enable the security debug, e.g. in application.yml
logging:
level:
org.springframework.security: DEBUG
Then you will see the oauth security chain creation:
Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/oauth/token'], Ant [pattern='/oauth/token_key'], Ant [pattern='/oauth/check_token']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#4ce4e309, org.springframework.security.web.context.SecurityContextPersistenceFilter#16331720, org.springframework.security.web.header.HeaderWriterFilter#60ef29a5, org.springframework.security.web.authentication.logout.LogoutFilter#4c9638cc, org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter#9eefda5, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#16d090e9, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#484a9950, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#1c4fefe8, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#12082780, org.springframework.security.web.session.SessionManagementFilter#20a49b7b, org.springframework.security.web.access.ExceptionTranslationFilter#24313d10, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#47ce08d2]
Note the request matchers.
UPDATE: If you want to 'remap' the endpoint to your own endpoint you may reconfigure that.
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.pathMapping("/oauth/token", "/oauth/http/token");
}

Related

Spring-security 6 - 403 denied because AuthenticationProvider not called

I've recently upgraded a project from using spring-security 6.0.0-M6 to 6.0.0, gradle config if you want to see it.
This project does not use spring-boot.
Context
My securityFilterChain is configured via code and looks approximately like this:
http.
authenticationManager(authnManager).
securityContext().securityContextRepository(securityRepo).
and().
authorizeRequests(). // <-- DEPRECATED
requestMatchers(RAID_V2_API + "/**").fullyAuthenticated().
The full codebase, starting with the FilterChain config, is publicly available.
Note that usage of WebSecurityConfigurerAdapter is deprecated, and I have not been using it since the original usage of 6.0.0-M6. So calling stuff like WebSecurityConfigurerAdapter.authenticationManagerBean() won't work.
This code works fine, but the call to authorizeRequests() causes a deprecation warning that I want to get rid of.
Problem
The deprecation tag says that I should use authorizeHttpRequests() instead, but when I do that - requests that require authorization (via the fullyAuthenticated() specification above) will be denied with a 403 error.
Analysis
It seems this happens because my AuthenticationProvider instances aren't being called,
because the ProviderManager isn't being called. Since the AuthnProviders don't get called, the security context still contains the pre-auth token instead of a verified post-auth token, so the eventual call to AuthorizationStrategy.isGranted() ends up calling isAuthenticated() on the pre-auth token, which (correctly) returns false and the request is denied.
Question
How do I use the authorizeHttpRequests() method but still have the ProviderManager be called so that my security config works?
My workaround is just to ignore the deprecation warning.
First, your security configuration does not specify any kind of authentication, like httpBasic, formLogin, etc. The AuthenticationManager is invoked by the filters created by those authentication mechanisms in order to authenticate credentials.
Second, the application is probably unwittingly relying on FilterSecurityInterceptor (authorizeRequests) to authenticate the user, which is not supported with authorizeHttpRequests. You need to declare an auth mechanism that collects credentials from the request and authenticates the user.
Because you are using JWT, you might want to consider Spring Security's OAuth2 Resource Server support. You can also refer to our samples repository in order to help you with sample configurations.
Here's a rough outline of what I did to to implement the "just use the resource server" suggestion from the answer.
include the oauth2-resource-server libraries in the build.
create an AuthenticationManagerResolver that replaces what the SecuritycontextRepository and the FilterSecurityInterceptor used to do:
#Bean
public AuthenticationManagerResolver<HttpServletRequest>
tokenAuthenticationManagerResolver(
AuthenticationProvider authProvider
) {
return (request)-> {
return authProvider::authenticate;
};
}
change AuthenticationProvider implementations to use the BearerTokenAuthenticationToken class as the pre-auth token, it still works basically the same way it used to: verifying the pre-auth token and returning a post-auth token.
hook up the new resolver class in the securityFilterChain config by replacing the old securityContextRepository() config with the new authenticationManagerResolver() config, which passes in the resolver created in step 2:
http.oauth2ResourceServer(oauth2 ->
oauth2.authenticationManagerResolver(tokenAuthenticationManagerResolver) );
I like this new approach because it makes it more obvious how the security chain works.
It's nice to replace the custom pre-auth token implementation with the built-in class too.
Note that it's likely this config can be simplified, but I needed the custom resolver since the project uses different types of bearer token depending on the endpoint called. Pretty sure the auth providers don't need to be AuthenticationProvider any more; the lambda function returned from the resolver serves that purpose - they can probably just be random spring components and as long as the method is SAM-type compatible.
The spring-security multi-tenancy doco was helpful for this.

Default Login Form for Secured Methodes in Spring Security

Maybe I am thinking completely the wrong way. I want to use Spring Security for my web application and only secure some functions of my Controller class without specifying the URLs.
When I include the dependency spring-boot-starter-security everything is secured by default with a side default login form. So far so good.
Now I activate method security with #EnableGlobalMethodSecurity(securedEnabled = true) and mark some of my methods with #Secured("USER").
To have no security as default I define a custom SecurityFilterChain:
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests((requests) -> requests.anyRequest().permitAll())
.build();
}
Of course I have a test user defined:
#Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("test")
.password("test")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
I get a 403 Forbidden on the secured methods. And a 404 Not Found when I add this to the SecurityFilterChain:
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
Because the default login form is missing.
What do I need to do to get the default login form for the secured methods?
I want to use Spring Security for my web application and only secure some functions of my Controller class without specifying the URLs.
Spring Security's web support is URL-based, so this does seem to contradict it's purpose. You can use only method-based security, but all of the behavior you're missing is expected because the filter chain is not in play. See Spring Security filter chain and Method Security.
To have no security as default I define a custom SecurityFilterChain:
This would not be a best practice. Consider what happens when a developer forgets to add the #Secured or similar annotation. Defense in depth would be a better approach, which is why Spring Security requires every endpoint to be authenticated by default.
I get a 403 Forbidden on the secured methods. And a 404 Not Found when I add this to the SecurityFilterChain:
At the method level, the ExceptionTranslationFilter does not trigger the AuthenticationEntryPoint. This would explain why you don't get the default behavior of a redirect to /login.
When defining a custom login page with http.formLogin().loginPage("/login"), you are responsible for providing a login page. This would explain why you receive a 404 Not Found.
In order to get the default behavior, you need to specify something that causes requests to be processed appropriately by the filter chain prior to method security. For example, if all of your secured endpoints start with /secured, you should add that as an authorization rule:
http.authorizeHttpRequests((requests) -> requests
.mvcMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
);
But this still leaves a gap and does not practice defense in depth. So the best configuration would invert the rules and identify only URLs that should be allowed by default, such as static resources.
http.authorizeHttpRequests((requests) -> requests
.mvcMatchers("/static/**").permitAll()
.anyRequest().authenticated()
);
In either case, security exceptions would be thrown by the filter chain while processing requests to #Secured endpoints, and so the AuthenticationEntryPoint will be triggered. Keep in mind however that authentication and authorization are related but separate concepts. In Spring Security, it's the ExceptionTranslationFilter that is tying them together in a way that produces the expected user experience.

OAuth in Spring Security 5.2.x: Store userInfo response

We are running an API on Sping Boot 2.2 and are consequently using Sping Security 5.2. In securing this API with OAuth, we are using the new features built into Spring Security (since the Spring Security OAuth project is now deprecated). We are using opaque tokens and (as indicated by the documentation) have a security config of the following form:
#Configuration
public static class OAuthWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().mvcMatchers("/path/to/api/**").hasAuthority(CUSTOM_SCOPE)
.oauth2ResourceServer().opaqueToken().introspector(opaqueTokenIntrospector());
}
}
Here opaqueTokenIntrospector() is a bean which performs the following tasks:
Send a request to the introspection endpoint to get the full token.
Also send a request to the userinfo endpoint to get additional info about the user from the IDP.
Map some of this additional info into custom spring roles and add these roles to the authenticated user.
The way this configuration is set up, each request to the API comes with two additional requests: one to the introspection endpoint and one to the userinfo endpoint. It would be better to save on some of these if a user performs successive requests to the API.
Is it possible to save the result of the opaqueTokenIntrospector() in the session of the user? This way the whole flow of the bean need only be done once per user, saving on redundant requests.
This is a common requirement when you get beyond basic APIs. Use a claims caching solution, which is an API gateway pattern:
When a token is first received do the lookups and save them into an object:
Token claims
User info claims
App specific claims
Then cache the results against the token, so that subsequent requests with the same token are fast:
Use a thread safe cache - my preference is to use an in memory one
Use the SHA256 hash of the access token as the key
Use the serialized claims as the value
This probably goes beyond Spring's default behaviour, but you can customise it.
I have a Java sample that does this - here are a couple of links, though my sample is quite a complex one:
Custom Authorizer Class
Caching Class
Java Write Up

OAuth2 Open ID client authentication followed by LDAP user details & authorities

Our app is currently set up with OAuth OpenID connect authentication with an external (third-party) server. The requirement is to use user details service that loads the user from LDAP (along with the authorities/roles) to complete the authentication. So authentication.getPrincipal() should be returning the custom UserDetails object we use that gets generated by querying LDAP using the username obtained from the Open ID authentication.
I have tried the following:
Followed Similar Issue but it seems like in the answer it's setting up the server-side and it doesn't work either
Tried adding custom UserDetailsService in WebSecurityConfig
#Configuration
public class OAuth2Config extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login().and().userDetailsService(myCustomUserDetailsService());
}
}
where myCustomUserDetailsService() handles the call to LDAP and fetches the user details info, including the authorities.
I'm not too familiar with the spring oauth2 framework, correct me if I'm wrong: I'm guessing I need to implement my own user info endpoint to make a call to LDAP, and not the user info endpoint provided to me by the OpenID service?
You could add a new filter in the security filter chain right after that one. The second filter could retrieve the principal/name from authentication and query for users.
Like a two steps authentication
Not as clean as I hoped, but I registered an application listener on InteractiveAuthenticationSuccessEvent and manually updated the authentication after login (similar post's answer https://stackoverflow.com/a/10747856/11204609)
If anyone has better suggestions?
UPDATE: The identity provider agreed to include LDAP info in their claims so I can directly obtain via the token without having to do a call-back (still an on-going discussion at the moment). That being said, having a auth success callback app listener is only way I could solve the problem.

Use oauth2 scope instead of role to secure spring actuator management endpoints

I've upgraded to Spring Cloud Dalston recently, that means Spring Boot 1.5.1, and I can not secure the management endpoints by checking an oauth2 scope anymore. It worked before in Spring Cloud Camden.
This is the configuration that worked before :
#Configuration
public class SecurityConfiguration extends ResourceServerConfigurerAdapter {
#Value("${management.context-path}")
private String managementContextPath;
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// some paths I don't want to secure at all
.antMatchers("/path1/**", "/path2/**").permitAll()
// access to health endpoint is open to anyone
.antMatchers(HttpMethod.GET, managementContextPath + "/health").permitAll()
// but app.admin scope is necessary for other management endpoints
.antMatchers(managementContextPath + "/**").access("#oauth2.hasScope('my-super-scope')") //
// And we make sure the user is authenticated for all the other cases
.anyRequest().authenticated();
}
}
And this is the important part of the config :
security:
oauth2:
client:
clientId: a-client
clientSecret: the-client-password
resource:
tokenInfoUri: http://my-spring-oauth2-provider/oauth/check_token
management:
context-path: /my-context
security:
enabled: true
endpoints:
health:
sensitive: false
When I try to POST on /my-context/refresh I get a HTTP 401 "Full authentication is needed" even though I give a valid OAuth2 token
Looking through the log I saw that my request was considered anonymous, and checking the FilterChainProxy log saw that the OAuth2AuthenticationProcessingFilter was not called. After a bit of digging I found that I could change the oauth2 resource filter order, so I tried that and now I have an OAuth2 authentication, yeah, finished right ?
Hum, no, now I have an Access is denied. User must have one of the these roles: ACTUATOR error.
I tried a few other things, including disabling management security (but my rules are not applied and access is open to everyone), playing with (ugh) #Order (no change), and even, lo and behold, reading and applying the documentation which says :
To override the application access rules add a #Bean of type
WebSecurityConfigurerAdapter and use
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) if you don’t want to
override the actuator access rules, or
#Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER) if you do
want to override the actuator access rules.
But this did not change the error : User must have one of these roles: ACTUATOR
Does anybody have a workaround/idea ?
Update : I'm also using Zuul, so I finally created a specific zuul route to the endpoint I needed (cloud bus refresh in my case), unprotected on an other backend service that was not exposed otherwise, and protected that zuul route with oauth2.
I'm leaving this question here nevertheless, if anyone finds a workaround, could be useful.
Probably being captain obvious, but see http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-monitoring.html. You can override the role with management.security.roles and simply add whatever role your Oauth2 credentials have.
I confronted with this issue also. The workaround that I used was to expose the actuator action on a new endpoint which I defined, and just call the actuator bean to handle request.
For example to secure /my-context/refresh with Oauth2 , I just expose a new resource at {whatever-api-prefix}/refreshConfig and I exposed a request handler on the rest controller for this URL; in the rest controller I wire the RefreshEndpoint bean and in the request handler I just call the refreshEndpoint.refresh().

Resources