BearerTokenAuthenticationFilter is allowing the request when Authorization token is not present in the request - spring-boot

I am extending the WebSecurityConfigurerAdapter and adding the OAuth2 authorization for the app to app communication. If I provide a wrong token or expired token, I am getting the failure response. But if I dont provide an Authorization header, then it is not failing.
http.authorizeRequests().
mvcMatchers(path).hasAnyAuthority(...)
.and()
.oauth2ResourceServer().
jwt();
I can see that the below code inside the BearerTokenAuthenticationFilter which may be the reason the no token scenario is not failing
if (token == null) {
filterChain.doFilter(request, response);
return;
}
How this scenario is usually handled.

Related

Logic to implement a RESTFUL logout API using oauth2ResourceServer JWT in a spring application

The issue I have is after the user is authenticated meaning user has signed in, I understand from the client side to logout a user, I delete the token from the local storage but the issue I have is how do I invalidate the token or logout from the serverside.
My intial approach was to make the logout API permit all in my SecurityFilterChain but when I try to grab the authenticated user from SecurityContextHolder after the user had signed in I was getting anonymousUser.
My second/current approach is I instead authorized LOGOUT API which means to access the API, a token has to passed in the header. Then I can then set SecurityContextHolder.getContext().setAuthentication(authentication == false); and also clearContext(). With this approach I am able to get the logged in user but my questions are:
Is this the right logic to implement a log out?
I understand a token cannot be invalidated because it is STATELESS. But is there a way to get around this? Because even after setting Authentication to false in SecurityContextHolder
and clearing security context SecurityContextHolder.clearContext(); when I try accessing Authenticated API i.e CRUD operations, I am still able to use the token.
Here is my login and logout methods in my RestController Class
logout
#PostMapping(path = "/logout", headers = "Authorization")
#ResponseStatus(OK)
public ResponseEntity<?> logout() {
LOGGER.info("Trying to Logout ");
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
LOGGER.info("Username {} ", username);
authentication.setAuthenticated(false);
SecurityContextHolder.getContext().setAuthentication(authentication);
SecurityContextHolder.clearContext();
return ResponseEntity.ok().body("Successfully logged out");
}
login
#PostMapping(path = "/login", consumes = "application/json", produces = "application/json")
#ResponseStatus(OK)
public ResponseEntity<?> login(#Valid #RequestBody UserDTO userDTO) {
Authentication authentication;
LOGGER.info("Authenticating {}", userDTO.getUsername());
var authenticationToken = confirmUser(userDTO); // returns a UsernamePasswordAuthenticationToken
try {
authentication = authenticationManager.authenticate(authenticationToken); // Authenticate user password token
SecurityContextHolder.getContext().setAuthentication(authentication); // Set the security context to the logged user
} catch (AuthenticationException e) {
LOGGER.error("Stack trace {}", e.getMessage());
SecurityContextHolder.getContext().setAuthentication(null);
throw new InvalidPasswordException("Wrong username or password");
}
LOGGER.info("{} has signed in", userDTO.getUsername());
return ResponseEntity.ok()
.header( AUTHORIZATION, tokenService.generateToken(authentication) )
.build();
}
I might recommend a different approach, but let's start with your question.
Expiring Access Tokens
To expire a resource server token, you will need to add some kind of state.
This usually comes in the form of some kind of list of valid tokens. If the token isn't in the list, then the token is not valid.
A common way to achieve this is to rely on the authorization server. Many authorization servers ship with an endpoint that you can hit to see if a token is still valid.
Modeling Things Differently
That said, it might be worth considering if you should be thinking about the access token differently. The access token does not represent a user's authenticated session. It represents the user granting access to the client to operate on the user's behalf.
So after the user logs out, it still makes quite a bit of sense for the client to have a valid access token so that the user doesn't have to reauthorize the client every time they log in.

Spring Security:Why check if authentication token is set in authorization filter?

