Keycloak + Spring. Ignore certain APIs regardless of Authorization Header - spring-boot

I want to allow certain APIs of pattern api/v1/public/** for all authenticated as well as anonymous users.
I have Keycloak adapter setup in my project, and my WebConfiguration looks like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class KeycloakWebConfig extends KeycloakWebSecurityConfigurerAdapter {
// ........ other methods .........//
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.authorizeRequests()
.anyRequest().permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/v1/public/**");
}
}
With this setup, when I call /api/v1/public/ping API without any headers, it works fine. I get the expected response.
However, if I add a random or empty Authorization header while making the request to the same api, it does not respond at all.
Using Postman to make request, it says "There was an error connecting to http://localhost:8080/api/v1/public/ping".
Using cURL terminal command to make request, it does not print anything and terminates the process.
If I make the same call again (using either postman or curl) without any header fields, it works perfectly fine again. Any idea what is causing this?

Related

Restrict the Rest API Methods except GET on a Spring Boot Project

I made a Rest API project with Spring Boot. There are every standard methods like get, post, put and delete in the Controller.
My aim is to make it possible for me to only be able to access the api calls (except get calls) via my angular app. Other methods (post, put and delete) can not be accessible from outside.
I tried to solve this problem with WebSecurityConfigurerAdapter and configure function but I couldn't get it.
When I first imported the security dependency (spring-boot-starter-security) on pom.xml, then all methods were blocked. I tried to permit the get calls in configure method but then I could not make a post call with basic auth over postman. Everytime I got 403 Forbidden error.
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.GET)
.antMatchers("/h2-console/**")
.antMatchers("/login/**");
}
}
By the way, I wanted to make my own username and passwort for spring security in the application.properties file. But I think that doesn't work if I use a SecurityConfig configuration file.
spring.security.user.name=myUsername
spring.security.user.password=myPassword
Regardless of my attempts, how can I actually get this from shortest and easiest way?
Then how can I call the blocked methods (post, put, delete) from my angular application?
Thanks.
If I'm not mistaken, you want your project to have no access restrictions for GET methods and everyone should have access to this method type.
All remaining requests (post, put, delete, etc.) can be accessed with an authentication.
You can achieve this as follows. Assuming you have a controller like below:
#RestController
#RequestMapping("security")
public class SecurityController {
#GetMapping("get")
public ResponseEntity<String> get() {
return ResponseEntity.ok("Get Method");
}
#PostMapping("post")
public ResponseEntity<String> post() {
return ResponseEntity.ok("Post Method");
}
#PutMapping("put")
public ResponseEntity<String> put() {
return ResponseEntity.ok("Put Method");
}
#DeleteMapping("delete")
public ResponseEntity<String> delete() {
return ResponseEntity.ok("delete");
}
}
In this case your WebSecurityConfigurer should be like below:
#EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.GET).permitAll()
.anyRequest().authenticated().and().httpBasic();
}
}
The first thing to do here is to determine that GET, which is an http method, can access without any authorization. It then authorizes the accesses of the remaining HttpMethod's. Finally, we specify that we are using Basic Auth with httpBasic(). This information consists of username and password information defined in your application.properties file.
You can see the difference between HttpSecurity and WebSecurity by examining the question here.
I hope this answer helps you.

Is possible ask for an acces token oauth2 just with refresh token in spring security? without basic authentication?

I would like to know if in spring oauth2 is possible get a new pair tokens (access token and refresh token) just using another refresh token, without the basic authentication (without clientId and clientSecret, is there any way?
For exemple:
WITH BASIC AUTH
curl -u clientId:clientSecret -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
WITHOUT BASIC AUTH
curl -u -X POST 'http://myapplication.oauth2/accounts/oauth/token?grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=' -v
I note that sprint BasicAuthenticationFilter in spring uses validation bellow, maybe override this filter and make the authentication just with refresh token.
String header = request.getHeader("Authorization");
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
The short answer is no. The class used to manage the Spring Oauth 2 endpoints is the following one:
#FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint
Both requests, I mean, get access token and refresh one use the same endpoint with different parameters. And the method to manage those ones is:
#RequestMapping(
value = {"/oauth/token"},
method = {RequestMethod.POST}
)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
} else {
String clientId = this.getClientId(principal);
...
As you can see, a Principal object is required (in this case provided by the Basic Authentication).
Even, if you configure the security of your project to permit that url without checking authentication, you will achieve to "enter" in above method but you will receive an InsufficientAuthenticationException because no Authentication instance has been provided.
Why custom authentication will not work
1. Create a custom AuthenticationProvider will not work because the method postAccessToken is invoked before. So you will receive an InsufficientAuthenticationException.
2. Create a OncePerRequestFilter and configure it to execute before process the current request:
#Override
protected void configure(HttpSecurity http) throws Exception {
http...
.anyRequest().authenticated()
.and()
.addFilterBefore(myCustomFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(POST, "/accounts/oauth/**");
}
with a code "similar to":
#Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("existingUser",
"passwordOfExistingUser",
Collections.emptyList()));
...
filterChain.doFilter(request, response);
}
The problem with this approach is the principal in TokenEndpoint comes from the HttpServletRequest not from Spring context, as you can see debugging BasicAuthenticationFilter class.
In your custom filter you can try, using reflection, set a value in userPrincipal property but, as you can verify, request has several "internal request properties" and that could be a "too tricky option".
In summary, Oauth standard needs user/pass to access to the resources, if you want to workaround in almost of provided endpoints maybe that project is not what you are looking for.
Workaround to include your own object in Spring Principal
I do not recommend that but if you still want to go ahead with this approach, there is a way to include your own value inside the principal parameter received by TokenEndpoint class.
It is important to take into account BasicAuthorizationFilter will be still executed, however you will be able to override the Spring principal object by your own one.
For this, we can reuse the previous CustomAuthenticationFilter but now your have to include the filters you need, I mean, allowed urls, parameters, etc You are going to "open the doors", so be careful about what you allow and not.
The difference in this case is, instead of add the configuration in our class that extends WebSecurityConfigurerAdapter we are going to do it in:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private CustomAuthenticationFilter customAuthenticationFilter;
...
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.checkTokenAccess("isAuthenticated()");
security.addTokenEndpointAuthenticationFilter(customAuthenticationFilter);
}
...

While protecting the app with OAuth2, I'd like to expose some URLs accessible to anyone

After I created a small Spring Boot 2.2.6 application and I configured AWS Cognito as authentication provider, everything work well. When accessing any of application's URLs, I am redirected to Cognito and, after login, the application worked well.
I try to add some public pages (/api/**), which do not require any authentication.
First I tried this:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").permitAll(); // This should be permitted for anyone
but, now, everything is open. No security at all. Ooops.
I change it to:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").permitAll() // This should be permitted for anyone
.anyRequest().authenticated(); // Everything else should be protected
Now, the whitelisted URL (/api/**) work well, no password. But all other URL (eg. /private), instead of redirecting me to the login page, produce a 403 error:
There was an unexpected error (type=Forbidden, status=403). Access Denied
Does anybody have any idea how to keep the original behaviour (password) but with few URLs accessible anonymously?
The WebSecurityConfigurerAdapter has another method that can be used to ignore certain urls:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/**");
}

LemonWebSecurityConfig customization

I use SpringLemon for my project. I would like to customize authorizeRequests method so that any request that starts with "/xyz" is only accessible for authenticated users. ("/xyz/abc", /xyz/def", "xyz/ghi/jkl", etc.)
In order to do this, I made my own class extending LemonWebSecurityConfig class, and made it a configuration class. I've overridden authorizeRequests method to look like this:
#Override
protected void authorizeRequests(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/xyz/**").authenticated()
.mvcMatchers("/**").permitAll();
}
As I tested it, it worked for those "/xyz" URLs (got 403 without authentication), "/api/core/context" gave me "200", but the "/api/core/login" URL always gave me 404. It responses with 404 even if I don't override authorizeRequests method and I only have the empty Configuration class.
What am I missing?
Actually I extended a wrong class. Using the right class (as it is seen in lemon-demo-jpa) it works perfectly:
#Component
public class MySecurityConfig extends LemonJpaSecurityConfig {
#Override
protected void authorizeRequests(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/xyz/**").authenticated();
super.authorizeRequests(http);
}
}

#EnableOAuth2Sso - How to protect / unprotect resources?

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.

Resources