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

Authentication with oidc-client.js and Identityserver4 in a React frontend

最近,我正在尝试将IdentityServer4与React客户端一起设置身份验证。我遵循了IdentityServer文档的Adding a JavaScript client教程(部分):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)如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 (
       
             
                <button id="login" onClick={() => {login()}}>Login</button>
                <button id="api" onClick={() => {api()}}>Call API</button>
                <button id="logout" onClick={() => {logout()}}>Logout</button>

                [cc]

<路由精确路径=" / callback" render = {()=> {window.location.href =" callback.html"}} />

{/ * { callback } * /}

);
}
}

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

testoidc.js文件包含以下功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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 + '\
\
';
  });
}

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();
}

有很多错误。单击登录按钮时,将重定向到identityServer的登录页面(很好)。当我使用有效的凭据登录时,我将重定向到我的React应用程序:http:// localhost:3000 / callback.html#id_token = Token

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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"
                    }

                }

尽管似乎从未调用过回调函数,但它仅停留在回调URL上,后面带有很长的标记。

另外,getUser函数在登录后仍显示"用户未登录",并且"调用API"按钮一直在说没有令牌。因此,显然事情无法正常进行。我只是不知道哪一点出了问题。
检查时,我可以看到本地存储区中生成了一个令牌:

enter image description here

同样,当我单击注销按钮时,我会重定向到Identity Host的注销页面,但是当我单击注销时,我不会重定向到我的客户端。

我的问题是:

  • 我是否可以与IdentityServer4一起实现oidc-client?
  • 我使用的是正确的库还是需要与oidc-client.js不同的库?
  • 是否有任何教程将React前端与IdentityServer4和oidc-client(无redux)结合使用,我找不到任何教程。
  • 如何/在何处添加callback.html,应将其重写?

有人可以向我指出正确的方向,这里很可能有更多的问题出了问题,但此刻我只是停留在开始的地方。


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

  • React App准备请求,并使用client_idredirect_uri(以及状态,随机数)将用户重定向到Auth服务器
  • IdentityServer检查client_idredirect_uri是否匹配。

    • 如果用户未登录,则显示一个登录框
    • 如果需要同意书(类似于您在某些应用中通过Facebook / Google登录),请显示必要的互动
    • 如果用户已通过身份验证和授权,请使用新参数将页面重定向到redirect_uri。对于您的情况,您的URL将如下所示:https://example.com/cb#access_token=...&id_token=...&stuff-like-nonce-and-state
  • 现在,React应用程序需要解析URL,访问值并将令牌存储在将来的请求中使用的某个位置:
  • 实现逻辑的最简单方法是首先在路由器中设置一个路由,该路由解析为将执行逻辑的组件。该组件可以是"不可见的"。它甚至不需要渲染任何东西。您可以这样设置路线:

    1
    <Route path="/cb" component={AuthorizeCallback} />

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

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