I'm trying to understand spring security. I came across following piece of code
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
// Reads the JWT from the Authorization header, and then uses JWT to validate the token
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if (user != null) {
// new arraylist means authorities
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
What is the need of
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
Authorization will be done after authentication right ? In that case header will be set anyway.What am I missing?
And also what is chain.doFilter() doing exactly? It is used to proceed and hit the servlet eventually right? If user isn't authenticated why proceed with request?If it is to proceed to authentication filter then how come authorization filter invoked before authentication filter?
In spring security a filter chain is basically a linked list. So as you do a request, it will hit the first filter, that filter will then call the next chain in the link, then the next, the next, etc.
// Get a specific header (im guessing authorization header)
String header = req.getHeader(HEADER_STRING);
// If no such header was found, or the found header does not include a specific prefix
// (im guessing the string Baerer) which means that if this is not a
// authentication header containing a baerer token, then call the next filter in
// the chain.. The next filter will have the same call, etc.
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
If a request is sent with no auth header and we proceed into the application, down the filter chain as we hit the actually endpoint, it will probably have an anotation saying that you need a specific role, which the user wont have, since it hasnt authenticated itself. And the result will be a 401 UNAUTHORIZED returned from the security framework.
If it on the other hand it finds an auth header with a bearer prefix it will try to extract out the token from the header, verify its integrity that no one has tampered with it, decode it, and get the subject field from the token. Using the subject it will create a UsernamePasswordAuthenticationToken and set this as the authenticated token in the security context. Basically setting the subject as the authenticated user.
If there is no subject in the token, filter will return null (which is sloppy coding and might crash the application, a 401 exception should be thrown).
Several bad things with this code:
String header = req.getHeader(HEADER_STRING); is called twice
default user case is to try to log in the user, should be the other way around, default case should be to throw a 401 unauthorised and all logic should try to prevent that.
if a authorization header containing the string bearer but no token is sent the parsing function will return a null value, and the filter will call setAuthentication with the value null which will probably result in a crash.
if a authorization header containing the string bearer the token does not contain a subject value the parsing function will return a null value, and the filter will call setAuthentication with the value null which will probably result in a crash.
Spring already includes support for Nimbus jwt library to encode and decode JWT's there is no need to pull in another library java-jwt as done in the example above.
Worth mentioning that from Spring 5 there is already a fully implemented JWT solution in spring security that only needs to be customised. So this entire custom filter is redundant.

Solution Spring Backend OAuth2 Client for both web apps as for native (mobile) apps

For the past days I have been trying to figuring out how to make OAuth2 work on a native app with the OAuth2 client consisting of a separate frontend application with a Spring backend. Good news! I figured out a way to make it work both as web app (on a browser) as on a native (mobile) app. Here I would like to share my findings and ask for any suggestions on possible improvements.
Where Spring works out of the box
Spring Oauth2 works out of the box for web apps. We add the dependency <artifactId>spring-security-oauth2-autoconfigure</artifactId>. We add the annotation #EnableOAuth2Client. Furthermore, we add the configuration. For an in detail tutorial I would like to refer you to this tutorial.
Where challenges start to arise
Spring works with a session cookie (JSESSIONID) to establish a session which is send to the frontend using a Set-Cookie header. In a mobile application this Set-Cookie header is not send back to the backend on subsequent requests. This means that on a mobile application the backend sees each request as a new session. To solve this, I implement a session header rather than a cookie. This header can be read and therefore added to the subsequent requests.
#Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
However, that solves only part of the problem. The frontend makes a request using window.location.href which makes it impossible to add custom headers (REST call cannot be used because it would make it impossible to redirect the caller to the authorization server login page, because the browser blocks this). The browser automatically adds cookies to calls made using window.location.href. That's why it works on browser, but not on a mobile application. Therefore, we need to modify Spring's OAuth2 process to be able to receive REST calls rather than a call using window.location.href.
The OAuth2 Client process in Spring
Following the Oauth2 process the frontend makes two calls to the backend:
Using window.location.href a call to be redirected to the Authorization server (e.g. Facebook, Google or your own authorization server).
Making a REST GET request with the code and state query parameter to retrieve an access token.
However, if Spring does not recognise the session (like on mobile phone) it creates a new OAuth2ClientContext class and therefore throws an error on the second call: InvalidRequestException("Possible CSRF detected - state parameter was required but no state could be found"); by the AuthorizationCodeAccessTokenProvider.class. The reason it throws this error is because the preservedState property is null on the request. This is nicely explained by this post's answer of #Nico de wit.
I created a visual of the Spring OAuth2 process which shows the box 'Context present in session?'. This is where it goes wrong as soon as you have retrieved the authorization code from logging into the authorization server. This is because further on in in the getParametersForToken box it checks the preservedState which is then null because it came from a new OAuth2ClientContext object (rather than the same object that was used when redirecting the first call to the page of the authorization server).
The solution
I solved this problem by extending OAuth2ClientContextFilter.class. This class is responsible for redirecting the user to the authorization server login page if no authorization code has been retrieved yet. Instead of redirecting, the custom class now sends back a 200 and the in the body an url to which the frontend needs to be redirected. Also the frontend can now make a REST call rather than using window.location.href to be redirected. That looks something like:
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
request.setAttribute(CURRENT_URI, this.calculateCurrentUri(request));
try {
chain.doFilter(servletRequest, servletResponse);
} catch (IOException var9) {
throw var9;
} catch (Exception var10) {
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
UserRedirectRequiredException redirect = (UserRedirectRequiredException)this.throwableAnalyzer.getFirstThrowableOfType(UserRedirectRequiredException.class, causeChain);
if (redirect == null) {
if (var10 instanceof ServletException) {
throw (ServletException)var10;
}
if (var10 instanceof RuntimeException) {
throw (RuntimeException)var10;
}
throw new NestedServletException("Unhandled exception", var10);
}
// The original code redirects the caller to the authorization page
// this.redirectUser(redirect, request, response);
// Instead we create the redirect Url from the Exception and add it to the body
String redirectUrl = createRedirectUrl(redirect);
response.setStatus(200);
response.getWriter().write(redirectUrlToJson(redirectUrl));
}
}
The createRedirectUrl contains some logic building the Url:
private String createRedirectUrl(UserRedirectRequiredException e) {
String redirectUri = e.getRedirectUri();
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(redirectUri);
Map<String, String> requestParams = e.getRequestParams();
Iterator it = requestParams.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> param = (Map.Entry)it.next();
builder.queryParam(param.getKey(), param.getValue());
}
if (e.getStateKey() != null) {
builder.queryParam("state", e.getStateKey());
}
return builder.build().encode().toUriString();
}
I hope it helps others in the future by implementing OAuth2 using Spring on web and mobile applications. Feel free to give feedback!
Regards,
Bart

