java 不存在“访问控制允许来源”标头(CORS)- Sping Boot (Spring安全性)微服务+ Vue.js

qnzebej0  于 2023-01-24  发布在  Java
关注(0)|答案(3)|浏览(115)

我正在进行Spring Boot项目,该项目基于后端的微服务架构和前端的Vue.js。我的项目结构如下:

为了避免CORS错误,我通常会在类中添加@CrossOrigin注解,它工作正常。这一切都很好,一直工作得很好,直到我添加了安全部分,能够登录用户。

我做了什么:

    • 1.**对于构建在spring-cloud-gateway上的API Gateway,我添加了AuthFilter,它用作拦截器来创建和检查JWT:

api-gateway/src/main/java/.../AuthFilter.java

@Component
public class AuthFilter extends AbstractGatewayFilterFactory<AuthFilter.Config> {
    private final WebClient.Builder webClientBuilder;

    @Autowired
    public AuthFilter(WebClient.Builder webClientBuilder) {
        super(Config.class);
        this.webClientBuilder = webClientBuilder;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            if(!exchange.getRequest().getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
                throw new RuntimeException("Missing auth information");
            }

            String authHeader = exchange.getRequest().getHeaders().get(org.springframework.http.HttpHeaders.AUTHORIZATION).get(0);
            String[] parts = authHeader.split(" ");

            if(parts.length != 2 || !"Bearer".equals(parts[0])) {
                throw new RuntimeException("Incorrect auth structure");
            }

            return webClientBuilder.build()
                    .post()
                    .uri("http://manager-service/api/v1/auth/validateToken?token=" + parts[1])
                    .retrieve()
                    .bodyToMono(EmployeeDTO.class) //EmployeeDTO.class is custom DTO that represents User
                    .map(user -> {
                        exchange.getRequest()
                                .mutate()
                                .header("x-auth-user-id", user.getId());
                        return exchange;
                    }).flatMap(chain::filter);

        };
    }

    public static class Config {
        //live it empty because we dont need any particular configuration
    }
}
    • 2.**我已经将AuthFilter作为过滤器添加到application.properties中的每个服务:

api-gateway/src/resource/application.properties

##Workshop service routes
spring.cloud.gateway.routes[0].id=workshop-service
spring.cloud.gateway.routes[0].uri=lb://workshop-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/api/v1/workshop/**
spring.cloud.gateway.routes[0].filters[0]=AuthFilter

##Manage service routes
spring.cloud.gateway.routes[1].id=manager-service
spring.cloud.gateway.routes[1].uri=lb://manager-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/api/v1/manage/**
spring.cloud.gateway.routes[1].filters[0]=AuthFilter

##Manage service for singIn. Here we dont need to add AuthFilter, cause sign in page should be available for all
spring.cloud.gateway.routes[2].id=manager-service-sign-in
spring.cloud.gateway.routes[2].uri=lb://manager-service
spring.cloud.gateway.routes[2].predicates[0]=Path=/api/v1/auth/signIn

...
    • 3.**用于控制系统基本实体(如用户、角色、用户工作所在的组织等)的管理器服务微服务,因此我在这里添加了SecurityConfigWebConfig,因为此微服务将负责JWT生成:

manager-service/src/main/java/.../SecurityConfig.java

@EnableWebSecurity
public class SecurityConfig  {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf().disable()
                .authorizeRequests().anyRequest().permitAll();
        return httpSecurity.build();
    }
   }

manager-service/src/main/java/.../WebConfig.java

@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    private static final Long MAX_AGE=3600L;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedHeaders(
                        HttpHeaders.AUTHORIZATION,
                        HttpHeaders.CONTENT_TYPE,
                        HttpHeaders.ACCEPT)
                .allowedMethods(
                        HttpMethod.GET.name(),
                        HttpMethod.POST.name(),
                        HttpMethod.PUT.name(),
                        HttpMethod.DELETE.name())
                .maxAge(MAX_AGE)
                .allowedOrigins("http://localhost:8100")
                .allowCredentials(false);
    }
}
    • 4.**在控制器中,代表auth,我还向类添加了@CrossOrigin注解:

manager-service/src/main/java/.../AuthController.java

@RestController
@RequestMapping("api/v1/auth")
@CrossOrigin(origins = "http://localhost:8100")
@Slf4j
public class AuthController {
    private final AuthService authService;

    @Autowired
    public AuthController(AuthService authService) {
        this.authService = authService;
    }

    @PostMapping("/signIn")
    public ResponseEntity<EmployeeDTO> signIn(@RequestBody CredentialsDTO credentialsDTO) {
        log.info("Trying to login {}", credentialsDTO.getLogin());

        return ResponseEntity.ok(EmployeeMapper.convertToDTO(authService.signIn(credentialsDTO)));
    }

