在React前端中使用oidc-client.js和Identityserver4进行身份验证

Nic*_*las 14 authentication reactjs identityserver4

最近我尝试使用IdentityServer4和React客户端设置身份验证.我按照Adding a JavaScript clientIdentityServer文档的教程(部分)进行了操作:https://media.readthedocs.org/pdf/identityserver4/release/identityserver4.pdf也使用了该Quickstart7_JavaScriptClient文件.

缺点是我使用React作为我的前端,而我对React的了解并不足以实现使用React的教程中使用的相同功能.

尽管如此,我开始阅读并试图开始使用它.我的IdentityServer项目和API已设置好,并且似乎正常工作(也与其他客户端一起测试).

我首先将oidc-client.js添加到我的Visual Code项目中.接下来,我创建了一个在开始时呈现的页面(将其命名为Authentication.js),这是包含Login,Call API和Logout按钮的位置.此页面(Authentication.js)如下所示:

import React, { Component } from 'react';
import {login, logout, api, log} from '../../testoidc'
import {Route, Link} from 'react-router';

export default class Authentication extends Component {
    constructor(props) {
      super(props);
    }

    render() {
      return (
        <div>
            <div>  
                <button id="login" onClick={() => {login()}}>Login</button>
                <button id="api" onClick={() => {api()}}>Call API</button>
                <button id="logout" onClick={() => {logout()}}>Logout</button>

                <pre id="results"></pre>

            </div>  

            <div>
                <Route exact path="/callback" render={() => {window.location.href="callback.html"}} />

                {/* {<Route path='/callback' component={callback}>callback</Route>} */}
            </div>
        </div>
      );
    }
  }
Run Code Online (Sandbox Code Playgroud)

在testoidc.js文件中(上面导入了)我添加了所有使用的oidc函数(示例项目中的app.js).路由部分应该使callback.html可用,我已经保留了该文件(这可能是错误的).

testoidc.js文件包含以下函数:

import Oidc from 'oidc-client'


export function log() {
  document.getElementById('results').innerText = '';

  Array.prototype.forEach.call(arguments, function (msg) {
      if (msg instanceof Error) {
          msg = "Error: " + msg.message;
      }
      else if (typeof msg !== 'string') {
          msg = JSON.stringify(msg, null, 2);
      }
      document.getElementById('results').innerHTML += msg + '\r\n';
  });
}

var config = {
  authority: "http://localhost:5000",
  client_id: "js",
  redirect_uri: "http://localhost:3000/callback.html",
  response_type: "id_token token",
  scope:"openid profile api1",
  post_logout_redirect_uri : "http://localhost:3000/index.html",
};
var mgr = new Oidc.UserManager(config);

mgr.getUser().then(function (user) {
  if (user) {
      log("User logged in", user.profile);
  }
  else {
      log("User not logged in");
  }
});

export function login() {
  mgr.signinRedirect();
}

export function api() {
  mgr.getUser().then(function (user) {
      var url = "http://localhost:5001/identity";

      var xhr = new XMLHttpRequest();
      xhr.open("GET", url);
      xhr.onload = function () {
          log(xhr.status, JSON.parse(xhr.responseText));
      }
      xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
      xhr.send();
  });
}

export function logout() {
  mgr.signoutRedirect();
} 
Run Code Online (Sandbox Code Playgroud)

有很多事情出错了.当我单击登录按钮时,我被重定向到identityServer的登录页面(这很好).当我使用有效凭据登录时,我将被重定向到我的反应应用程序:http:// localhost:3000/callback.html#id_token =令牌

Identity项目中的此客户端定义如下:

new Client
                {
                    ClientId = "js",
                    ClientName = "JavaScript Client",
                    AllowedGrantTypes = GrantTypes.Implicit,
                    AllowAccessTokensViaBrowser = true,

                    // where to redirect to after login
                    RedirectUris = { "http://localhost:3000/callback.html" },

                    // where to redirect to after logout
                    PostLogoutRedirectUris = { "http://localhost:3000/index.html" },

                    AllowedCorsOrigins = { "http://localhost:3000" },
                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "api1"
                    }

                }
Run Code Online (Sandbox Code Playgroud)

虽然,似乎永远不会调用回调函数,但它只是留在回调网址上,背后有一个很长的令牌.

此外,getUser函数在登录后仍然显示"用户未登录",并且Call API按钮一直表示没有令牌.显然事情没有正常工作.我只是不知道它出了什么问题.检查时,我可以看到本地存储中生成了令牌:

在此输入图像描述

此外,当我单击注销按钮时,我会被重定向到身份主机的注销页面,但是当我单击注销时,我不会被重定向到我的客户端.

我的问题是:

  • 我是否在与IdentityServer4结合使用oidc-client的正确轨道上?
  • 我使用正确的库还是做出反应需要不同于oidc-client.js的库.
  • 是否有任何教程将react前端与IdentityServer4和oidc-client(没有redux)结合使用,我找不到任何教程.
  • 如何/在何处添加callback.html,是否应该重写?

有人能指出我正确的方向,这里很可能会出现更多错误,但目前我只是陷入了甚至开始的地方.

Gas*_*sim 7

IdentityServer4 只是 OIDC 的一个后端实现;因此,您需要做的就是使用给定的 API 在客户端中实现流程。我不知道 oidc-client.js 文件是什么,但它很可能会做与您可以自己实现的相同的事情。流程本身非常简单:

  1. React 应用程序准备请求并使用client_idredirect_uri(和状态,随机数)将用户重定向到身份验证服务器
  2. IdentityServer 检查client_id和 是否redirect_uri匹配。
    • 如果用户未登录,则显示登录框
    • 如果需要同意书(类似于您在某些应用程序中通过 Facebook/Google 登录时),请显示必要的交互
    • 如果用户经过身份验证和授权,则将页面重定向到redirect_uri具有新参数的 。在您的情况下,您的 URL 将如下所示:https://example.com/cb#access_token=...&id_token=...&stuff-like-nonce-and-state
  3. 现在,React 应用程序需要解析 URL,访问值,并将令牌存储在某个地方以供将来的请求使用:

实现逻辑的最简单方法是首先在路由器中设置一个路由,该路由解析为将执行逻辑的组件。该组件可以是“不可见的”。它甚至不需要渲染任何东西。你可以这样设置路由:

<Route path="/cb" component={AuthorizeCallback} />
Run Code Online (Sandbox Code Playgroud)

然后,在AuthorizeCallback组件中实现 OIDC 客户端逻辑。在组件中,您只需要解析 URL。您可以使用location.hash访问#access_token=...&id_token=...&stuff-like-nonce-and-state部分 URL。您可以使用URLSearchParams或第三方库,如qs。然后,只需将值存储在某个地方(sessionStorage、localStorage,如果可能,还有 cookie)。您所做的任何其他事情都只是实现细节。例如,在我的一个应用程序中,为了记住用户在应用程序中所在的活动页面,我将值存储在 sessionStorage 中,然后使用该存储中的值AuthorizeCallback将用户重定向到正确的页面。因此,身份验证服务器重定向到解析为的“/cb”AuthorizeCallback 并且此组件根据用户所在的位置重定向到所需的位置(如果未设置位置,则为“/”)。

另外,请记住,如果授权服务器的会话 cookie 未过期,则如果令牌已过期或被删除,您将不需要重新登录。如果令牌已过期,这很有用,但在您注销时可能会出现问题。这就是为什么当您注销时,您需要在从存储中删除令牌之前立即向授权服务器发送请求以删除/过期令牌。