Как вызвать метод контроллера после аутентификации

#java #spring #spring-boot #spring-security

#java #весна #spring-boot #spring-безопасность

Вопрос:

У меня есть следующий метод в контроллере:

 @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");
}
 

вот 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 читает из тела json и подготавливает 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());
        }
    }
}
 

этот код работает нормально, но это одна вещь, которую я хочу заархивировать.

После успешной аутентификации ответ выдается немедленно:

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

Здесь, конечно, я могу вернуть что-то клиенту (в теле), но есть ли какая-либо возможность вызвать метод контроллера public TokenResponse login , и этот метод должен возвращать ответ (на основе контракта метода и аннотаций для http-кода)?

Этот метод в контроллере в этом сценарии никогда не вызывается.

Комментарии:

1. Почему клиент просто не отправляет запрос в /api/users/login для входа в систему? Если это произойдет, будет вызван метод контроллера.

2. @Cyril вы думаете о простом вызове контроллера без каких-либо фильтров, который аутентифицирует пользователя? И внутри этого метода контроллера будет выполняться обработка аутентификации (выдача ответа при успешном выполнении или выдача несанкционированного исключения)?

Ответ №1:

Был бы formLogin, вы могли бы использовать successHandler(...) для перенаправления на нужную страницу. Обратите внимание, что вы также должны подумать об ответах на ошибки.

Поскольку вы явно отключили formLogin, я рекомендую, чтобы пользователи вызывали /api/users/login вместо аутентификации attemptAuthentication(...) .

Итак, как вы выразились ..addFilterBefore(mobileAuthenticationFilter(objectMapper), UsernamePasswordAuthenticationFilter.class) , ваш фильтр будет запущен, заполняя результирующий ответ.

Ваш контроллер будет выглядеть примерно так:

 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;
        }
    }
 

Комментарии:

1. Спасибо за ответ! Я подумал о перемещении логики аутентификации в метод контроллера и отключении этого фильтра. Этот AuthenticationManager вводится в контроллер? И будет достаточно исключения?