十分钟实现单点登录

avatar
@Authing
原文链接: mp.weixin.qq.com
单点登录(Single Sign On),简称为SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 更好的阅读体验请访问:docs.authing.cn/authing/quickstart/implement-sso-with-authing

开始之前

如果你不了解用户池、单点登录和认证授权,建议先阅读基础概念。

预备知识

  1. 基本的 HTML 和 CSS 知识

  2. 中级 JavaScript 技能

所需工具

  1. 你喜欢的文本编辑器

  2. 可以在本地运行的 Web 服务器(比如:npm install http-server -g

注册一个 Authing 账号

如果你还没有账号,请点击这里注册 Authing 账号,注册完成后请进入控制台并创建一个用户池。

创建一个 OIDC 应用

依次点击第三方登录 -> OIDC 应用选项卡,点击蓝色的「创建 OIDC 应用」按钮。
填上你的应用名,指定此 OIDC 应用的二级域名(认证地址),回调地址,其他参数保留默认即可。点击「确定」。
参数解释 认证地址,一个 authing.cn 的二级域名,用户将在此网址进行登录。 回调 URL,OIDC 登录成功后,回调到开发者自己业务的地址。本教程为演示,填写的地址是 http://localhost:8080,实际场景下要填写自己的业务地址。 在应用列表中点击刚创建好的应用,记录下 AppID,二级域名,供以后使用。

使用 AuthingSSO SDK 集成单点登录

创建一个空白的 HTML 文档用来编写 Authing 程序

本教程只是为了演示,因此我们没选择高级框架,这可以让我们专注于 Authing 本身。
<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <meta http-equiv="X-UA-Compatible" content="ie=edge" />    <title>Authing SSO Example</title>  </head>  <body></body></html>

添加三个按钮

增加三个按钮控件到 body 中,目的是为了演示如何使用 SDK 管理单点登录状态。
<button id="btn-login">login</button><button id="btn-track-session">trackSession</button><button id="btn-logout">logout</button>

引入 AuthingSSO 并初始化

从 CDN 加载 AuthingSSO 的 SDK。填入你的 OIDC 应用 ID 和 域名,进行初始化。
<script src="https://cdn.jsdelivr.net/npm/@authing/sso/dist/AuthingSSO.umd.min.js"></script><script>  let auth = new AuthingSSO({    appId: "YOUR_OIDC_APP_ID",    appType: "oidc",    appDomain: "OIDC_APP_DOMAIN.authing.cn"  });</script>

为按钮注册点击事件

达到的效果是:
  • 点击 login 按钮,浏览器会跳转到 OIDC 登录页面,与用户完成身份确认。

  • 点击 trackSession 按钮,会显示当前登录状态。

  • 点击 logout 按钮,进行单点登出。

let login = document.getElementById("btn-login");let trackSession = document.getElementById("btn-track-session");let logout = document.getElementById("btn-logout");login.onclick = function() {  auth.login();};trackSession.onclick = async function() {  let res = await auth.trackSession();  alert(JSON.stringify(res));};logout.onclick = async function() {  let res = await auth.logout();  alert(JSON.stringify(res));};

完整代码

<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <meta http-equiv="X-UA-Compatible" content="ie=edge" />    <title>Authing SSO Example</title>  </head>  <body>    <button id="btn-login">login</button>    <button id="btn-track-session">trackSession</button>    <button id="btn-logout">logout</button>    <script src="https://cdn.jsdelivr.net/npm/@authing/sso/dist/AuthingSSO.umd.min.js"></script>    <script>      let auth = new AuthingSSO({        appId: "YOUR_OIDC_APP_ID",        appType: "oidc",        appDomain: "OIDC_APP_DOMAIN.authing.cn"      });      let login = document.getElementById("btn-login");      let trackSession = document.getElementById("btn-track-session");      let logout = document.getElementById("btn-logout");      login.onclick = function() {        auth.login();      };      trackSession.onclick = async function() {        let res = await auth.trackSession();        alert(JSON.stringify(res));      };      logout.onclick = async function() {        let res = await auth.logout();        alert(JSON.stringify(res));      };    </script>  </body></html>
示例代码可从 Github 上找到,建议将 Github 上的代码下载运行。

运行方法

在终端中运行以下命令
$ git clone https://github.com/Authing/authing-sso-demo$ cd authing-sso-demo$ npm install -g http-server$ http-server
之后在浏览器访问 http://localhost:8080。 如果本地 8080 端口已被占用,应用可能会运行在 8081、8082 等后续端口。

运行效果

最初,我们没有登录,因此,点击 trackSession 按钮获取到的登录状态为空。
现在我们点击 login 按钮,会跳转到 OIDC 应用的用户认证页面,输入用户名密码进行登录。
浏览器被重定向到我们之前设置的回调链接,记下 code 参数,用于后面换取用户信息。 点击 trackSession 按钮,此时能够获取到该用户的登录状态,包括用户 ID,应用 ID,应用类型。
点击 logout 按钮,输出单点登出成功。
此时我们再点击 trackSession 按钮,可见登录状态为空,说明用户已经单点登出了。

获取用户信息

使用 OIDC 流程中返回的 code 换取 access_token

向以下地址发送 POST 请求:
POST https://OIDC_APP_DOMAIN.authing.cn/oauth/oidc/token
body 参数

返回示例
{  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyWkZGckt5VTNNVmV4OVQyODE3S3gwdmJpNmlfS2MifQ.eyJqdGkiOiJ4R01uczd5cmNFckxiakNRVW9US1MiLCJzdWIiOiI1YzlmNzVjN2NjZjg3YjA1YTkyMWU5YjAiLCJpc3MiOiJodHRwczovL2F1dGhpbmcuY24iLCJpYXQiOjE1NTQ1Mzc4NjksImV4cCI6MTU1NDU0MTQ2OSwic2NvcGUiOiJvcGVuaWQgcHJvZmlsZSBvZmZsaW5lX2FjY2VzcyBwaG9uZSBlbWFpbCIsImF1ZCI6IjVjYTc2NWUzOTMxOTRkNTg5MWRiMTkyNyJ9.wX05OAgYuXeYM7zCxhrkvTO_taqxrCTG_L2ImDmQjMml6E3GXjYA9EFK0NfWquUI2mdSMAqohX-ndffN0fa5cChdcMJEm3XS9tt6-_zzhoOojK-q9MHF7huZg4O1587xhSofxs-KS7BeYxEHKn_10tAkjEIo9QtYUE7zD7JXwGUsvfMMjOqEVW6KuY3ZOmIq_ncKlB4jvbdrduxy1pbky_kvzHWlE9El_N5qveQXyuvNZVMSIEpw8_y5iSxPxKfrVwGY7hBaF40Oph-d2PO7AzKvxEVMamzLvMGBMaRAP_WttBPAUSqTU5uMXwMafryhGdIcQVsDPcGNgMX6E1jzLA",  "expires_in": 3600,  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyWkZGckt5VTNNVmV4OVQyODE3S3gwdmJpNmlfS2MifQ.eyJzdWIiOiI1YzlmNzVjN2NjZjg3YjA1YTkyMWU5YjAiLCJub25jZSI6IjIyMTIxIiwiYXRfaGFzaCI6Ik5kbW9iZVBZOEFFaWQ2T216MzIyOXciLCJzaWQiOiI1ODM2NzllNC1lYWM5LTRjNDEtOGQxMS1jZWFkMmE5OWQzZWIiLCJhdWQiOiI1Y2E3NjVlMzkzMTk0ZDU4OTFkYjE5MjciLCJleHAiOjE1NTQ1NDE0NjksImlhdCI6MTU1NDUzNzg2OSwiaXNzIjoiaHR0cHM6Ly9hdXRoaW5nLmNuIn0.IQi5FRHO756e_eAmdAs3OnFMU7QuP-XtrbwCZC1gJntevYJTltEg1CLkG7eVhdi_g5MJV1c0pNZ_xHmwS0R-E4lAXcc1QveYKptnMroKpBWs5mXwoOiqbrjKEmLMaPgRzCOdLiSdoZuQNw_z-gVhFiMNxI055TyFJdXTNtExt1O3KmwqanPNUi6XyW43bUl29v_kAvKgiOB28f3I0fB4EsiZjxp1uxHQBaDeBMSPaRVWQJcIjAJ9JLgkaDt1j7HZ2a1daWZ4HPzifDuDfi6_Ob1ZL40tWEC7xdxHlCEWJ4pUIsDjvScdQsez9aV_xMwumw3X4tgUIxFOCNVEvr73Fg",  "refresh_token": "WPsGJbvpBjqXz6IJIr1UHKyrdVF",  "scope": "openid profile offline_access phone email",  "token_type": "Bearer"}

验证 access_token 和 id_token 的合法性

OIDC 默认使用 OIDC 应用的 secret 对 token 进行验证(也就是在创建应用时默认选择 HS256 算法)。如果你使用 javascript 那么可以使用 jsonwebtoken 进行验证:
const jwt = require('jsonwebtoken');let decoded = jwt.verify(token, <appSecret>);
如果是其他语言,那么你在服务端需要用 app_secret 作为 HS256 签名参数来计算签名和 JWT 中的签名进行对比,伪代码如下:
HMACSHA256(  base64UrlEncode(header) + "." +  base64UrlEncode(payload),  "1133fd20c14e4cc29b6ecb71fb8eb952"// app_secret)
如果是 RS256 等非对称加密算法,需要使用公钥验证签名。Authing 将使用私钥进行签名,请使用 Authing 的公钥来验证签名:
-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxRijj2seoesv5K0Z+ymRK7DSDPxdsM2sGQD2ZVhLjLsxZWJtXUXh7ERdUU6OT3BqYZZf7CLIhN6yyNtTOgfgpLG9HVJd7ZSKzuy2dS7mo8jD8YRtptAJmNFqw6z8tQp5MNG1ZHqp9isKqJmx/CFYkRdXBmjjj8PMVSP757pkC3jCq7fsi0drSSg4lIxrSsGzL0++Ra9Du71Qe/ODQKU0brxaI1OKILtfcVPTHTaheV+0dw4eYkSDtyaLBG3jqsQbdncNg8PCEWchNzdO6aajUq4wbOzy/Ctp399mz0SGKfuC5S8gqAFABFT3DH3UD21ZztQZwFEV2AlvF+bcGEstcwIDAQAB-----END PUBLIC KEY-----

使用 access_token 换取用户信息

开发者在自己的服务中可以使用 access_token 换取用户信息。根据 scope 的不同,这里的返回信息也会有所不同,字段符合 OIDC 规范,字段解释请参考用户信息字段含义。请求链接:
GET https://users.authing.cn/oauth/oidc/user/userinfo?access_token=<access_token>
返回示例:
{  "sub": "<用户在 Authing 的唯一标识>",  "nickname": "Authing",  "name": "张三",  "locale": "en-US"}
更多字段解释请参考用户信息字段含义。

什么是 Authing?

Authing 提供专业的身份认证和授权服务。我们为开发者和企业提供用以保证应用程序安全所需的认证模块,这让开发人员无需成为安全专家。你可以将任意平台的应用接入到 Authing(无论是新开发的应用还是老应用都可以),同时你还可以自定义应用程序的登录方式(如:邮箱/密码、短信/验证码、扫码登录等)。你可以根据你使用的技术,来选择我们的 SDK 或调用相关 API 来接入你的应用。当用户发起授权请求时,Authing 会帮助你认证他们的身份和返回必要的用户信息到你的应用中。
  • 官网:authing.cn

  • 小登录:wxapp.authing.cn/#

  • 仓库:

    • gitee.com/Authi_ng

    • github.com/authing

  • 文档:docs.authing.cn/authing