I'm trying to enable spring security in a spring boot rest services project and I'm getting some problems.
I configured it with this code
@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    @Autowired
    private LdapAuthenticationProvider ldapAuthenticationProvider;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .httpBasic().and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(ldapAuthenticationProvider);
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
and implemented a custom authentication provider in order to login to LDAP (which has a non standard configuration so I wasn't able to make the default ldap provider works)
@Component
public class LdapAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        String email = authentication.getName();
        String password = authentication.getCredentials().toString();
        LdapConnection ldap = new LdapConnection();
        String uid = ldap.getUserUID(email);
        if(uid == null || uid == ""){
            throw new BadCredentialsException("User " + email + " not found");
        }
        if(ldap.login(uid, password)){
            return new UsernamePasswordAuthenticationToken(uid, null, new ArrayList<>());
        }else{
            throw new BadCredentialsException("Bad credentials");
        }
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return true;  
        //To indicate that this authenticationprovider can handle the auth request. since there's currently only one way of logging in, always return true
    }
}
This code is working fine, in the sense that calling my services with a basic authorization header it is able to correctly login and return the service called. The problems started when I tried to insert a different authorization/authentication. Instead of using the basic authentication I would like to pass the credential from a form in my react front end, so I would like to pass them as a json body in a POST call. (the idea is then to generate a jwt token and use that for the following communication).
So I changed the configure method to this:
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JWTAuthenticationFilter(authenticationManager()))
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
and defined a custom authentication filter:
public class JWTAuthenticationFilter extends
        UsernamePasswordAuthenticationFilter {
    @Autowired
    private AuthenticationManager authenticationManager;
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException{
        String requestBody;
        try{
            requestBody = IOUtils.toString(req.getReader());
            JsonParser jsonParser = JsonParserFactory.getJsonParser();
            Map<String, Object> requestMap = jsonParser.parseMap(requestBody);
            return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(requestMap.get("email"), requestMap.get("password"), new ArrayList<>()));
        }catch(IOException e){
            throw new InternalAuthenticationServiceException("Something goes wrong parsing the request body",e );
        }
    }
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException{
        JwtTokenProvider tokenProvider = new JwtTokenProvider();
        String token = tokenProvider.generateToken(auth.getPrincipal().toString());
        Cookie cookie = new Cookie("jwt",token);
        cookie.setHttpOnly(true);
        cookie.setSecure(true);
        res.addCookie(cookie);
    }
}
Problem is, whatever I'm doing, the runtime doesn't seems to enter in this filter at all. What am I missing? I guess is something big and stupid but I can't figure it out...
UPDATE: the problem seems to be that the UsernamePassWordAuthenticationFilter can be called only through a form. I then change my code to extend AbstractAuthenticationProcessingFilter instead.
The modified filter:
public class JWTAuthenticationFilter extends
        AbstractAuthenticationProcessingFilter {
    private AuthenticationManager authenticationManager;
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super("/api/secureLogin");
        this.authenticationManager = authenticationManager;
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException{
        String requestBody;
        try{
            requestBody = IOUtils.toString(req.getReader());
            JsonParser jsonParser = JsonParserFactory.getJsonParser();
            Map<String, Object> requestMap = jsonParser.parseMap(requestBody);
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(requestMap.get("email"), requestMap.get("password"), new ArrayList<>()); 
            return authenticationManager.authenticate(token);
        }catch(IOException e){
            throw new InternalAuthenticationServiceException("Something goes wrong parsing the request body",e );
        }
    }
}
and the modified configure method:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().authorizeRequests()
        .antMatchers(HttpMethod.POST, "/api/secureLogin").permitAll()
        .antMatchers(HttpMethod.GET, "/api").permitAll()
        .antMatchers("/api/**").authenticated()
        .and()
        .addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
You expect the filter to be triggered by accessing the path api/secureLogin. By default UsernamePasswordAuthenticationFilter is triggered only by accessing /login.
If you add following line in the constructor of the JWTAuthenticationFilter which extends UsernamePasswordAuthenticationFilter it should work:
  this.setFilterProcessesUrl("/api/secureLogin");
Hi @Mikyjpeg
You can use UsernamePassWordAuthenticationFilter. It does not have to be called from a form as you mentioned.
As long as it is called via a POST method with the url /login (instead of your api/secureLogin url), it will be executed.
The constructor uses a request matcher that only allows this url & request method:
public UsernamePasswordAuthenticationFilter() {
  super(new AntPathRequestMatcher("/login", "POST"));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With