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
A common requirement for a web application is to redirect different types of users to different pages after login. An example of this would be redirecting standard users to a /homepage.html page and admin users to a /console.html page for example.
This article will show how to quickly and safely implement this mechanism using Spring Security. The article is also building on top of the Spring MVC tutorial which deals with setting up the core MVC stuff necessary for the project.
2. The Spring Security Configuration
Spring Security provides a component that has the direct responsibility of deciding what to do after a successful authentication β the AuthenticationSuccessHandler.
2.1. Basic Configuration
Letβs first configure a basic @Configuration and @Service class:
@Configuration
@EnableWebSecurity
public class SecSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// ... endpoints
.formLogin(formLogin -> formLogin.loginPage("/login.html")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/homepage.html", true))
// ... other configuration
return http.build();
}
}
The part of this configuration to focus on is the defaultSuccessUrl() method. After a successful login, any user will be redirected to homepage.html.
Furthermore, we need to configure users and their roles. For the purpose of this article, weβll implement a simple UserDetailService with two users, each having one single role. For more on this topic, read our article Spring Security β Roles and Privileges.
@Service
public class MyUserDetailsService implements UserDetailsService {
private Map<String, User> roles = new HashMap<>();
@PostConstruct
public void init() {
roles.put("admin2", new User("admin", "{noop}admin1", getAuthority("ROLE_ADMIN")));
roles.put("user2", new User("user", "{noop}user1", getAuthority("ROLE_USER")));
}
@Override
public UserDetails loadUserByUsername(String username) {
return roles.get(username);
}
private List<GrantedAuthority> getAuthority(String role) {
return Collections.singletonList(new SimpleGrantedAuthority(role));
}
}
Also note that in this simple example, we wonβt use a password encoder, therefore the passwords are prefixed with {noop}.
2.2. Adding the Custom Success Handler
We now have two users with the two different roles: user and admin. After a successful login, both will be redirected to hompeage.html. Letβs look at how we can have a different redirect based on the userβs role.
First, we need to define a custom success handler as a bean:
@Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
return new MySimpleUrlAuthenticationSuccessHandler();
}
And then replace the defaultSuccessUrl call with the successHandler method, which accepts our custom success handler as a parameter:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// endpoints
.formLogin(formLogin -> formLogin.loginPage("/login.html")
.loginProcessingUrl("/login")
.successHandler(myAuthenticationSuccessHandler())
// other configuration
return http.build();
}
2.3. XML Configuration
Before looking at the implementation of our custom success handler, letβs also look at the equivalent XML configuration:
<http use-expressions="true" >
<!-- other configuration -->
<form-login login-page='/login.html'
authentication-failure-url="/login.html?error=true"
authentication-success-handler-ref="myAuthenticationSuccessHandler"/>
<logout/>
</http>
<beans:bean id="myAuthenticationSuccessHandler"
class="com.baeldung.security.MySimpleUrlAuthenticationSuccessHandler" />
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user1" password="{noop}user1Pass" authorities="ROLE_USER" />
<user name="admin1" password="{noop}admin1Pass" authorities="ROLE_ADMIN" />
</user-service>
</authentication-provider>
</authentication-manager>
3. The Custom Authentication Success Handler
Besides the AuthenticationSuccessHandler interface, Spring also provides a sensible default for this strategy component β the AbstractAuthenticationTargetUrlRequestHandler and a simple implementation β the SimpleUrlAuthenticationSuccessHandler. Typically these implementations will determine the URL after login and perform a redirect to that URL.
While somewhat flexible, the mechanism to determine this target URL does not allow the determination to be done programmatically β so weβre going to implement the interface and provide a custom implementation of the success handler. This implementation is going to determine the URL to redirect the user to after login based on the role of the user.
First of all, we need to override the onAuthenticationSuccess method:
public class MySimpleUrlAuthenticationSuccessHandler
implements AuthenticationSuccessHandler {
protected Log logger = LogFactory.getLog(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
Our customized method calls two helper methods:
protected void handle(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws IOException {
String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
logger.debug(
"Response has already been committed. Unable to redirect to "
+ targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
Where the following method does the actual work and maps the user to the target URL:
protected String determineTargetUrl(final Authentication authentication) {
Map<String, String> roleTargetUrlMap = new HashMap<>();
roleTargetUrlMap.put("ROLE_USER", "/homepage.html");
roleTargetUrlMap.put("ROLE_ADMIN", "/console.html");
final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (final GrantedAuthority grantedAuthority : authorities) {
String authorityName = grantedAuthority.getAuthority();
if(roleTargetUrlMap.containsKey(authorityName)) {
return roleTargetUrlMap.get(authorityName);
}
}
throw new IllegalStateException();
}
Note that this method will return the mapped URL for the first role the user has. So if a user has multiple roles, the mapped URL will be the one that matches the first role given in the authorities collection.
protected void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
The determineTargetUrl β which is the core of the strategy β simply looks at the type of user (determined by the authority) and picks the target URL based on this role.
So, an admin user β determined by the ROLE_ADMIN authority β will be redirected to the console page after login, while the standard user β as determined by ROLE_USER β will be redirected to the homepage.
