I recently ran in an issue at work:
Using a role-based access control with Spring Security, how can I refresh
user B
’s authorities whenuser A
modifiesuser B
’s roles?
You’d think that this would be done automatically, but unfortunately, it isn’t. The authorities are decided upon authentication, meaning that until the user re-authenticates, his authorities won’t necessarily reflect the current access he should be having.
I searched quite a bit for an easy answer, and there were many posts who were left unanswered, or even worse, answers from XML-based Spring configuration.
Ugh, XML.
Anyhow, I ended up having to figure it out on my own: just force each users to re-authenticate every time they make a request. By using an interceptor, we can do just that! The downside is that well, obviously, if you use a database to get the roles of your users, you’ll have to query your database every time there’s a request.
Luckily for me (and unluckily for you, probably), performance was not a concern, so I could ignore the performance impact.
First, create an interceptor:
@Component
public class VerifyAccessInterceptor implements HandlerInterceptor {
// ...
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Set<GrantedAuthority> authorities = new HashSet<>();
if (auth.isAuthenticated()) {
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
}
User userFromDatabase = getUserFromDatabase(auth.getName());
if (userFromDatabase != null) {
// add whatever authorities you want here
authorities.add(new SimpleGrantedAuthority("..."));
}
Authentication newAuth = null;
if (auth.getClass() == OAuth2AuthenticationToken.class) {
OAuth2User principal = ((OAuth2AuthenticationToken)auth).getPrincipal();
if (principal != null) {
newAuth = new OAuth2AuthenticationToken(principal, authorities,(((OAuth2AuthenticationToken)auth).getAuthorizedClientRegistrationId()));
}
}
SecurityContextHolder.getContext().setAuthentication(newAuth);
return true;
}
}
My specific implementation uses OAuth2 (OAuth2AuthenticationToken
), but you can use UsernamePasswordAuthenticationToken
instead.
And now, to add your interceptor to the configuration:
@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
@Autowired
private VerifyAccessInterceptor verifyAccessInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(verifyAccessInterceptor).addPathPatterns("/**");
}
}
Obviously, this is just a very rough implementation. It works as-is, but that it shouldn’t be given a bit more love. A few enhancements can be made, such as:
Authentication
object