CORS 问题 - 使用 Keycloak 保护 ReactJS 前端和 JAX-RS 后端

Posted

技术标签:

【中文标题】CORS 问题 - 使用 Keycloak 保护 ReactJS 前端和 JAX-RS 后端【英文标题】:CORS Problem - Protecting ReactJS Frontend and JAX-RS Backend with Keycloak 【发布时间】:2021-11-14 03:39:50 【问题描述】:

我是一名来自德国的初级软件开发人员,我目前正在尝试从 Jax-RS Rest API(部署在 Wildfly 服务器上)设置一些 Web 服务,这些服务受“Keycloak”保护,可通过简单的 react 应用程序访问。

我已经完成了这篇文章中的所有确切步骤:https://medium.com/devops-dudes/secure-front-end-react-js-and-back-end-node-js-express-rest-api-with-keycloak-daf159f0a94e。

唯一的区别如下:

我有三项服务:“零级”、“一级”和“二级”。 每个服务都只返回一个字符串(例如:“This is the Service Level One”) 我在 Keycloak 中定义了两个角色:“一级用户”和“二级用户” 二级用户有权访问所有服务 一级用户只能被授权访问一级和零级服务 其他所有用户只能访问零级服务

在我的 React 应用程序中,我有三个按钮,它们将通过框架“axios”访问服务。您单击一个按钮,如果您被授权,返回的字符串将在控制台中注销。

我的问题: 如果我运行我的应用程序,当我尝试以经过身份验证的一级或二级用户身份访问“LevelOne”或“LevelTwo”时,我的 Web 控制台中总是会出现 CORS 错误。不受 Keycloak 保护的零级服务不存在此问题。

我的(翻译后的)错误: 跨源请求被阻止:同源策略不允许对外部资源 URL_FROM_API_SERVICE 进行读取访问 - 原因:缺少 CORS 标头“Access-Control-Allow-Origin”!

