Я использую JWT в своем приложении Spring Boot. Когда я пытаюсь войти в систему из клиента Angular 6, я получаю ошибку CORS
Access to XMLHttpRequest at 'http://localhost:8082/login' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Я попытался добавить заголовки для "Access-Control-Allow-Origin, я даже попытался использовать некоторые расширения Chrome, но все равно не смог обойти CORS. Я могу получить доступ к API входа в систему с помощью Postman и получить токен.
Весенние загрузочные классы
WebSecurityConfig.java
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurityConfig(@Qualifier("customUserDetailsService") UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
WebConfig.java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping( "/**" )
.allowedOrigins( "http://localhost:4200" )
.allowedMethods( "GET", "POST", "DELETE" )
.allowedHeaders( "*" )
.allowCredentials( true )
.exposedHeaders( "Authorization" )
.maxAge( 3600 );
}
}
JWTAuthorization.java класс, предоставляющий доступ пользователю
@Order(Ordered.HIGHEST_PRECEDENCE)
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader(HEADER_STRING);
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization");
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authenticationToken = getAuthenticationToken(request);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken getAuthenticationToken(HttpServletRequest request){
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
System.out.println(user);
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}
JWTAuthenticationFilter.java класс, который обрабатывает запрос на вход и возвращает токен
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
User user = new ObjectMapper().readValue(request.getInputStream(),User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
user.getUsername(),
user.getPassword())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String username = ((org.springframework.security.core.userdetails.User) authResult.getPrincipal()).getUsername();
String token = Jwts
.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
System.out.println("TOKEN: " + token);
String bearerToken = TOKEN_PREFIX + token;
response.getWriter().write(bearerToken);
response.addHeader(HEADER_STRING, bearerToken);
}
}
Пример почтальона, который работает
Вот как я делаю почтовый запрос на вход, который выдает ошибку
@Injectable({
providedIn: 'root'
})
export class AuthenticationService {
public apiURL:string = "http://localhost:8082";
constructor(private httpClient:HttpClient) { }
validateUser(user:User){
let userData = "username=love"+ "&password=12345" + "&grant_type=password";
let reqHeader = new HttpHeaders({ 'Content-Type': 'application/json' });
const data = new FormData();
data.append("username", user.username);
data.append("password", user.password);
console.info(data);
return this.httpClient.post<User>(this.apiURL + '/login',data,{headers:reqHeader});
}
storeToken(token: string) {
localStorage.setItem("token", token);
}
getToken() {
return localStorage.getItem("token");
}
removeToken() {
return localStorage.removeItem("token");
}
}
Также интерфейс User в Angular
export interface User {
username:string;
password:string;
}




Поскольку сообщение касается вашего запроса предполетный, то есть запроса OPTIONS,
Я думаю, вам нужно сделать две вещи на стороне сервера / в коде загрузки Spring,
attemptAuthentication в качестве первой проверки, т.е. не выполнять настоящую аутентификацию для предполетных запросов,if (CorsUtils.isPreFlightRequest(httpServletRequest)) {
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
return new Authentication() ; //whatever your token implementation class is - return an instance of it
}
CorsUtils - это org.springframework.web.cors.CorsUtils
.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
Вы также можете разрешить неавторизованные запросы OPTIONS, но я думаю, это не лучшая идея. Кроме того, по возможности постарайтесь сузить "/ **" до определенных URL-адресов.
в качестве первого шага в этом методе, либо внутри try в первую очередь, либо перед try, если вы делаете это в первую очередь.
Это не совсем те ответы, которые вы предоставили, но это дало мне идею, основанную на другом вопросе, ссылку для всех, кто интересуется будущим stackoverflow.com/questions/46410682/…
Я имею в виду, что эта настройка работает для меня с Angular5, а также ... У меня есть настройка, о которой говорится в другом ответе, но когда я посмотрел на ваш вопрос, я подумал, что у вас уже есть эта часть. В любом случае проблема решена, это хорошо.
есть ли другой способ, кроме allowAll HttpMethod.OPTIONS? Мне это кажется защитной нитью.
Есть еще одно хорошее решение здесь (уже опубликованное ниже) для решения проблемы Spring CORS. Добавьте http.cors(); в конфигурацию безопасности.
У меня была аналогичная ошибка, и проблема заключалась в названии пакетов ... Попробуйте переименовать свои пакеты в этой модели: groupId.artifactId.security.jwt
У меня была та же проблема, наконец, я только добавил httpRequest.cors(); в конце метода настройки в классе WebSecurityConfig, и он работал хорошо, объяснение почему:
явно предполетные запросы не исключаются из авторизации в конфигурации Spring Security. Помните, что Spring Security по умолчанию защищает все конечные точки.
В результате API также ожидает токен авторизации в запросе OPTIONS.
Spring предоставляет готовое решение для исключения запросов OPTIONS из проверок авторизации:
Метод cors() добавит предоставленный Spring CorsFilter в контекст приложения, который, в свою очередь, обходит проверки авторизации для запросов OPTIONS.
для всех деталейhttps://www.baeldung.com/spring-security-cors-preflight
1) Создайте bean-компонент, возвращающий CorsFilter
Я сделал это в классе SecurityConfig, который расширяет WebSecurityConfigurerAdapter
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenProvider jwtTokenProvider;
@Autowired
public SecurityConfig(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
public void configure(HttpSecurity http) throws Exception {
//....
http.cors();
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("PUT");
config.addAllowedMethod("DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
2) Предоставьте этот CorsFilter классу, расширяющему SecurityConfigurerAdapter
public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final JwtTokenProvider jwtTokenProvider;
public JwtConfigurer(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
JwtTokenFilter jwtTokenFilter = new JwtTokenFilter(jwtTokenProvider);
httpSecurity.addFilterBefore(
new SecurityConfig(jwtTokenProvider).corsFilter(),
UsernamePasswordAuthenticationFilter.class);
httpSecurity.addFilterBefore(
jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
3) Добавьте метод configure(HttpSecurity http) { /* ... */ http.cors(); } в класс SecurityConfig
Показано в первом фрагменте кода выше.
Я также столкнулся с той же проблемой. Когда я пытался от почтальона, он работал нормально, но тот же вызов, сделанный из приложения реакции, я столкнулся с проблемой.
Обычно мы отключаем cors в конфигурации при работе с пружинной защитой. Но здесь вы должны это позволить. Проверьте приведенный ниже фрагмент кода.
Просто добавил http.cors (); в код ниже
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtFilter jwtFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/secure/authenticate")
.permitAll().anyRequest().authenticated()
.and().exceptionHandling().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);;
http.cors();
}
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Я не уверен, где разместить первое утверждение, вы сказали под
attemptAuthentication. Вы имели в виду «внутри» или «до пробуждения»?