[英]Keycloak secured Spring Boot Application CORS error when hitting endpoint using ReactJS application
I have a ReactJS and Java Spring Boot applications, both secured by Keycloak 11.0.2.我有一个 ReactJS 和 Java Spring Boot 应用程序,它们都由 Keycloak 11.0.2 保护。
Keycloak is on port 8083, ReactJS on 3000 and Spring App is on 8085. If I try to use the configuration provided below, I'm not able to hit my endpoint and I'm getting CORS error. Keycloak 在端口 8083 上,ReactJS 在 3000 上,Spring App 在 8085 上。如果我尝试使用下面提供的配置,我将无法访问我的端点并且我收到 CORS 错误。
Firefox:火狐:
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 and Microsoft Edge: 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
When I try to hit my endpoint using Postman, I'm able to hit it.当我尝试使用 Postman 访问我的端点时,我能够访问它。 Below is my Keycloak Web Security configuration.
下面是我的 Keycloak 网络安全配置。 The configuration uses application.properties file to configure Keycloak adapter.
配置使用 application.properties 文件来配置 Keycloak 适配器。 When I set
.authorizeRequests().antMatchers("/**").permitAll()
in the config, I'm also able to hit my endpoint from browser and Postman.当我在配置中设置
.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();
}
}
Here is a part of application.properties that sets up Keycloak:这是设置 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
Here is my ReactJS application's Keycloak adapter setup:这是我的 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();
}
});
}
Here is how I send the request to server:这是我将请求发送到服务器的方式:
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);
}
});
},
}
Here is my Keycloak configuration for frontend.这是我的前端 Keycloak 配置。 Backend is the same, except the Access Type is confidential and the Root/Base url are different (not 3000 but 8085):
后端是相同的,除了访问类型是机密的并且根/基本 url 不同(不是 3000 而是 8085):
Here is my CORS configuration bean:这是我的 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;
}
}
And lastly, here is my endpoint.最后,这是我的终点。 URL resolves to
api/worker/create/product
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;
}
}
I've managed to solve this.我已经设法解决了这个问题。 The problem wasn't on the server side, but on client side.
问题不在服务器端,而是在客户端。
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);
}
});
},
Server was unable to process the token, because I was sending it as a JSON object property.服务器无法处理令牌,因为我将它作为 JSON 对象属性发送。 These changes made everything work OK.
这些更改使一切正常。
So, CORS wasn't an issue at all.因此,CORS 根本不是问题。 The issue was that request didn't contain an Authorization header.
问题是请求不包含授权标头。
There are a lot of StackOverflow questions regarding KeyCloak, and some of them incomplete and cryptic.有很多关于 KeyCloak 的 StackOverflow 问题,其中一些是不完整和神秘的。 I encountered a good amount of errors, because of OpenJDK, JDK versions etc. If anyone needs explanations and solutions, working Spring Boot configuration is on my repository:
https://github.com/milosrs/EventSorcererBackend
由于 OpenJDK、JDK 版本等原因,我遇到了大量错误。如果有人需要解释和解决方案,可以在我的存储库中找到有效的 Spring Boot 配置:
https://github.com/milosrs/EventSorcererBackend
: https://github.com/milosrs/EventSorcererBackend
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.