Another way that uses http.addFilterBefore()
with custom filters
This solution is more like a skeleton to help you set up the basics.
I've created a working demo
and added some necessary comments to help understand the process. It comes with with some simple role-based
and permission-based
authentication/authorization, a publically accessable endpoint
settings that you can easily pick up and use.
So it's better to see the full code, and run the app in action: github repo
User class set up:
public class User implements UserDetails {
private final String username;
private final String password;
private final List<? extends GrantedAuthority> grantedAuthorities;
public User(
String username,
String password,
List<? extends GrantedAuthority> grantedAuthorities
) {
this.username = username;
this.password = password;
this.grantedAuthorities = grantedAuthorities;
}
// And other default method overrides
}
Adding custom filters through addFilterBefore()
method:
http
.authorizeRequests()
.antMatchers("/")
.permitAll()
.addFilterBefore( // Filter login request only
new LoginFilter("login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class
)
.addFilterBefore( // Filter logout request only
new LogoutFilter("logout"),
UsernamePasswordAuthenticationFilter.class
)
.addFilterBefore( // Verify user on every request
new AuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class
);
Custom LoginFilter
extends AbstractAuthenticationProcessingFilter
, and override three methods, to deal with autentication:
public class LoginFilter extends AbstractAuthenticationProcessingFilter {
public LoginFilter(String url, AuthenticationManager authManager) {
super(url, authManager);
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest req,
HttpServletResponse res
)
throws AuthenticationException, IOException {
LoginUserDto loginUserDto = new ObjectMapper() // this dto is a simple {username, password} object
.readValue(req.getInputStream(), LoginUserDto.class);
return getAuthenticationManager()
.authenticate(
new UsernamePasswordAuthenticationToken(
loginUserDto.getUsername(),
loginUserDto.getPassword()
)
);
}
@Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth
)
throws IOException, ServletException {
User user = (User) auth.getPrincipal();
req.getSession().setAttribute(UserSessionKey, user); // Simply put it in session
res.getOutputStream().print("You are logged in as " + user.getUsername());
}
@Override
protected void unsuccessfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException failed
)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("text/plain");
response.getOutputStream().print(failed.getMessage());
}
}
Custom AuthenticationFilter
check for auth info
stored in session and pass to SecurityContext
:
public class AuthenticationFilter extends GenericFilterBean {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain filterChain
)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpSession session = req.getSession();
User user = (User) session.getAttribute(UserSessionKey);
if (user != null) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
user,
user.getPassword(),
user.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authToken);
}
// Either securityContext has authToken or not, we continue the filter chain
filterChain.doFilter(request, response);
}
}
Custom LogoutFilter
is rather simple and straightforward, invalidate session and terminate authentication process:
public class LogoutFilter extends AbstractAuthenticationProcessingFilter {
public LogoutFilter(String url) {
super(url);
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest req,
HttpServletResponse res
)
throws AuthenticationException, IOException {
req.getSession().invalidate();
res.getWriter().println("You logged out!");
return null;
}
}
A bit of explanation:
What these three custom filters do is that, login
and logout
filter only listen to their repective endpoint.
In login filter, we get username and password
sent from client and check it against a DB(in real world) for validation, if it's valid user, then put it in session and pass it onto SecurityContext
.
In logout filter, we simply invalidate the session
and return a string.
While the custom AuthenticationFilter
will authenticate every incomming request in an attempt to get user info from session, and then pass it onto SecurityContext
.