If you're working on a Spring Security (and especially an OAuth) implementation, definitely have a look at the Learn Spring Security course:
>> LEARN SPRING SECURITYMocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.
Get started with mocking and improve your application tests using our Mockito guide:
Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.
Get started with understanding multi-threaded applications with our Java Concurrency guide:
Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:
Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.
But these can also be overused and fall into some common pitfalls.
To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:
Get started with Spring and Spring Boot, through the Learn Spring course:
>> LEARN SPRINGExplore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:
Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.
I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.
You can explore the course here:
Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.
Get started with Spring Data JPA through the guided reference course:
Refactor Java code safely β and automatically β with OpenRewrite.
Refactoring big codebases by hand is slow, risky, and easy to put off. Thatβs where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.
Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions β one for newcomers and one for experienced users. Youβll see how recipes work, how to apply them across projects, and how to modernize code with confidence.
Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.
1. Overview
In this quick tutorial, weβre going to show how we can add logout functionality to an OAuth Spring Security application.
We will, of course, use the OAuth application described in a previous article β Creating a REST API with OAuth2.
Note: this article is using the Spring OAuth legacy project. For the version of this article using the new Spring Security 5 stack, have a look at our article Logout in an OAuth Secured Application.
2. Remove the Access Token
Simply put, logging out in an OAuth-secured environment involves rendering the userβs Access Token invalid β so it can no longer be used.
In a JdbcTokenStore-based implementation, this means removing the token from the TokenStore.
Letβs implement a delete operation for the token. Weβre going to use the parimary /oauth/token URL structure here and simply introduce a new DELETE operation for it.
Now, because weβre actually using the /oauth/token URI here β we need to handle it carefully. We wonβt be able to simply add this to any controller β because the framework already has operations mapped to that URI β with POST and GET.
Instead what we need to do is to define this is a @FrameworkEndpoint β so that it gets picked up and resolved by the FrameworkEndpointHandlerMapping instead of the standard RequestMappingHandlerMapping. That way we wonβt run into any partial matches and we wonβt have any conflicts:
@FrameworkEndpoint
public class RevokeTokenEndpoint {
@Resource(name = "tokenServices")
ConsumerTokenServices tokenServices;
@RequestMapping(method = RequestMethod.DELETE, value = "/oauth/token")
@ResponseBody
public void revokeToken(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.contains("Bearer")){
String tokenId = authorization.substring("Bearer".length()+1);
tokenServices.revokeToken(tokenId);
}
}
}
Notice how weβre extracting the token out of the request, simply using the standard Authorization header.
3. Remove the Refresh Token
In a previous article on Handling the Refresh Token, we have set up our application to be able to refresh the Access Token, using a Refresh Token. This implementation makes use of a Zuul proxy β with a CustomPostZuulFilter to add the refresh_token value received from the Authorization Server to a refreshToken cookie.
When revoking the Access Token, as shown in the previous section, the Refresh Token associated with it is also invalidated. However, the httpOnly cookie will remain set on the client, given that we canβt remove it via JavaScript β so we need to remove it from the server side.
Letβs enhance the CustomPostZuulFilter implementation that intercepts the /oauth/token/revoke URL so that it will remove the refreshToken cookie when encountering this URL:
@Component
public class CustomPostZuulFilter extends ZuulFilter {
//...
@Override
public Object run() {
//...
String requestMethod = ctx.getRequest().getMethod();
if (requestURI.contains("oauth/token") && requestMethod.equals("DELETE")) {
Cookie cookie = new Cookie("refreshToken", "");
cookie.setMaxAge(0);
cookie.setPath(ctx.getRequest().getContextPath() + "/oauth/token");
ctx.getResponse().addCookie(cookie);
}
//...
}
}
4. Remove the Access Token from the AngularJS Client
Besides revoking the access token from the token store, the access_token cookie will also need to be removed from the client side.
Letβs add a method to our AngularJS controller that clears the access_token cookie and calls the /oauth/token/revoke DELETE mapping:
$scope.logout = function() {
logout($scope.loginData);
}
function logout(params) {
var req = {
method: 'DELETE',
url: "oauth/token"
}
$http(req).then(
function(data){
$cookies.remove("access_token");
window.location.href="login";
},function(){
console.log("error");
}
);
}
This function will be called when clicking on the Logout link:
<a class="btn btn-info" href="#" ng-click="logout()">Logout</a>
5. Conclusion
In this quick but in-depth tutorial, weβve shown how we can logout a user from an OAuth secured application and invalidate the tokens of that user.