An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: 406 Not Acceptable

I am trying use spirng-oauth2-client to connect my project with a third-party authentication server (following this instruction), ans right now when I run the application, after the authorization step, I am redirect back for my application, and a page with this error is displayed:
[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: 406 Not Acceptable: [Media is not supported]
In the comments for an answer in another Stack Overflow post, someone suggested that this is happening because "Spring makes the POST for the authenntication code with FORM parameters, whereas mercadolibre expects no body, only query parameters".
I have this configuration right now:
application.properties
spring.security.oauth2.client.registration.mercadolivre.provider=mercadolivre
spring.security.oauth2.client.registration.mercadolivre.client-id=...
spring.security.oauth2.client.registration.mercadolivre.client-secret=...
spring.security.oauth2.client.registration.mercadolivre.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.mercadolivre.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.provider.mercadolivre.authorization-uri=https://auth.mercadolivre.com.br/authorization
spring.security.oauth2.client.provider.mercadolivre.token-uri=https://api.mercadolibre.com/oauth/token
security.java
#Configuration
public class Security extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.defaultSuccessUrl("/");
}
}
Anyone knows how to change the Spring behavior to match th required for the service? I mean, making the POST for the authenntication code with no body, only query parameters?
For me the error was [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: 401 Unauthorized
The issue was an expired/outdated Client Id and Secret. (I used the Client Id and Secret before and it worked)
This error relates to the response you get from the authentication server, either during client authentication or during fetching of the user-info. We can force the method for both requests to be BASIC instead of POST with these properties
spring.security.oauth2.client.registration.mercadolivre.client-authentication-method=BASIC
spring.security.oauth2.client.provider.mercadolivre.user-info-authentication-method=BASIC
In you controller tha you is redirected for, try to put consumes Json like this:
#GetMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE)
public String indexPage() {
.
.
}
Or MediaType.ALL_VALUE

unable to get Oauth2 token from auth server

I have an auth-server configured in spring with
clients
.inMemory()
.withClient("my-web-app")
.secret("my-web-app-secret")
.authorizedGrantTypes(//
"authorization_code",//
"refresh_token",//
"password"//
).scopes("openid")
Now I want to develop a command line application for the webapp. Hence I need to register one more client with a seperate client id and secret.
I have done something like this
.and()
.inMemory()
.withClient("my-cli")
.secret("my-cli-secret")
.authorizedGrantTypes("authorization_code","client_credentials")
.scopes("read","write","trust").authorities("ROLE_USER");
What I want to achieve is use simply provide the username/password and the client app should be able to get the auth token from the auth server.
What I have tried and understood is I should be using Resource Owner password Grant. I have to use the spring Oauth2restTemplate after this.
The problem in this configuration is when I m hitting the tokenuri i m getting
{
error: "unauthorized"
error_description: "Full authentication is required to access this resource"
}
and everytime it is hitting with the anonymous user.
If you want to user username/password for obtaining the access token you definitely need to use grant_type=password.
You also don't need to specify .inMemory() twice - just two clients with .and() between them.
So the configuration then have to be something like
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
// First client
.withClient("my-web-app")
.secret("my-web-app-secret")
.authorizedGrantTypes(//
"authorization_code",//
"refresh_token",//
"password"//
).scopes("openid")
// Second Client(CLI)
.and()
.withClient("my-cli")
.secret("my-cli-secret")
.authorizedGrantTypes("password")
.scopes("read", "write", "trust")
.authorities("ROLE_USER");
}
And other very important thing - you need to set the Authorization: header in the http request for the token. So the header should be
"Authorization: Basic " + Base64.encodeBase64String((clientId + ":" + clientSecret).getBytes())
This header is checked before the username\password and defines that the client(CLI in your case) is an authorized client (this can cause the error from your question).
That would be really good if you can add the code how exactly you use Oauth2RestTemplate

Resources