繁体   English   中英

使用 ReactJS 应用程序命中端点时,Keycloak 保护 Spring Boot 应用程序 CORS 错误

[英]Keycloak secured Spring Boot Application CORS error when hitting endpoint using ReactJS application

我有一个 ReactJS 和 Java Spring Boot 应用程序,它们都由 Keycloak 11.0.2 保护。

Keycloak 在端口 8083 上,ReactJS 在 3000 上,Spring App 在 8085 上。如果我尝试使用下面提供的配置,我将无法访问我的端点并且我收到 CORS 错误。

火狐:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/auth?response_type=code&client_id=event_sorcerer&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Fsso%2Flogin&state=f52216b1-c235-4328-a2f9-d8448c3bf886&login=true&scope=openid. (Reason: CORS request did not succeed).

Chrome 和 Microsoft Edge:

Access to XMLHttpRequest at 'http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/auth?response_type=code&client_id=event_sorcerer&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Fsso%2Flogin&state=f57ffa9f-9679-4476-aa03-af86c3abb3c2&login=true&scope=openid' (redirected from 'http://localhost:8085/api/worker/create/product') from origin 'http://localhost:3000' 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.


xhr.js:184 GET http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/auth?response_type=code&client_id=event_sorcerer&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2Fsso%2Flogin&state=f57ffa9f-9679-4476-aa03-af86c3abb3c2&login=true&scope=openid net::ERR_FAILED

当我尝试使用 Postman 访问我的端点时,我能够访问它。 下面是我的 Keycloak 网络安全配置。 配置使用 application.properties 文件来配置 Keycloak 适配器。 当我在配置中设置.authorizeRequests().antMatchers("/**").permitAll()时,我还可以从浏览器和 Postman 访问我的端点。

@KeycloakConfiguration
public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder authBuilder) throws Exception {
        final KeycloakAuthenticationProvider authProvider = keycloakAuthenticationProvider();
        
        authProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        authBuilder.authenticationProvider(authProvider);
    }
    
    
    /**
     * Call superclass configure method and set the Keycloak configuration
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        
        http
            .csrf().disable()
            .cors()
            .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and().anonymous()
            //.and().authorizeRequests().antMatchers("/**").permitAll()         //Uncomment for requests to be allowed!
            .and().authorizeRequests().antMatchers("/api/admin/**").hasRole("ADMIN")
            .and().authorizeRequests().antMatchers("/api/manager/**").hasAnyRole("MANAGER")
            .and().authorizeRequests().antMatchers("/api/worker/**").hasRole("WORKER")
            .anyRequest().authenticated();
    }

    /**
     * Setup Auth Strategy. Don't add prefixes and suffixes to role strings
     */
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }
    
    /**
     * Don't use keycloak.json. Instead, use application.yml properties.
     * @return
     */
    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }
}

这是设置 Keycloak 的 application.properties 的一部分:

spring:
  jersey:
    type: filter


security: 
    oauth2: 
      resourceserver: 
        jwt: 
          issuer-uri: http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/token
          jwk-set-uri: http://localhost:8083/auth/realms/sorcerer_realm/protocol/openid-connect/certs 


keycloak:
  realm: sorcerer_realm
  auth-server-url: http://localhost:8083/auth/
  ssl-required: external
  resource: event_sorcerer
  verify-token-audience: true
  credentials:
    secret-jwt:
      secret: d84611c9-af79-423b-b12c-bfa7fec23e85
  use-resource-role-mappings: true
  confidential-port: 0

这是我的 ReactJS 应用程序的 Keycloak 适配器设置:

const keycloakConfig = {
    "clientId": "event_sorcerer_frontend",
    "realm": "sorcerer_realm",
    "auth-server-url": "http://localhost:8083/auth/",
    "url": "http://localhost:8083/auth",
    "ssl-required": "external",
    "resource": "event_sorcerer",
    "public-client": true,
    "verify-token-audience": true,
    "use-resource-role-mappings": true,
    "confidential-port": 0
};

const keycloak = new Keycloak(keycloakConfig);

const initKeycloak = (onSuccessCallback, onFailureCallback) => {
    let success = false;

    timeoutWrapper(() => {
        if(!success){
            onFailureCallback();
        }
    });

    keycloak.init({
        onLoad: 'check-sso',
        silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
        pkceMethod: 'S256',

    }).then((isAuthenticated) => {
        success = true;
        if(isAuthenticated) {
            onSuccessCallback();
        } else {
            login();
        }
    });
}

这是我将请求发送到服务器的方式:

export const Request = {
    configureAxiosDefault: () => {
        axios.defaults.baseURL = axiosDefaultConfiguration.baseUrl;
    },

    create: (data, endpoint, callback, errorCallback, finalCallback) => {
        axios.post(serverEndpoint + endpoint, {
            data: data,
            headers: {
                Authorization: `Bearer ${UserService.getToken()}`
            }
        })
        .then(response => Utility.isEmpty(callback) ?  defaultCallback(response) : callback(response))
        .catch(response => Utility.isEmpty(errorCallback) ? defaultErrorCallback(response) : errorCallback(response))
        .finally(response => {
            if(!Utility.isEmpty(finalCallback)) {
                finalCallback(response);
            }
        });
    },
}

这是我的前端 Keycloak 配置。 后端是相同的,除了访问类型是机密的并且根/基本 url 不同(不是 3000 而是 8085):

前端

这是我的 CORS 配置 bean:

@Configuration
public class CORSConfiguration {
    /**
     * Setup CORS
     * @return
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        
        config.setAllowCredentials(true);
        config.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
        config.setAllowedMethods(Arrays.asList(CorsConfiguration.ALL));
        config.setAllowedHeaders(Arrays.asList(CorsConfiguration.ALL));
        config.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", config);
        
        return source;
    }
}

最后,这是我的终点。 URL 解析为api/worker/create/product

@RestController
@RequestMapping(ControllerEndpointsPrefix.WORKER + "/create")
public class CreationController {
    @Autowired
    private UserAgregate userAgregate;

    @PostMapping("/product")
    public boolean createProduct(@RequestBody CreateProductCommand command) {
        return true;
    }
}

我已经设法解决了这个问题。 问题不在服务器端,而是在客户端。

configureAxiosDefault: () => {
    axios.defaults.baseURL = axiosDefaultConfiguration.baseUrl;
    axios.defaults.headers.Authorization = `Bearer ${UserService.getToken()}`
},

create: (data, endpoint, callback, errorCallback, finalCallback) => {
        axios.post(serverEndpoint + endpoint, data)
        .then(response => Utility.isEmpty(callback) ?  defaultCallback(response) : callback(response))
        .catch(response => Utility.isEmpty(errorCallback) ? defaultErrorCallback(response) : errorCallback(response))
        .finally(response => {
            if(!Utility.isEmpty(finalCallback)) {
                finalCallback(response);
            }
        });
    },

服务器无法处理令牌,因为我将它作为 JSON 对象属性发送。 这些更改使一切正常。

因此,CORS 根本不是问题。 问题是请求不包含授权标头。

有很多关于 KeyCloak 的 StackOverflow 问题,其中一些是不完整和神秘的。 由于 OpenJDK、JDK 版本等原因,我遇到了大量错误。如果有人需要解释和解决方案,可以在我的存储库中找到有效的 Spring Boot 配置: https://github.com/milosrs/EventSorcererBackend : https://github.com/milosrs/EventSorcererBackend

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM