Issue
I'm trying to implement a custom authentication process for REST API back end. Some implementation details:
- I implemented custom
AuthenticationProvider
which authenticates user successfully and returnsAuthentication
object. - I registered brand new
AuthenticationManager
like this to skip some spring boot auto configuration:
@Bean
fun authenticationManager() {
return AuthenticationManager()
}
- In my
AuthController
I have/login
endpoint. The logic of this is fairly simple: it converts DTO to customAuthentication
object and callsAuthenticationManager.authenticate(my_custom_authentication)
.
Note: I do not directly manipulate SecurityContextHolder.getContext()
anywhere in my code.
The problem is that security context is not get updated after successful authentication and when I try to access SecurityContextHolder.getContext()
in spring interceptor post processing method I see it is not my Authentication
object from custom AuthenticationProvider
I got after successful authentication.
Here are some questions:
- Is it ok and common to directly access
SecurityContextHolder.getContext()
to edit it? It feels wrong and hacky to do something like this. - I thought that
AuthenticationManager
handles security context updates on its own. I mean we callAuthenticationManager.authenticate(my_custom_authentication)
and it updates context on successful authentication. If it is not the case and we should keep security context up-to-date manually why do we even need to haveAuthenticationManager
?
Can you please point me on my mistakes if any?
Also if there is classical rest api spring based login and security implementation it would be very nice to look at this as it seems there are a lot of things to be done for this to work properly.
EDIT Here is a sample project to represent the issue I have.
Solution
If you want to skip the auto config of Spring Security add an @Configuration
class with @EnableWebSecurity
. Don't register an empty AuthenticationManager
.
Add a bean for your custom provider and register that with the AuthenticationManager
through the AuthenticationManagerBuilder
.
@Configuration
@EnableWebSecurity
public void WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
public void configure(HttpSecurity http) { ... // Security config here}
@Bean
public CustomAuthenticationProvider customProvider() {
return new CustomAuthenticationProvider();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customProvider());
}
}
This will configure the AuthenticationManager
to use your customized AuthenticationProvider
. If you use a recent version of Spring Security (not sure which version includes this) it is enough to just configure your custom AuthenticationProvider
which will then be automatically detected by the AuthenticationManagerBuilder
. See the Spring Security Documentation as well.
The SecurityContext
is being set by the filter chain (for instance through the UsernamePasswordAuthenticationFilter
which delegates authentication itself to the AuthenticationManager
.
Update
The crucial part in your project is that your login endpoint is basically bypassing everything in Spring Security. The main part of Spring Security is implemented in filters, if you want to authenticate in a web application (regardless the mechanism) you will need to add a filter to the chain.
public CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final ObjectMapper mapper;
public CustomAuthenticationFilter(ObjectMapper mapper) {
super(new AntPathRequestMatcher("/auth/login", "POST"));
this.mapper=mapper;
}
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException,
ServletException {
MyPrincipal principal = mapper.readValue(request.getInputStream(), MyPrincipal.class);
MyAuthentication authentication = new MyAuthentication(principal);
setDetails(request, authentication); //assuming you are extending AbstractAuthenticationToken
return getAuthenticationManager().authenticate(authentication);
}
protected void setDetails(HttpServletRequest request,
MyAuthentication authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
In your configuration add this filter to Spring Security and configure the filter accordingly.
@Bean
public CustomAuthenticationFilter customAuthenticationFilter(ObjectMapper mapper) {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter(mapper);
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
// To prevent registering the filter in the default filter chain!
@Bean
public FilterRegistrationbean customAuthenticationFilterRegistration() {
FilterRegistrationBean filterReg = new FilterRegistrationBean(customAuthenticationFilter());
filterReg.setEnabled(false);
return filterReg;
}
@Override
public void configure(HttpSecurity http) {
http.addFilterBefore(customerAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// other config here
}
Now your authentication is part of the Spring Security part.
Answered By - M. Deinum