Issue
I am trying to implement a very simple example of customized authentication process in Spring to get a better understanding of the concept.
I thought i had everything in place now but sending a request to test what I implemented results in a NullPointerException which can be tracked down to this.getAuthenticationManager() returning null in my custom filter. But I don't understand why. The very similar existing question didn't really help me unfortunately. So I would be thankful for help; here are the most relevant classes I think, feel free to ask if any more is needed.
MyAuthenticationFilter (Based on the source-code of UsernamePasswordAuthenticationFilter), error happens in last line of this:
public class MyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public MyAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = request.getParameter("username");
String password = request.getParameter("password");
String secondSecret = request.getParameter("secondSecret");
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
MyAuthenticationToken authRequest = new MyAuthenticationToken(username, new MyCredentials(password, secondSecret));
return this.getAuthenticationManager().authenticate(authRequest);
}
}
My config-class:
@Configuration
@EnableWebSecurity
@EnableWebMvc
@ComponentScan
public class AppConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyAuthenticationProvider myAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(myAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new MyAuthenticationFilter(), BasicAuthenticationFilter.class)
.authorizeRequests().antMatchers("/**")
.hasAnyRole()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
MyAuthenticationProvider:
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
MyAuthenticationToken myAuthenticationToken = (MyAuthenticationToken) authentication;
MyCredentials credentials = (MyCredentials) myAuthenticationToken.getCredentials();
if (credentials.getPassword().equals("sesamOeffneDich") && credentials.getSecondSecret().equals(MyAuthenticationToken.SECOND_SECRET)) {
myAuthenticationToken.setAuthenticated(true);
return myAuthenticationToken;
} else {
throw new BadCredentialsException("Bad credentials supplied!");
}
}
@Override
public boolean supports(Class<?> authentication) {
return MyAuthenticationToken.class.isAssignableFrom(authentication);
}
}
Solution
Why you're seeing a NullPointerException
You're seeing a NullPointerException
because you do not have an AuthenticationManager
wired into your filter. According to the javadocs for AbstractAuthenticationProcessingFilter
The filter requires that you set the authenticationManager property. An AuthenticationManager is required to process the authentication request tokens created by implementing classes
This being the case, I do scratch my head as to why the authenticationManager
did not make the cut for a constructor argument for this abstract filter. I would recommend enforcing this in your constructor for your custom filter.
public MyAuthenticationFilter(AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher("/login", "POST"));
this.setAuthenticationManager(authenticationManager);
}
AuthenticationManager or the AuthenticationProvider
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(myAuthenticationProvider);
}
The AuthenticationManagerBuilder
will create a ProviderManager (an AuthenticationManager)
.
The ProviderManager
is a collection of AuthenticationProvider
and will attempt to authenticate()
with every AuthenticationProvider
that it manages. (This is where public boolean supports(Class<?> authentication)
is extremely important in theAuthenticationProvider
contract)
In your configuration, you have created an ProviderManager
containing just your custom Authentication Provider
Cool. Now where is my AuthenticationManager?
It is possible to grab the AuthenticationManager
built by the configure()
method by this.authenticationManager()
in the WebSecurityConfigurerAdapter
@Bean
public AuthenticationManager authenticationManager throws Exception() {
this.authenticationManager();
}
Recommendations
Creating your own ProviderManager
does have it's benefits as it would be explicit and within your control.
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(myAuthenticationProvider));
}
This will let you be flexible with the placement of your AuthenticationManager
bean and avoid:
- Potential cyclic dependency issues in your bean configurations
- Bubbling up the checked
Exception
as a result of callingthis.authenticationManager()
Putting it all Together
...
public class AppConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyAuthenticationProvider myAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new MyAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class)
...
}
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(myAuthenticationProvider));
}
}
Answered By - shinjw
Answer Checked By - Pedro (JavaFixing Volunteer)