我尝试了很多我在网上找到的东西 --> 我在我的 Rest API 中创建了一个 CORS 过滤器,我尝试在我的 keycloak.json 中放入“enable-cors:true”,我在我的 Keycloak 客户端配置中的“web-origins”字段。但没有任何效果。 :(

我做错了什么?我真的需要你的帮助!我对这一切都很陌生,非常感谢一些支持。

我的 Keycloak 配置与文章中显示的相同,只是名称不同。

在 keycloak.json 中添加“enable-cors:true”并将“web-origin”设置为 Keycloak 管理控制台上的正确来源也无济于事:(

API 和 React App 目前在 HTTP 上运行,而 Keycloak 在另一台具有自签名证书的机器上在 HTTPS 上运行。

这是我所有的代码:

React 应用中的我的 App.js:

import './App.css';
import Secured from './components/Secured.js'
import axios from 'axios';

var axiosInstance = axios.create(
    baseURL: 'http://MY_BASE_URL/login-restapi/api/'
);

axiosInstance.interceptors.request.use(
    config => 
      const token = window.accessToken ? window.accessToken : 'dummy_token';
      config.headers['Authorization'] = 'Bearer' + token;
      return config;
    ,
    error => 
      Promise.reject(error)
    );

axiosInstance.interceptors.response.use((response) => 
  return response
, function (error) 
  return Promise.reject(error);
);

function App() 

  return (
    <div className="App">
      <Secured></Secured>
        <button onClick=() => 
            axiosInstance.get('/levelZero').then(res => 
                console.log(res.data)
            )
        
        >LevelZero</button>
        <button onClick=() => 
            axiosInstance.get('/levelOne').then(res => 
                console.log(res.data)

            )
        
        >LevelOne</button>
        <button onClick=() => 
            axiosInstance.get('/levelTwo').then(res => 
                console.log(res.data)
            )
        
        >LevelTwo</button>
    </div>
  );


export default App;

我在 React 应用中的 Secured.js:

import React,  Component  from 'react';
import Keycloak from 'keycloak-js';


class Secured extends Component 

    constructor(props) 
        super(props);
        this.state =  keycloak: null, authenticated: false ;
    



    componentDidMount() 
        const keycloak = Keycloak('/keycloak.json');
        keycloak.init( onLoad: 'login-required' ).then(authenticated => 
            this.setState( keycloak: keycloak, authenticated: true)
            if (authenticated) 
                window.accessToken = keycloak.token;
            
        )
    

    render()
        if (this.state.keycloak) 
            if(this.state.authenticated) return (
                <div>
                    <p>You are now logged in :)</p>
                </div>
            ); else return (<div>Unable to authenticate!</div>)
        
        return (
            <div>Initializing Keycloak...</div>
        );
    


export default Secured;

React 应用中的我的 Keycloak.json:


  "realm": "(MY_REALM_NAME)",
  "auth-server-url": "MY_AUTHSERVER_URL",
  "ssl-required": "none",
  "resource": "react-web-app",
  "public-client": true,
  "verify-token-audience": true,
  "use-resource-role-mappings": true,
  "confidential-port": 0

我的零级服务:

@Path("/levelZero")
public class LevelZeroResource 
    @GET
    @Produces("text/plain")
    public String levelZero() 
        return "Everybody can access this.";
    

我的一级服务:

@Path("/levelOne")
public class LevelOneResource 
    @GET
    @Produces("text/plain")
    public String levelOne() 
        return "You need to be at least a Level One User to access this.";
    

我的二级服务:

@Path("/levelTwo")
public class LevelTwoResource 
    @GET
    @Produces("text/plain")
    public String levelTwo() 
        return "You need to be a LevelTwo-user to access this.";
    

我的来自 Rest API 的 CORS 过滤器:

@Provider
 public class CORSFilter implements ContainerResponseFilter 

    @Override
    public void filter(final ContainerRequestContext requestContext,
                       final ContainerResponseContext cres) throws IOException 
        cres.getHeaders().add("Access-Control-Allow-Origin", "http://URL_FROM_REACT_APP");
        cres.getHeaders().add("Access-Control-Allow-Headers", "*");
        cres.getHeaders().add("Access-Control-Allow-Credentials", "*");
        cres.getHeaders().add("Access-Control-Allow-Methods", "*");
        cres.getHeaders().add("Access-Control-Max-Age", "1209600");
    


我的来自 Rest API 的 Keycloak.json:


  "realm": "MY_REALM_NAME",
  "bearer-only": true,
  "enable-cors": true,
  "auth-server-url": "https://MY_AUTH_SERVER_URL",
  "ssl-required": "none",
  "resource": "login-restapi",
  "verify-token-audience": true,
  "use-resource-role-mappings": true,
  "confidential-port": 0

我的来自 Rest API 的 web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <module-name>login-restapi</module-name>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>LevelOneResource</web-resource-name>
            <url-pattern>/api/levelOne</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>levelOneRole</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>LevelTwoResource</web-resource-name>
            <url-pattern>/api/levelTwo</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>levelTwoRole</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <login-config>
        <auth-method>KEYCLOAK</auth-method>
        <realm-name>MY_REALM_NAME</realm-name>
    </login-config>

    <security-role>
        <role-name>levelOneRole</role-name>
    </security-role>
    <security-role>
        <role-name>levelTwoRole</role-name>
    </security-role>

</web-app>

【问题讨论】:

【参考方案1】:

您不需要添加自定义过滤器。 只需将enable-cors 设置为true(就像您所做的那样)并确保在Keycloak 中正确配置了Web origins。您需要将 React 前端的来源添加到 Keycloak 客户端配置中的 Web origins 列表中。

Keycloak 然后会将这些来源编码为您的访问令牌内的有效来源。 JAX-RS 应用程序中的 Keycloak 适配器将解释它们并根据需要返回 Access-Control-Allow-Origin 标头。

【讨论】:

感谢您的回复!不幸的是,这并没有帮助:(到目前为止没有任何改变。另外:React App 和 API 目前使用 HTTP 运行,而 Keycloak 服务器使用自签名证书在 HTTPS 上运行 --> 是否有潜在的问题有可能吗?在实际的 CORS 错误之前,我还会收到另一个错误:“401 Unauthorized, Referrer Policy: strict origin when cross origin”即使授权令牌已分配给请求并且用户具有正确的角色...跨度> 【参考方案2】:

我看到你已经在你的 keycloak 配置文件 "enable-cors: true" 中启用了 cors,所以请确保 keycloak 上的 web 来源字段(如 @sventorben 所示)也有后端 url。

因为即使keycloak-js 对用户进行身份验证,后端也会进行授权,这意味着也与 keycloak 对话。

【讨论】:

我现在试过了,但仍然没有任何改变:( 您在 login-restapi 或 react-web-app 中进行了更改? 显然我只能更改 react-web-app,只要 login-restapi 的访问类型是“仅承载”-> 客户端配置中不存在 web origins 字段然后. 你必须删除 enable-cors 然后 你的意思是在 react-web-app 或 login-rest-api 的 keycloak.json 中?

以上是关于CORS 问题 - 使用 Keycloak 保护 ReactJS 前端和 JAX-RS 后端的主要内容,如果未能解决你的问题,请参考以下文章

注销重定向时出现 Keycloak CORS 问题

keycloak CORS 过滤器弹簧靴

与登录重定向相关的 Keycloak CORS 问题

使用 Keycloak 时的静默身份验证 CORS 问题

从 keycloak 刷新访问令牌时出现 CORS 错误

Keycloak-angular:访问/帐户失败,出现 403 错误和 CORS 消息