I’m trying to use the #EnableOAuth2Sso feature in Spring Cloud Security. Specifically, I’m attempting to protect some resources with OAuth2 while leaving others publicly accessible. I've managed to get this working, but I'm looking at the resulting code and wondering if there is a cleaner way.
I’m following the documentation here: https://github.com/spring-cloud/spring-cloud-security/blob/master/src/main/asciidoc/spring-cloud-security.adoc AND similar guidance from the Spring Boot reference. I have a tiny code example that illustrates my dilemma: https://github.com/kennyk65/oAuthSsoExample.
In a nutshell, I want the localhost:8080/unprotected resource to be publicly available, and I want the localhost:8080/protected resource to require OAuth2 (via github, as configured). I’m able to get the basic OAuth2 behavior to work just fine, but causing /unprotected to be publicly available is problematic.
First, The docs indicate that you can just use the OAuth2SsoConfigurer’s match() method to specify the resources to protect. I've found this doesn't work; when I try I get an IllegalStateException saying that at least one mapping is required. This appears to be referring to the unimplemented configure(HttpSecurity) method.
Next, I’ve tried to specify a mapping in configure(HttpSecurity) that states that the ‘unprotected’ resources should be unprotected. However, this results in Http basic security being applied to that resource. Curiously, this results in the ‘protected’ resource being completely public!
// This results in “unprotected” being protected by HTTP Basic
// and “protected” being completely open!
#Configuration
protected static class OauthConfig extends OAuth2SsoConfigurerAdapter {
#Override
public void match(RequestMatchers matchers) {
matchers.antMatchers("/protected/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/unprotected/**").permitAll();
}
}
On a whim I tried deliberately adding that the protected resource should be authenticated. This resulted in the protected resource getting OAuth2 protection (hurrah!) but the unprotected resource got http basic security applied (huh?).
// This results in “protected” being protected by OAuth 2
// and “unprotected” being protected by HTTP Basic, even though we say permitAll():
#Configuration
protected static class OauthConfig extends OAuth2SsoConfigurerAdapter {
#Override
public void match(RequestMatchers matchers) {
matchers.antMatchers("/protected/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/protected/**”).authenticated();
.antMatchers("/unprotected/**").permitAll();
}
}
At wit’s end to try to find the magic combination, I tried simply switching HTTP basic authentication off using security.basic.enabled: false. This worked (hurrah!), though I’m still a bit puzzled what the issue is with the mappings.
So I guess my question is, is this correct? What is the best way to protect some resources with OAuth 2 and leave others alone?
If you match on /protected/** then it makes no sense to then add an access rule to /unprotected/** (the path doesn't match so the rule will never be applied). You either need another filter chain for your "unprotected" resources, or a wider match for the SSO one. In the former case, the default one that you get from Spring Security will do if you don't mind switching off the security it is providing. E.g.
#Configuration
protected static class OauthConfig extends OAuth2SsoConfigurerAdapter {
#Override
public void match(RequestMatchers matchers) {
matchers.antMatchers("/protected/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**”).authenticated();
}
}
and set security.basic.enabled=false.
Related
Is there a way to change this place dynamically? In other words, invoke the method that adds or removes antMatchers or override completely. map roles, etc.
#EnableWebSecurity
public class WebSecurityConfigAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//Change this configs dynamically at runtime
}
}
In Spring Security version 5.6, which is in 5.6.0.M3 as of now, you can create an AuthorizationManager bean and define place your rules anywhere you want, like so:
#Autowired
private MyCustomAuthorizationManager access;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().access(access);
}
Or even better, you can define a SecurityFilterChain bean and make use of method parameter injection, instead of extending WebSecurityConfigurerAdapter:
#Bean
SecurityFilterChain app(HttpSecurity http, MyCustomAuthorizationManager access) throws Exception {
...
http.authorizeRequests().access(access);
...
return http.build();
}
There is a great presentation showing how to do this.
I ended up with this solution. The solution is to close the current context and run the new one. Of course, it has the disadvantage because it causes downtime but I use a load balancer and several nodes so it's was ok for me.
I have developed a regular spring mvc application, and want to add some rest controller for developing mobile application. I have written rest controller, and multi spring security configurations.
Problem is, they are in precedence, hence both are loaded at once, and whole application breaks down.I want to use one based upon what type of request it is getting, for example, If I am requesting from Postman, Rest API security configuration should work and if we are using web, web security configuration should work.
Here is my implementation, I don't know how to achieve that, Please suggest what is the right way to doing this. As separating whole Thymeleaf and MVC controller , and moving altogether with Angular is not possible at this stage.
Please note that, we have all rest api defined in /v1/ap1/** and all other mvc part is in /**
Any comments, suggestions would be much appreciated, it is killing my days since 3 days. Thanks in advance
#Configuration
#EnableWebSecurity
public class SecurityConfig {
// ... other codes
#Configuration
#Order(1)
public static class RestAPISecurity extends WebSecurityConfigurerAdapter {
//.. other codes
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/signin/**").permitAll()
.antMatchers("/api/v1/**").hasAnyAuthority("ADMIN", "USER")
.antMatchers("/api/users/**").hasAuthority("ADMIN")
.antMatchers("/api/v1/**").authenticated()
.antMatchers("/login", "/logout", "/register", "/j_spring_security_check").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint).accessDeniedHandler(new CustomAccessDeniedHandler());
}
// .. other codes
#Configuration
#Order(2)
public static class MVCSecurityConfiguration extends WebSecurityConfigurerAdapter {
//.. other codes
// form login and other MVC stuffs
}
}
You can add a request matcher for the first spring security filter chain and every thing else goes to second chain
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(httpServletRequest -> {
String userAgent = httpServletRequest.getHeader("User-Agent");
//If you want to check based on content type
String contentType = httpServletRequest.getContentType();
return userAgent.contains("....")
//check what value postman sends as user agent and use it
})
.sessionManagement()
....
}
I have a requirement to use two kinds of authentication,
for web we #EnableRedisHttpSession and for other consumers like mobile we use #EnableAuthorizationServer with #EnableResourceServer.
suppose we try to protect a controller common to both the authentication mechanisms for e.g /api/v1/test
i have hit a roadblock.
i am only able to use one kind of authentication scheme
if i set #WebSecurityConfigurerAdapter #order(2) and #ResourceServerConfigurerAdapter #order(3) then i can only access the resource via web
and if i set #ResourceServerConfigurerAdapter #order(2) and #WebSecurityConfigurerAdapter #order(3) then only OAuth works.
i am unable to use both the mechanism at the same time.how can we make the two work together, for e.g if the request comes from web use the filter responsible for that and if the request comes from mobile use the appropriate filter. web uses cookies and API Consumers use Authorization : Bearer header.
please help
It's sounds very strange. I suggest you to review how REST API is used and why it should be used by browser users. Better to separate web views and REST API, don't mix it.
However, answering your question "can I use two kinds of authentication for some URI at once" - yes, you can.
You need custom RequestMatcher that will decide how to route incoming request.
So:
for "API consumers" - check existence of Authorization header
contains "Bearer"
for "browser users" - just inverse first rule
Code example:
public abstract class AntPathRequestMatcherWrapper implements RequestMatcher {
private AntPathRequestMatcher delegate;
public AntPathRequestMatcherWrapper(String pattern) {
this.delegate = new AntPathRequestMatcher(pattern);
}
#Override
public boolean matches(HttpServletRequest request) {
if (precondition(request)) {
return delegate.matches(request);
}
return false;
}
protected abstract boolean precondition(HttpServletRequest request);
}
OAuth2 authentication
#EnableResourceServer
#Configuration
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcherWrapper("/api/v1/test") {
#Override
protected boolean precondition(HttpServletRequest request) {
return String.valueOf(request.getHeader("Authorization")).contains("Bearer");
}
}).authorizeRequests().anyRequest().authenticated();
}
}
Web authentication
#Configuration
#EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new AntPathRequestMatcherWrapper("/api/v1/test") {
#Override
protected boolean precondition(HttpServletRequest request) {
return !String.valueOf(request.getHeader("Authorization")).contains("Bearer");
}
}).authorizeRequests().anyRequest().authenticated();
}
}
Using this configuration it's possible to use two different authentication types for one URI /api/v1/test.
In addition, I highly recommended to read the article about Spring Security architecture by Dave Syer, to understand how does it work:
https://spring.io/guides/topicals/spring-security-architecture/#_web_security
I am implementing spring security in a spring boot application to perform JWT validation where I have a filter and an AuthenticationManager and an AuthenticationProvider. What I want to do is that I want to disable security for certain resource paths (make them unsecure basically).
What I have tried in my securityConfig class (that extends from WebSecuirtyConfigurerAdapater) is below:
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(),
UsernamePasswordAuthenticationFilter.class);
httpSecurity.authorizeRequests().antMatchers("/**").permitAll();
httpSecurity.csrf().disable();
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
What I am trying to do right now is that I want to make all my resource paths to be un-secure,
but the above code doesn't work and my authenticate method in my CustomAuthenticationProvider (that extends from AuthenticationProvider) get executed every time
Authentication piece gets executed irrespective of using permitAll on every request. I have tried anyRequest too in place of antMatchers:
httpSecurity.authorizeRequests().anyRequest().permitAll();
Any help would be appreciated.
Override the following method in your class which extends WebSecuirtyConfigurerAdapater:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/unsecurePage");
}
try updating your code in order to allow requests for specific paths as below
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(),
UsernamePasswordAuthenticationFilter.class);
httpSecurity.authorizeRequests().antMatchers("/").permitAll().and()
.authorizeRequests().antMatchers("/exemptedPaths/").permitAll();
httpSecurity.csrf().disable();
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
I'm trying to setup a resource server to work with separate authorization server using spring security oauth. I'm using RemoteTokenServices which requires /check_token endpoint.
I could see that /oauth/check_token endpoint is enabled by default when #EnableAuthorizationServer is used. However the endpoint is not accessible by default.
Should the following entry be added manually to whitelist this endpoint?
http.authorizeRequests().antMatchers("/oauth/check_token").permitAll();
This will make this endpoint accessible to all, is this the desired behavior? Or am I missing something.
Thanks in advance,
You have to
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception
{
oauthServer.checkTokenAccess("permitAll()");
}
For more information on this ::
How to use RemoteTokenService?
Just to clarify a couple of points, and to add some more information to the answer provided by Pratik Shah (and by Alex in the related thread):
1- The configure method mentioned is overridden by creating a class that extends AuthorizationServerConfigurerAdapter:
#EnableAuthorizationServer
#Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("ger-client-id")
.secret("ger-secret")
.authorizedGrantTypes("password")
.scopes("read", "write");
}
}
2- I suggest reading this Spring guide explaining the automatic configuration carried out by Spring Boot when we include the #EnableAuthorizationServer annotation, including an AuthorizationServerConfigurer bean. If you create a configuration bean extending the AuthorizationServerConfigurerAdapter as I did above, then that whole automatic configuration is disabled.
3- If the automatic configuration suits you just well, and you JUST want to manipulate the access to the /oauth/check_token endpoint, you can still do so without creating an AuthorizationServerConfigurer bean (and therefore without having to configure everything programmatically).
You'll have to add the security.oauth2.authorization.check-token-access property to the application.properties file, for example:
security.oauth2.client.client-id=ger-client-id
security.oauth2.client.client-secret=ger-secret
security.oauth2.client.scope=read,write
security.oauth2.authorization.check-token-access=permitAll()
Of course, you can give it an isAuthenticated() value if you prefer.
You can set the log level to DEBUG to check that everything is being configured as expected:
16:16:42.763 [main] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression 'permitAll()', for Ant [pattern='/oauth/check_token']
There is no much documentation about these properties, but you can figure them out from this autoconfiguration class.
One last thing worth mentioning, even though it seems to be fixed in latest Spring versions, I just submitted an issue in the spring-security-oauth project; it seems that the token_check functionality is enabled by default if you add a trailing slash to the request:
$ curl localhost:8080/oauth/check_token/?token=fc9e4ad4-d6e8-4f57-b67e-c0285dcdeb58
{"scope":["read","write"],"active":true,"exp":1544940147,"authorities":["ROLE_USER"],"client_id":"ger-client-id"}
There are three POST parameters, namely client_id (user name), client_secret (password corresponding to the user name), token (token applied for), client_id, client_secret are different from the parameters in the /oauth/token interface
enter image description here
First, config token access expression:
#Override
public void configure(AuthorizationServerSecurityConfigurer securityConfigurer) throws Exception {
securityConfigurer
.allowFormAuthenticationForClients()
.checkTokenAccess("isAuthenticated()")
.addTokenEndpointAuthenticationFilter(checkTokenEndpointFilter());
}
Then, we need define a filter to process client authentication:
#Bean
public ClientCredentialsTokenEndpointFilter checkTokenEndpointFilter() {
ClientCredentialsTokenEndpointFilter filter = new ClientCredentialsTokenEndpointFilter("/oauth/check_token");
filter.setAuthenticationManager(authenticationManager);
filter.setAllowOnlyPost(true);
return filter;
}