【问题标题】:Spring Boot JWT CORS with Angular 6带有 Angular 6 的 Spring Boot JWT CORS
【发布时间】:2019-05-05 10:13:28
【问题描述】:

我在我的 Spring Boot 应用程序中使用 JWT。当我尝试从 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。我可以使用 Postman 访问登录 API 并获取令牌。

Spring Boot 类

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处理登录请求并返回token的类

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.log(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");
  }
}

还有 Angular 中的 User 接口

export interface User {
  username:string;
  password:string;
}

【问题讨论】:

    标签: java angular spring-boot cors jwt


    【解决方案1】:

    由于消息是关于您的 preflight 请求,即OPTIONS 请求,

    我猜,你需要在服务器端/Spring Boot 代码上做两件事,

    1. 从身份验证过滤器返回 OK,因此需要在 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

    1. 让 Spring Security 将 Authorized Options 请求输入系统,因此在 Security Config 中添加这些行,

    .authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

    您也可以允许未经授权的 OPTIONS 请求,但我想这不是一个好主意。此外,如果可能,请尝试将“/**”缩小到特定的 URL。

    【讨论】:

    • 我不知道第一句话应该放在哪里,你在attemptAuthentication下面说。您是说在 try-catch 内部还是之前?
    • 作为该方法中的第一件事,无论是在 try 内作为第一件事,还是在 try 之前,只要它是你做的第一件事。
    • 这不是你提供的答案,但它给了我一个基于另一个问题的想法,链接给任何对未来感兴趣的人stackoverflow.com/questions/46410682/…
    • 我的意思是,这个设置适用于 Angular5 以及 ..我已经设置了其他答案在说什么,但是当我查看你的问题时,我认为你已经有了那部分。无论如何,问题已经解决,很高兴知道。
    • 除了 permitAll HttpMethod.OPTIONS 还有其他方法吗?在我看来,它就像一个安全线程。
    【解决方案2】:

    1) 创建一个返回CorsFilter的bean

    我是在 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) 在SecurityConfig类中添加方法configure(HttpSecurity http) { /* ... */ http.cors(); }

    如上面第一个代码sn-p所示。

    【讨论】:

      【解决方案3】:

      我遇到了同样的问题,最后我只在WebSecurityConfig类的configure方法末尾添加了httpRequest.cors();,效果很好,解释为什么是

      明确地,预检请求不会从 Spring Security 配置中的授权中排除。请记住,Spring Security 默认保护所有端点。

      因此,API 在 OPTIONS 请求中也需要一个授权令牌。

      Spring 提供了一个开箱即用的解决方案,可以将 OPTIONS 请求排除在授权检查之外:

      cors()方法会将 Spring 提供的 CorsFilter 添加到应用程序上下文中,从而绕过 OPTIONS 请求的授权检查。

      了解所有详情 https://www.baeldung.com/spring-security-cors-preflight

      【讨论】:

        【解决方案4】:

        我遇到了类似的错误,问题是包的名称...尝试在此模型中重命名您的包:groupId.artifactId.security.jwt

        【讨论】:

          【解决方案5】:

          我也遇到了同样的问题。当我从邮递员那里尝试时,它工作正常,但从 react 应用程序发出的相同调用我遇到了问题。

          通常我们在使用 spring security 时禁用配置中的 cors。 但在这里你应该允许它。检查下面的代码sn-p。

          刚刚在下面的代码中添加了 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();
              }
              
              
          }
          

          【讨论】:

            猜你喜欢
            • 2020-11-19
            • 2018-03-06
            • 2018-01-29
            • 2019-01-27
            • 2017-09-13
            • 2020-02-07
            • 2021-05-22
            • 2017-09-09
            相关资源
            最近更新 更多