Я пытаюсь включить безопасность Spring в проекте служб Spring Boot Rest, и у меня возникают некоторые проблемы.
Я настроил это с помощью этого кода
@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();
}
}
и реализовал настраиваемого поставщика аутентификации для входа в LDAP (который имеет нестандартную конфигурацию, поэтому я не смог заставить работать поставщика ldap по умолчанию)
@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
}
}
Этот код работает нормально в том смысле, что вызывая мои службы с базовым заголовком авторизации, он может правильно войти в систему и вернуть вызванную службу. Проблемы начались, когда я попытался вставить другую авторизацию / аутентификацию. Вместо использования базовой аутентификации я хотел бы передать учетные данные из формы в моем интерфейсе реагирования, поэтому я хотел бы передать их как тело json в вызове POST. (идея состоит в том, чтобы сгенерировать токен jwt и использовать его для следующего сообщения).
Поэтому я изменил метод настройки на этот:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
и определил настраиваемый фильтр аутентификации:
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);
}
}
Проблема в том, что что бы я ни делал, среда выполнения, похоже, вообще не попадает в этот фильтр. Что мне не хватает? Я предполагаю, что это что-то большое и глупое, но я не могу этого понять ...
Обновлено: проблема, похоже, в том, что UsernamePassWordAuthenticationFilter можно вызвать только через форму. Затем я изменяю свой код, чтобы вместо этого расширить AbstractAuthenticationProcessingFilter.
Модифицированный фильтр:
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 );
}
}
}
и модифицированный метод настройки:
@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);
}
У меня есть собственный контроллер (который в основном записывает строку) в / api / login и ведет себя так же. Я тоже попробовал / войти (на котором я ничего не определил), но это не меняется: фильтр не попадает.
Итак, я обнаружил, что на самом деле это вызывается только в том случае, если я использую форму входа по умолчанию (добавление .formLogin() в мой метод настройки). Тогда я, вероятно, мог бы определить настраиваемую форму, но, будучи приложением веб-службы (пользовательский интерфейс управляется в другом месте), я не хочу создавать здесь форму входа, поскольку я хотел бы использовать уже имеющийся у меня вызов отдыха ... если я расширяю другой класс тогда UsernamePasswordAuthenticationFilter? В этом случае есть ли класс, который я могу использовать для своего сценария, или мне следует расширить класс AbstractAuthenticationProcessingFilter?
Итак, здесь есть пара вещей. Во-первых, по умолчанию UsernamePasswordAuthenticationFilter # tryAuthentication вызывается только в / login или в том, что настроено как filterProcessesUrl - фильтр может отклонять запрос в методе requiresAuthentication. Во-вторых, да, если это вызов для отдыха, тогда расширение UsernamePasswordAuthenticationFilter немного странно. Если вы посмотрите на successAuthentication, вы увидите, что по умолчанию successHandler хочет перенаправить вызов, когда это будет сделано. Я бы, наверное, просто расширил OncePerRequestFilter.
Спасибо, я решил использовать AbstractAuthenticationProcessingFilter (см. Обновленную информацию в сообщении), но я посмотрю на упомянутый вами класс OncePerRequestFilter.
Я вижу, что вы добавляете конфигурацию, переопределяя метод настройки, попробуйте добавить сопоставление фильтра в свой web.xml. Что-то вроде этого в узле "веб-приложение":
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
<filter-name>JWTAuthenticationFilter</filter-name>
<filter-class>com.yourProject.JWTAuthenticationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>JWTAuthenticationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
У меня нет web.xml, так как я работаю с весенней загрузкой. В любом случае аннотация Configuration не нужна, и я, вероятно, оставил ее там по ошибке из предыдущей попытки, я собираюсь ее удалить.
Вы ожидаете, что фильтр будет активирован при доступе к пути api/secureLogin. По умолчанию UsernamePasswordAuthenticationFilter запускается только при доступе к /login.
Если вы добавите следующую строку в конструктор JWTAuthenticationFilter, который расширяет UsernamePasswordAuthenticationFilter, он должен работать:
this.setFilterProcessesUrl("/api/secureLogin");
Привет @Mikyjpeg
Вы можете использовать UsernamePassWordAuthenticationFilter. Его не нужно вызывать из формы, как вы упомянули.
Пока он вызывается с помощью метода POST с URL-адресом /login (вместо вашего URL-адреса api/secureLogin), он будет выполняться.
Конструктор использует сопоставление запросов, которое разрешает только этот метод URL и запроса:
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
Ответ ADAM сработал для меня. Те, кто работает с конфигурацией на основе аннотаций, могут добавить следующие
@Override protected Filter[] getServletFilters() {
return new Filter[]{
new JwtAuthenticationFilter()
};
}
В ваш AppInitialiser распространяется AbstractAnnotationConfigDispatcherServletInitializer
Пожалуйста, подтвердите, что вы попали в конечную точку «/ login», когда ожидаете, что этот фильтр сработает.