I\'m attempting to implement an API with resources that are protected by either Oauth2 OR Http-Basic authentication.
When I load the WebSecurityConfigurerAdapter which a
If anyone is trying to get this working with Spring WebFlux, the method which determines whether the request is handled is called "securityMatcher", rather than "requestMatcher".
i.e.
fun configureBasicAuth(http: ServerHttpSecurity): SecurityWebFilterChain {
return http
.securityMatcher(BasicAuthServerWebExchangeMatcher())
.authorizeExchange()
...
I managed to get this work based on the hints by Michael Ressler's answer but with some tweaks.
My goal was to allow both Basic Auth and Oauth on the same resource endpoints, e.g., /leafcase/123. I was trapped for quite some time due to the ordering of the filterChains (can be inspected in FilterChainProxy.filterChains); the default order is as follows:
Since resource server's filterChains ranks higher than the one by WebSecurityConfigurerAdapter configured filterchain, and the former matches practically every resource endpoint, then Oauth resource server logic always kick in for any request to resource endpoints (even if the request uses the Authorization:Basic header). The error you would get is:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
I made 2 changes to get this work:
Firstly, order the WebSecurityConfigurerAdapter higher than the resource server (order 2 is higher than order 3).
@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
Secondly, let configure(HttpSecurity) use a customer RequestMatcher that only matches "Authorization: Basic".
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.requestMatcher(new BasicRequestMatcher())
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(oAuth2AuthenticationEntryPoint())
.and()
// ... other stuff
}
...
private static class BasicRequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
return (auth != null && auth.startsWith("Basic"));
}
}
As a result it matches and handles a Basic Auth resource request before the resource server's filterChain has a chance to match it. It also ONLY handles Authorizaiton:Basic resource request, thus any requests with Authorization:Bearer will fall through, and then handled by resource server's filterChain (i.e., Oauth's filter kicks in). Also, it ranks lower than the AuthenticationServer (in case AuthenticationServer is enabled on the same project), so it doesn't prevent AuthenticaitonServer's filterchain from handling the request to /oauth/token, etc.
The solution @kca2ply provided works very well. I noticed the browser wasn't issuing a challenge so I tweaked the code a little to the following:
@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.anonymous().disable()
.requestMatcher(request -> {
String auth = request.getHeader(HttpHeaders.AUTHORIZATION);
return (auth != null && auth.startsWith("Basic"));
})
.antMatcher("/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
// @formatter:on
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
Using both requestMatcher()
and antMatcher()
made things work perfectly. Browsers and HTTP clients will now challenge for basic creds first if not provided already. If no credentials are provided, it falls through to OAuth2.
This may be close to what you were looking for:
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new OAuthRequestedMatcher())
.authorizeRequests()
.anyRequest().authenticated();
}
private static class OAuthRequestedMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
// Determine if the client request contained an OAuth Authorization
return (auth != null) && auth.startsWith("Bearer");
}
}
The only thing this doesn't provide is a way to "fall back" if the authentication isn't successful.
To me, this approach makes sense. If a User is directly providing authentication to the request via Basic auth, then OAuth is not necessary. If the Client is the one acting, then we need this filter to step in and make sure the request is properly authenticated.
And why not doing this the other way round? Just bypass resource server if there is no token attached, then fallback to normal security filter chain. This is by the way what resource server filter is stopping on.
@Configuration
@EnableResourceServer
class ResourceServerConfig : ResourceServerConfigurerAdapter() {
@Throws(Exception::class)
override fun configure(resources: ResourceServerSecurityConfigurer) {
resources.resourceId("aaa")
}
/**
* Resources exposed via oauth. As we are providing also local user interface they are also accessible from within.
*/
@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.requestMatcher(BearerAuthorizationHeaderMatcher())
.authorizeRequests()
.anyRequest()
.authenticated()
}
private class BearerAuthorizationHeaderMatcher : RequestMatcher {
override fun matches(request: HttpServletRequest): Boolean {
val auth = request.getHeader("Authorization")
return auth != null && auth.startsWith("Bearer")
}
}
}
Can't provide you with complete example, but here's a hints to dig:
Roughly, spring auth is just a combination of request filter that extract auth data from request (headers) and authentication manager that provides authentication object for that auth.
So to get basic and oauth at the same url, you need 2 filters installed in filter chain BasicAuthenticationFilter and OAuth2AuthenticationProcessingFilter.
I think the problem is that ConfiguringAdapters good for more simple confs as they tend to override each other. So as a first step try to move
.httpBasic();
call to ResourceServerConfiguration
Note that you also need to provide 2 different auth managers: one for basic auth and one for oauth