Spring Boot Security - Sub Urls are unauthorized - spring-security

I am doing my first Spring Security project and I am struggling to solve this bug. I want every URL starting with /user to be authorized and then get the response associated with the URL.
This is configuration code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().permitAll()
.and().httpBasic();
}
When I access /user using postman I get the expected response. But when i try to access /user/foo on postman I get error code 401: unauthorised even with the valid username and password.
{
"timestamp": "2020-07-11T12:38:17.805+00:00",
"status": 401,
"error": "Unauthorized",
"message": "",
"path": "/user/foo"
}
Same thing happens with /admin/... URLs, /admin is accessible but /admin/foo is unauthorized.
URLS are /user/{username} and /admin/{username}. Considering foo as the username. I am not sure if I have used wildcard in the right way or not.
Also /user/{username} is applied for GET, PUT, POST and DELETE methods.

Related

What is httpBasic method in spring security?

I override configure(HttpSecurity http) method in SampleSecurityConfig Class like this
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/delete/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin().and().httpBasic();
}
If i don't use httpBasic method, it seems no problem occurred.
what does httpBasic method exactly do?
Calling this method on HttpSecurity will enable Http Basic Authentication for your application with some "reasonable" defaults.
It will return a HttpBasicConfigurer for further customization.
You can test this by curl and passing a header like Authorization: Basic bzFbdGfmZrptWY30YQ== but base64 encoding a valid username/password combination.
Documentation for httpBasic
What are we saying by calling httpBasic() ?
When httpBasic() is called, we are telling Spring to authenticate the request using the values passed by the Authorization request header. If the request is not authenticated you will get a returned status of 401 and a error message of Unauthorized
What is actually happening when httpBasic() is called ?
By calling httpBasic(), an instance of the BasicAuthenticationFilter is added to the filter chain. The BasicAuthenticationFilter will then proceed with trying to authenticate the request in the typical Spring Security fashion. If authentication is successful, the resulting Authentication object will be placed into the SecurityContextHolder, which can then be used for future authentication purposes.

Logout not working in Basic Auth in Spring Security

The official Spring Github Repo's Readme reads:
The application is almost finished functionally. The last thing we
need to do is implement the logout feature that we sketched in the
home page. If the user is authenticated then we show a "logout" link
and hook it to a logout() function in the AppComponent. Remember, it
sends an HTTP POST to "/logout" which we now need to implement on the
server. This is straightforward because it is added for us already by
Spring Security (i.e. we don’t need to do anything for this simple use
case). For more control over the behaviour of logout you could use the
HttpSecurity callbacks in your WebSecurityAdapter to, for instance
execute some business logic after logout.
Taken from: https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/single
However, I am using basic authentication and testing it with Postman app. The POST on '/logout' gives me a 403 Forbidden like so:
{
"timestamp": "2018-07-30T07:42:48.172+0000",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/logout"
}
My Security Configurations are:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/save")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/user/**")
.hasRole("USER")
.and()
.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.and()
.httpBasic()
.and()
.logout()
.permitAll()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
I want the session to be invalidated, all the cookies to be deleted, such that when I query again on the endpoint /user with wrong credentials, I should get a 403. However, even after POST on /logout (which gives 403 anyway), the application accepts the GET on /user from the previous session and shows me the details of the user.
The endpoint is:
#GetMapping
public Principal user(Principal user){
return user;
}

OAuth2 with Implicit client and csrf protection

I have an API I want to secure with OAuth2. I already did a dummy test with the password grant_type and everything works. I can request tokens, access secured endpoints with it, etc. The server acts as the authorization and resource server.
Later on I read that I should be using the implicit grant_type as the client will be a javascript app.
My client is configured like so:
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {// #formatter:off
clients
.inMemory().withClient("web")
.redirectUris("http://localhost:3000")
.secret("secret")
.authorizedGrantTypes("implicit", "refresh_token").scopes("read", "write")
.accessTokenValiditySeconds(3600).refreshTokenValiditySeconds(2592000);
}
If I try accessing the endpoint like this:
http://localhost:8080/oauth/authorize?grant_type=implicit&client_id=web&response_type=token&redirect_uri=http%3A%2F%2Flocalhost%3A3000
I get this:
{
"timestamp": 1464136960414,
"status": 403,
"error": "Forbidden",
"message": "Expected CSRF token not found. Has your session expired?",
"path": "/oauth/authorize"
}
How can I have a CSRF token if it's the first time I'm calling the API?
If (just for testing) I disable csrf then I get this:
{
"timestamp": 1464136840865,
"status": 403,
"error": "Forbidden",
"exception": "org.springframework.security.authentication.InsufficientAuthenticationException",
"message": "Access Denied",
"path": "/oauth/authorize"
}
Setting the client with the password grant_type I'm able to make this call and everything works:
http://localhost:8080/oauth/token?grant_type=password&username=test&password=123
And adding the Authorization Basic header with the client id/secret.
Just to clarify, the idea is to have this unique trusted client. So the user should just input login/password without asking the user to grant access rights to the app.
Sorry if this is a dumb question. I've been reading everything I can find but cannot seem to make progress with it.
Thanks!
EDIT:
My Spring Security Config:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MongoDBAuthenticationProvider authenticationProvider;
#Autowired
public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
My OAuth Config:
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory().withClient("web")
.redirectUris("http://localhost:3000")
.secret("secret")
.authorizedGrantTypes("implicit", "refresh_token").scopes("read", "write")
.accessTokenValiditySeconds(3600).refreshTokenValiditySeconds(2592000);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
}
Exception in Server:
2016-05-25 19:52:20.744 DEBUG 34968 --- [nio-8080-exec-5] .s.o.p.e.FrameworkEndpointHandlerMapping : Looking up handler method for path /oauth/authorize
2016-05-25 19:52:20.744 DEBUG 34968 --- [nio-8080-exec-5] .s.o.p.e.FrameworkEndpointHandlerMapping : Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(java.util.Map<java.lang.String, java.lang.Object>,java.util.Map<java.lang.String, java.lang.String>,org.springframework.web.bind.support.SessionStatus,java.security.Principal)]
2016-05-25 19:52:20.746 DEBUG 34968 --- [nio-8080-exec-5] o.s.s.w.a.ExceptionTranslationFilter : Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.authentication.InsufficientAuthenticationException: User must be authenticated with Spring Security before authorization can be completed.
at org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(AuthorizationEndpoint.java:138) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_40]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_40]
....
When you call the authorization server for implicit grant type you have to include an opaque string value as state parameter to avoid csrf attacks. So, the request url to the authorization server will look like:
http://localhost:8080/oauth/authorize?grant_type=implicit&client_id=web&response_type=token&redirect_uri=http%3A%2F%2Flocalhost%3A3000&state=123abc
The value you mentioned in the state parameter will be echoed back to you in the response. You then compare the echoed value with initial value to confirm that there is no csrf attack happened.
Thank you,
Soma.