    @PostMapping("/validateToken")
    public ResponseEntity<EmployeeDTO> validateToken(@RequestParam String token) {
        log.info("Trying to validate token {}", token);
        Employee validatedTokenUser = authService.validateToken(token);
        return ResponseEntity.ok(EmployeeMapper.convertToDTO(validatedTokenUser));
    }
}
    • 5.**对于前端,我使用Vue.js。对于请求,我使用axios。下面是post-请求登录:
axios.post('http://localhost:8080/api/v1/auth/signIn', this.credentials).then(response => {
              console.log('response = ', response)
              console.log('token from response', response.data.token)
              this.$store.commit('saveToken', response.data.token)
            }).catch(error => {
          console.log('Error is below')
          console.log(error)
        })

我得到的只是一个错误:Access to XMLHttpRequest at 'http://localhost:8080/api/v1/auth/signIn' from origin 'http://localhost:8100' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.。下面你会看到标题,显示Chrome浏览器请求:

我一直在尝试添加另一个corsConfiguration,尝试用CrossOrigin只标注方法,而不是类,但它没有任何效果。如果我尝试用postman发出这样的请求,它会给我预期的响应,并生成令牌。
如果你知道我做错了什么我会很感激的。
谢谢!

    • 更新:正如我所理解的-所有问题都在api-gateway中。如果我直接向服务发出请求-我得到了正确的响应,但如果我通过网关**发出请求-我面临着一个错误,api-gateway的日志如下:
2022-07-05 00:34:18.128 TRACE 8105 --- [or-http-epoll-5] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "[/api/v1/workshop/**]" does not match against value "/api/v1/auth/signIn"
2022-07-05 00:34:18.129 TRACE 8105 --- [or-http-epoll-5] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "[/api/v1/manage/**]" does not match against value "/api/v1/auth/signIn"
2022-07-05 00:34:18.129 TRACE 8105 --- [or-http-epoll-5] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "/api/v1/auth/signIn" matches against value "/api/v1/auth/signIn"
2022-07-05 00:34:18.129 DEBUG 8105 --- [or-http-epoll-5] o.s.c.g.h.RoutePredicateHandlerMapping   : Route matched: manager-service-sign-in
2022-07-05 00:34:18.129 DEBUG 8105 --- [or-http-epoll-5] o.s.c.g.h.RoutePredicateHandlerMapping   : Mapping [Exchange: OPTIONS http://localhost:8080/api/v1/auth/signIn] to Route{id='manager-service-sign-in', uri=lb://manager-service, order=0, predicate=Paths: [/api/v1/auth/signIn], match trailing slash: true, gatewayFilters=[], metadata={}}
2022-07-05 00:34:18.129 DEBUG 8105 --- [or-http-epoll-5] o.s.c.g.h.RoutePredicateHandlerMapping   : [e5b87280-8] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@78df1cfc
wswtfjt7

wswtfjt71#

经过研究我解决了问题都是盖特的错
正如我前面提到的,直接请求会给我正确的响应,但只有当我通过api-gateway时,它才会给我错误。

因此,解决方案是将CORS配置规则添加到网关:

spring:
  cloud:
    gateway:
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "http://localhost:8100"
            allowedHeaders: "*"
            allowedMethods: "*"

请注意,如果您不添加带有gateway: default-filters的节,您将面临包含多个值的标题的类似错误。
多亏了answer by Pablo Aragonés in another questionSpring Cloud Documentation

tcomlyy6

tcomlyy62#

把这个添加到网关中的applications.yml,为我解决了问题:

spring:
  cloud:
    gateway:
    default-filters:
       - DedupeResponseHeader=Access-Control-Allow-Origin Access-Control- 
       Allow-Credentials, RETAIN_UNIQUE
  globalcors:
    corsConfigurations:
      '[/**]':
        allowedOrigins: "http://localhost:<your port>"
        allowedHeaders: "*"
        allowedMethods: "*"
u3r8eeie

u3r8eeie3#

我也遇到过类似的问题,通过在我的配置中定义以下bean得到了解决。

@Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(Collections.singletonList("http://localhost:8100")); // Provide list of origins if you want multiple origins
        config.setAllowedHeaders(Arrays.asList("Origin", "Content-Type", "Accept"));
        config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH"));
        config.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

您可以注解掉所有其他细节,例如@CrossOrigin(origins = "http://localhost:8100"), WebConfig.java, SecurityConfig.java,因为一旦我们定义了上面的bean,就不需要这些东西了。
您的代码可能没有运行,因为您定义了bean、安全配置以及webconfig,这些配置在处理您的请求时可能会发生冲突。

相关问题