Piotr Olaszewski
Piotr Olaszewski

Reputation: 6204

How to call controller method after authentication

I have following metod in controller:

@PostMapping(path = "/api/users/login", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
@ResponseStatus(OK)
public TokenResponse login(@RequestBody LoginUserRequest loginUserRequest, Principal principal) {
    return new TokenResponse().setAccessToken("token");
}

here is a WebSecurityConfigurerAdapter

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .formLogin().disable()
            .authorizeRequests().antMatchers("/api/users/login").permitAll()
            .and()
            .authorizeRequests().antMatchers("/api/**").authenticated()
            .and()
            .addFilterBefore(mobileAuthenticationFilter(objectMapper), UsernamePasswordAuthenticationFilter.class)
            .addFilter(new JwtAuthorizationFilter(authenticationManager(), super.userDetailsService()));
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);

    auth.jdbcAuthentication().dataSource(dataSource)
            .usersByUsernameQuery("SELECT login, pass, active FROM users WHERE login = ?")
            .authoritiesByUsernameQuery("SELECT login, 'ROLE_USER' FROM users WHERE login = ?")
            .passwordEncoder(new CustomPasswordEncoder());
}

@Bean
public MobileAuthenticationFilter mobileAuthenticationFilter(ObjectMapper objectMapper) throws Exception {
    MobileAuthenticationFilter mobileAuthenticationFilter = new MobileAuthenticationFilter(objectMapper);
    mobileAuthenticationFilter.setAuthenticationManager(authenticationManager());
    mobileAuthenticationFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
        System.out.println(request);
    });
    return mobileAuthenticationFilter;
}

MobileAuthenticationFilter is reading from json body and prepare UsernamePasswordAuthenticationToken

public class MobileAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private final ObjectMapper objectMapper;

    public MobileAuthenticationFilter(ObjectMapper objectMapper) {
        super(new AntPathRequestMatcher("/api/users/login"));
        this.objectMapper = objectMapper;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            BufferedReader reader = request.getReader();
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            reader.mark(0);
            LoginUserRequest loginUserRequest = objectMapper.readValue(sb.toString(), LoginUserRequest.class);
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginUserRequest.getLogin(), loginUserRequest.getPassword());
            return getAuthenticationManager().authenticate(token);
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getMessage());
        }
    }
}

this code works fine but is one thing which I want to archive.

After successfully authentication, response is produced immediately by the:

mobileAuthenticationFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
    System.out.println(request);
});

Here ofcourse I can return something to client (in body), but there is any possibility to invoke controller method public TokenResponse login and that method should return a response (based on method contract and annotations for http code)?

This method in controller in that scenario is never called.

Upvotes: 0

Views: 813

Answers (1)

Aman
Aman

Reputation: 1744

Would there be a formLogin, you could have used the successHandler(...) to redirect to the page you want. Note that you have to also think about error responses.

Since you have explicitly disabled formLogin, I recommend if users call /api/users/login instead of authenticating them in attemptAuthentication(...).

So, as you have put it ..addFilterBefore(mobileAuthenticationFilter(objectMapper), UsernamePasswordAuthenticationFilter.class), your filter will be triggered populating the resulting response.

Your controller will look like something like this:

public TokenResponse login(@Valid @RequestBody LoginUserRequest loginUserRequest) {
   //may be check for AuthenticationException
   try{
       ...
       generateToken(loginUserRequest.getUserName(), loginUserRequest.getPassword());
       ...
   } catch(AuthenticationException ex){
        // status = HttpStatus.UNAUTHORIZED;
   } catch (Exception ex) {
        //status = HttpStatus.INTERNAL_SERVER_ERROR;
   }
}
public String generateToken(String name, String password) {
        try {
            // check for null or empty
            
            UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(name, password, new ArrayList<>());
            Authentication authentication = authenticationManager.authenticate(upToken);
            
            // do whatever operations you need and return token 
        } catch (Exception ex) {
            throw ex;
        }
    }

Upvotes: 1

Related Questions