#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 вводится в контроллер? И будет достаточно исключения?