带有 Angular 6 的 Spring Boot JWT CORS

2022-01-10 00:00:00 cors jwt angular java spring-boot

我在我的 Spring Boot 应用程序中使用 JWT.当我尝试从 Angular 6 客户端登录时,出现 CORS 错误

I am using JWT in my Spring Boot app. When I try to login from the Angular 6 client, I get the CORS error

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 和获取令牌.

I tried adding headers for "Access-Control-Allow-Origin, I even tried using some chrome extensions and still it couldn't bypass the CORS. I can access the login API with Postman and get the token.

Spring Boot 类

Spring Boot Classes

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 授予用户访问权限的类

JWTAuthorization.java the class that gives access to user

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

JWTAuthenticationFilter.java the class that handles the login request and returns the 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);

    }
}

有效的邮递员示例

这是我发出登录请求的方式,但会出现错误

Here is how I make the post request to login that gives me the error

@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 界面

Also the User interface in Angular

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

推荐答案

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

Since message is about your preflight request i.e. OPTIONS request,

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

I guess, you need to do two things on server side / Spring Boot code ,

  1. 从身份验证过滤器返回 OK,因此需要在 attemptAuthentication 方法中添加以下内容作为第一个检查,即不对预检请求进行真正的身份验证,
  1. Return OK from Authentication filter so need to add below in attemptAuthentication method as first check i.e. don't do real authentication for preflight requests,

if (CorsUtils.isPreFlightRequest(httpServletRequest)) {httpServletResponse.setStatus(HttpServletResponse.SC_OK);返回新的身份验证();//无论你的令牌实现类是什么 - 返回它的一个实例
}

CorsUtils 是 - org.springframework.web.cors.CorsUtils

CorsUtils is - org.springframework.web.cors.CorsUtils

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

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

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

You can allow unauthorized OPTIONS requests too but I guess , that wouldn't be a good idea. Also, try to narrow down "/**" to specific URLs if possible.

相关文章