Spring OAuth: Resource Server with Authorization Server backend

I want to develop two independent services, one for the business stuff and one for the user authentication using Spring OAuth 2
Let's call them Business-Service and OAuth-Service.
Now I want the Business-Service delegate to the OAuth-Service if a request is not authenticated. The client application (an Android app) should not know about the OAuth-Service a priori, it should only be delegated to it by the Business-Service with an 302 HTTP redirect for non-authenticated request. To be precise, I want my API landing page to provide a link to http://businessservice.com/login and when my client app decides to follow this link, it gets redirected to the OAuth-Service.
If I annotate the Business-Service with #EnableOAuth2Resource , all of its resources are protected returning a 401 when I curl them without an access token. So far so good. If I provide an access token like this:
curl -v http://localhost:8667/resource/ -H "Authorization: Bearer $TOKEN"
I can access the resource. Still good.
However if I annotate the Business-Service with #EnableOAuth2Sso for enabling the redirection to the OAuth service, it looses the capability of accessing the resources with an access token (same curl as above), it only returns a 302 to the login page http://localhost:8667/login
If I use both annotations, the #EnableOAuth2Resource always seems to "win", as the authentication works but calling http://localhost:8667/login returns a 404.
So what is the right way to create a resource server that delegates to the auth server for non-authenticated calls?
After trying around for hours I now found a solution.
The Business Server (Resource Server) now looks as follows:
#SpringBootApplication
#EnableOAuth2Sso
#EnableOAuth2Resource
public class BusinessService {
public static void main(final String[] args) {
final ConfigurableApplicationContext context = SpringApplication.run(BusinessService.class, args);
}
}
with two configurations, one for the SSO:
#Configuration
public class OAuth2SsoConfiguration extends OAuth2SsoConfigurerAdapter {
#Override
public void match(final RequestMatchers matchers) {
matchers.antMatchers("/");
}
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll();
}
}
and one for the Resource:
#Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/resource/**").and().authorizeRequests().anyRequest().authenticated().antMatchers("/").permitAll();
}
}
This results in the following:
curl -v http://localhost:8667/
returns
HTTP/1.1 200 OK
{"links":[{"rel":"login","href":"http://localhost:8667/login"}]}
curl -v http://localhost:8667/resource/
returns
HTTP/1.1 401 Unauthorized
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
curl -v http://localhost:8667/login
returns
HTTP/1.1 302 Found
Location: http://localhost:8666/user/oauth/authorize?client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A8667%2Flogin&response_type=code&state=YmmNO9
So my business servie is secured with as a resource server returning a 401 for all business resources. The root of the service is applicable for all clients so they can discover the login relation and if they follow this relation, they're redirected to the Authorization server

why Spring Security Exception Translation Filter creates 403 Response Code for default configuration

I am pretty new to Spring Security land. I am using programmatic configuration of Spring Security with servletApi() which is pretty neat.
Here is the configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.securityContext().and()
.servletApi().and()
.authorizeUrls()
.antMatchers("/login").permitAll()
.antMatchers("/").permitAll()
.antMatchers("/**").authenticated();
}
I am using http servlet api login I am not using any filter for this.
In case a unauthorised request, ExceptionTranslationFilter uses Http403EntryForbiddenEntryPoint to return 403 forbidden status.
In my scenario:
If user does not authenticated, a 401 status code should return.
If user authenticated but not authorised, a 403 status code should return.
But default configuration creates 403 status for both case.
Here are my questions:
Why is the default entry point is Http403EntryForbiddenEntryPoint? It can be 401?
If I change Http403EntryForbiddenEntryPoint to Http401EntryForbiddenEntryPoint, does It create a problem?
Thanks

Resources