开发者友好的赛博恋爱指南(SSO 版)
某公司全栈开发 Danny ENFJ 人格
Danny 和女朋友是打游戏认识的,经历了八年的爱情长跑,羡煞同事,不过只有 Danny 自己知道异地恋有多岌岌可危。
为了哄女友,Danny 想把存储在游戏里的几百张截图打印出来,做一个“能让对象感动哭了”的纪念相册。
一步之遥在于,Danny 完全想不起来密码。
问:除了找回账密,还有什么办法?
A. 把和别的女生的合照 p 成和女友的
B. 连夜写代码黑进去
答案:就 Danny 这种游走于几十上百个应用账户的海王来说,找回了密码也很容易忘记。其实,单点登录(Single Sign On) ,简称为 SSO,就能够有效解决这个问题。
单点登录(SSO)是指,在多个应用系统中,用户只需使用一次登录凭据就可以访问其他所有被授予权限的应用。
也就是说,Danny 登录了 steam 游戏应用后,所有子游戏也处于登录态,无需再次输入账号和密码。打完游戏就可以马上哄对象了,不可谓不方便。
某公司后端开发 Sam INTP 人格
去年七夕,Sam 被居家隔离,还不忘给暧昧对象(自以为的)订了一束玫瑰。
暧昧对象扫描完祝福卡片上的二维码,沉默了。“请设置16位密码,须包含大小写字母、数字、特殊符号”,她看着手边还没拆的二十几份礼物,干脆地扔掉了卡片。
于是,Sam 用 Vue 写的表白小程序压根没派上用场。暧昧对象既没听到王心凌的《爱你》,也不知道是谁送的花。
问:Sam 究竟做错了什么?
A. 红玫瑰太俗了,应该送一把茼蒿
B. 二维码太普通,应该做成 8D 的
答案:Sam 错在太老套。使用单点登录(SSO) 就不需要到处设置密码,也无需到处查找密码,还有助于规避密码被破解的风险。
如果暧昧对象拆了一份礼物,其他礼物也自动打开,她的生产力和效率便大大提升,不就有时间认真检阅你的礼物了嘛?
对于企业来说,随着业务的发展,各类系统只增不减,权限管理 也日渐力不从心。单点登录(SSO)则能够帮助企业做到,员工只需登录个人 OA 系统的账号密码就可以访问飞书、销售易、客户系统等应用程序。
如果企业自研身份模块,时间和人力成本的投入都很大。不过我最近发现了 Authing 这个摸鱼神器,使用 Authing 单点登录,用几行代码就可以集成登录系统,支持用户统一登录。
这是因为,Authing 提供完善易用的文档,并且支持主流编程语言的 SDK 。开发者可以通过直接调用 SDK 接口与 Authing 完成集成,为多个业务软件在 web 内实现跨主域的单点登录效果。
具体操作如下:
创建自建应用
也可以使用现有应用
在控制台的「自建应用」页面,点击「创建自建应用」,应用类型选择「单页 Web 应用」,并填入以下信息:
- 应用名称:你的应用名称;
- 认证地址:选择一个二级域名,必须为合法的域名格式,例如
my-spa-app
;
¶配置单点登录
参考 自建应用 SSO 方案
¶修改配置
找到刚刚配置好的应用,进入应用配置页面
- 认证配置:配置
登录回调 URL
- 授权配置:
授权模式
开启authorization_code
、refresh_token
- 授权配置:
返回类型
开启code
- 点击保存进行保存配置
如下图所示:
至此,配置完成
¶安装
Authing Browser SDK 支持通过包管理器安装、script 标签引入的方式的方式集成到你的前端业务软件。
¶使用 NPM 安装
$ npm install @authing/browser
复制成功
¶使用 Yarn 安装
$ yarn add @authing/browser
复制成功
¶使用 script 标签直接引入
<script src="https://cdn.jsdelivr.net/npm/@authing/browser"></script>
<script>const sdk = new Authing({// 很重要,请仔细填写!// 如果应用开启 SSO,这儿就要写单点登录的“应用面板地址”;否则填写应用的“认证地址”。domain: '认证域名',appId: '应用 ID',// 登录回调地址,需要在控制台『应用配置 - 登录回调 URL』中指定redirectUri: '登录回调地址'});</script>
复制成功
¶初始化
¶应用 ID
如图所示:
¶认证域名
如图所示:
¶回调地址
根据你自己的业务填写回调地址,如图所示:
为了使用 Authing Browser SDK,你需要填写应用 ID
、认证域名
、回调地址
等参数,如下示例:
import { Authing } from '@authing/browser';const sdk = new Authing({// 很重要,请仔细填写!// 如果应用开启 SSO,这儿就要写单点登录的“应用面板地址”;否则填写应用的“认证地址”。domain: '认证域名',appId: '应用 ID',// 登录回调地址,需要在控制台『应用配置 - 登录回调 URL』中指定redirectUri: '登录回调地址'
});
复制成功
¶登录
Authing Browser SDK 可以向 Authing 发起认证授权请求,目前支持三种形式:
- 在当前窗口转到 Authing 托管的登录页;
- 弹出一个窗口,在弹出的窗口中加载 Authing 托管的登录页。
- 静默登录
¶跳转登录
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Authing } from '@authing/browser';
import type { LoginState } from '@authing/browser/dist/types/global';function App() {const sdk = useMemo(() => {return new Authing({// 很重要,请仔细填写!// 如果应用开启 SSO,这儿就要写单点登录的“应用面板地址”;否则填写应用的“认证地址”。
domain: '单点登录的“应用面板地址”',// 应用 ID
appId: '应用 ID',// 登录回调地址,需要在控制台『应用配置 - 登录回调 URL』中指定
redirectUri: '登录回调 URL',});}, []);const [loginState, setLoginState] = useState<LoginState | null>();/**
* 以跳转方式打开 Authing 托管的登录页
*/const login = () => {
sdk.loginWithRedirect();};/**
* 获取用户的登录状态
*/const getLoginState = useCallback(async () => {const state = await sdk.getLoginState();setLoginState(state);}, [sdk]);useEffect(() => {// 判断当前 URL 是否为 Authing 登录回调 URLif (sdk.isRedirectCallback()) {/**
* 以跳转方式打开 Authing 托管的登录页,认证成功后需要配合 handleRedirectCallback 方法,
* 在回调端点处理 Authing 发送的授权码或 token,获取用户登录态
*/
sdk.handleRedirectCallback().then((res) => setLoginState(res));} else {getLoginState();}}, [getLoginState, sdk]);return (<div className="App">
<p>
<button onClick={login}>loginWithRedirect</button>
</p>
<p>
<code>{JSON.stringify(loginState)}</code>
</p>
</div>);
}export default App;
复制成功
如果你想自定义参数,也可以对以下参数进行自定义传参,如不传参将使用默认参数
const login = () => {const params: {// 回调地址,默认为初始化参数中的 redirectUri
redirectUri?: string;// 发起登录的 URL,若设置了 redirectToOriginalUri 会在登录结束后重定向回到此页面,默认为当前 URL
originalUri?: string;// 即使在用户已登录时也提示用户再次登录
forced?: boolean;// 自定义的中间状态,会被传递到回调端点
customState?: any;} = {redirectUri: '回调地址',originalUri: '发起登录的 URL',forced: false,customState: {},}
sdk.loginWithRedirect(params);
};
复制成功
¶弹出窗口登录
你也可以在你的业务软件页面使用下面的方法,通过弹出一个新窗口的方式让用户在新窗口登录:
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Authing } from '@authing/browser';
import type { LoginState } from '@authing/browser/dist/types/global';function App() {const sdk = useMemo(() => {return new Authing({// 很重要,请仔细填写!// 如果应用开启 SSO,这儿就要写单点登录的“应用面板地址”;否则填写应用的“认证地址”。
domain: '单点登录的“应用面板地址”',// 应用 ID
appId: '应用 ID',// 登录回调地址,需要在控制台『应用配置 - 登录回调 URL』中指定
redirectUri: '登录回调 URL',});}, []);const [loginState, setLoginState] = useState<LoginState | null>();/**
* 以弹窗方式打开 Authing 托管的登录页
*/const login = async () => {const res = await sdk.loginWithPopup();setLoginState(res);};/**
* 获取用户的登录状态
*/const getLoginState = useCallback(async () => {const state = await sdk.getLoginState();setLoginState(state);}, [sdk]);useEffect(() => {getLoginState();}, [getLoginState]);return (<div className="App">
<p>
<button onClick={login}>login</button>
</p>
<p>
<code>{JSON.stringify(loginState)}</code>
</p>
</div>);
}export default App;
复制成功
如果你想自定义参数,也可以对以下参数进行自定义传参,如不传参将使用默认参数
const login = async () => {const params: {// 回调地址,默认为初始化参数中的 redirectUri
redirectUri?: string;// 即使在用户已登录时也提示用户再次登录
forced?: boolean;} = {
redirectUri: '回调地址',
forced: false,};const res = await sdk.loginWithPopup(params);setLoginState(res);
};
复制成功
¶静默登录
在 自建应用 SSO 方案 一文中有提到,可以将多个自建应用添加到「单点登录 SSO」面板,如果用户已经登录过其中的一个应用,那么在同一浏览器另一个标签页访问其他应用的时候,就可以实现静默登录,直接获取到用户信息,实现单点登录效果。
import React, { useEffect, useMemo, useState } from 'react';
import { Authing } from '@authing/browser';
import type { LoginState } from '@authing/browser/dist/types/global';function App() {const sdk = useMemo(() => {return new Authing({// 很重要,请仔细填写!// 如果应用开启 SSO,这儿就要写单点登录的“应用面板地址”;否则填写应用的“认证地址”。
domain: '单点登录的“应用面板地址”',// 应用 ID
appId: '应用 ID',// 登录回调地址,需要在控制台『应用配置 - 登录回调 URL』中指定
redirectUri: '登录回调 URL',});}, []);const [loginState, setLoginState] = useState<LoginState | null>();useEffect(() => {// 判断当前 URL 是否为 Authing 登录回调 URLif (sdk.isRedirectCallback()) {console.log('redirect');/**
* 以跳转方式打开 Authing 托管的登录页,认证成功后需要配合 handleRedirectCallback 方法,
* 在回调端点处理 Authing 发送的授权码或 token,获取用户登录态
*/
sdk.handleRedirectCallback().then((res) => setLoginState(res));} else {console.log('normal');// 获取用户的登录状态
sdk.getLoginState().then((res) => {if (res) {setLoginState(res);} else {// 如果用户没有登录,跳转认证中心
sdk.loginWithRedirect();}});}}, [sdk]);return (<div>
<p>
Access Token: <code>{loginState?.accessToken}</code>
</p>
<p>
User Info: <code>{JSON.stringify(loginState?.parsedIdToken)}</code>
</p>
<p>
Access Token Info:
<code>{JSON.stringify(loginState?.parsedAccessToken)}</code>
</p>
<p>
Expire At: <code>{loginState?.expireAt}</code>
</p>
</div>);
}export default App;
复制成功
¶高级使用
每次发起登录本质是访问一个 URL 地址,可以携带许多参数。Authing Browser SDK 默认会使用缺省参数。如果你需要精细控制登录请求参数,可以参考本示例。
import { Authing } from '@authing/browser';const sdk = new Authing({// 很重要,请仔细填写!// 如果应用开启 SSO,这儿就要写单点登录的“应用面板地址”;否则填写应用的“认证地址”。domain: '认证域名',appId: '应用 ID',// 登录回调地址,需要在控制台『应用配置 - 登录回调 URL』中指定redirectUri: '登录回调地址',// 应用侧向 Authing 请求的权限,以空格分隔,默认为 'openid profile'scope: 'openid email phone profile',// 回调时在何处携带身份凭据,默认为 fragment// fragment: 在 URL hash 中携带// query: 在查询参数中携带responseMode: 'fragment',// 是否使用 OIDC implicit 模式替代默认的 PKCE 模式// 由于 implicit 模式安全性较低,不推荐使用,只用于兼容不支持 crypto 的浏览器useImplicitMode: false,// implicit 模式返回的凭据种类,默认为 'token id_token'// token: 返回 Access Token// id_token: 返回 ID TokenimplicitResponseType: 'token id_token',// 是否在每次获取登录态时请求 Authing 检查 Access Token 有效性,可用于单点登出场景,默认为 false// 如果设为 true,需要在控制台中将『应用配置』-『其他配置』-『检验 token 身份验证方式』设为 noneintrospectAccessToken: false,// 弹出窗口的宽度popupWidth: 500,// 弹出窗口的高度popupHeight: 600,
});
复制成功
¶检查登录态并获取 Token
如果你想检查用户的登录态,并获取用户的 Access Token
、ID Token
,可以调用 getLoginState
方法,如果用户没有在 Authing 登录,该方法会抛出错误:
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Authing } from '@authing/browser';
import type { LoginState } from '@authing/browser/dist/types/global';function App() {const sdk = useMemo(() => {return new Authing({// 很重要,请仔细填写!// 如果应用开启 SSO,这儿就要写单点登录的“应用面板地址”;否则填写应用的“认证地址”。
domain: '单点登录的“应用面板地址”',// 应用 ID
appId: '应用 ID',// 登录回调地址,需要在控制台『应用配置 - 登录回调 URL』中指定
redirectUri: '登录回调 URL',});}, []);const [loginState, setLoginState] = useState<LoginState | null>();/**
* 以跳转方式打开 Authing 托管的登录页
*/const login = () => {
sdk.loginWithRedirect();};/**
* 获取用户的登录状态
*/const getLoginState = useCallback(async () => {const state = await sdk.getLoginState();setLoginState(state);}, [sdk]);useEffect(() => {// 判断当前 URL 是否为 Authing 登录回调 URLif (sdk.isRedirectCallback()) {/**
* 以跳转方式打开 Authing 托管的登录页,认证成功后需要配合 handleRedirectCallback 方法,
* 在回调端点处理 Authing 发送的授权码或 token,获取用户登录态
*/
sdk.handleRedirectCallback().then((res) => setLoginState(res));} else {getLoginState();}}, [getLoginState, sdk]);return (<div className="App">
<p>
<button onClick={login}>login</button>
</p>
<p>
<code>{JSON.stringify(loginState)}</code>
</p>
</div>);
}export default App;
复制成功
¶获取用户信息
你需要使用 Access Token 获取用户的个人信息:
- 用户初次登录成功时可以在回调函数中拿到用户的 Access Token,然后使用 Access Token 获取用户信息;
- 如果用户已经登录,你可以先获取用户的 Access Token 然后使用 Access Token 获取用户信息。
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Authing } from '@authing/browser';
import type { LoginState, UserInfo } from '@authing/browser/dist/types/global';function App() {const sdk = useMemo(() => {return new Authing({// 很重要,请仔细填写!// 如果应用开启 SSO,这儿就要写单点登录的“应用面板地址”;否则填写应用的“认证地址”。
domain: '单点登录的“应用面板地址”',// 应用 ID
appId: '应用 ID',// 登录回调地址,需要在控制台『应用配置 - 登录回调 URL』中指定
redirectUri: '登录回调 URL',});}, []);const [loginState, setLoginState] = useState<LoginState | null>();const [userInfo, setUserInfo] = useState<UserInfo | null>();/**
* 以跳转方式打开 Authing 托管的登录页
*/const login = () => {
sdk.loginWithRedirect();};/**
* 获取用户的登录状态
*/const getLoginState = useCallback(async () => {const state = await sdk.getLoginState();setLoginState(state);}, [sdk]);/**
* 用 Access Token 获取用户身份信息
*/const getUserInfo = async () => {if (!loginState) {alert("用户未登录");return;}const userInfo = await sdk.getUserInfo({
accessToken: loginState?.accessToken,});setUserInfo(userInfo);};useEffect(() => {// 判断当前 URL 是否为 Authing 登录回调 URLif (sdk.isRedirectCallback()) {/**
* 以跳转方式打开 Authing 托管的登录页,认证成功后需要配合 handleRedirectCallback 方法,
* 在回调端点处理 Authing 发送的授权码或 token,获取用户登录态
*/
sdk.handleRedirectCallback().then((res) => setLoginState(res));} else {getLoginState();}}, [getLoginState, sdk]);return (<div className="App">
<p>
<button onClick={login}>login</button>
<button onClick={getUserInfo}>getUserInfo</button>
</p>
<p>
loginState:
<code>{JSON.stringify(loginState)}</code>
</p>
<p>
userInfo:
<code>{JSON.stringify(userInfo)}</code>
</p>
</div>);
}export default App;
复制成功
¶退出登录
可以调用 SDK 的 logoutWithRedirect
方法退出登录
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Authing } from '@authing/browser';
import type { LoginState } from '@authing/browser/dist/types/global';function App() {const sdk = useMemo(() => {return new Authing({// 很重要,请仔细填写!// 如果应用开启 SSO,这儿就要写单点登录的“应用面板地址”;否则填写应用的“认证地址”。
domain: '单点登录的“应用面板地址”',// 应用 ID
appId: '应用 ID',// 登录回调地址,需要在控制台『应用配置 - 登录回调 URL』中指定
redirectUri: '登录回调 URL',});}, []);const [loginState, setLoginState] = useState<LoginState | null>();/**
* 以跳转方式打开 Authing 托管的登录页
*/const login = () => {
sdk.loginWithRedirect();};/**
* 获取用户的登录状态
*/const getLoginState = useCallback(async () => {const state = await sdk.getLoginState();setLoginState(state);}, [sdk]);/**
* 登出
*/const logout = async () => {await sdk.logoutWithRedirect();};useEffect(() => {// 判断当前 URL 是否为 Authing 登录回调 URLif (sdk.isRedirectCallback()) {/**
* 以跳转方式打开 Authing 托管的登录页,认证成功后需要配合 handleRedirectCallback 方法,
* 在回调端点处理 Authing 发送的授权码或 token,获取用户登录态
*/
sdk.handleRedirectCallback().then((res) => setLoginState(res));} else {getLoginState();}}, [getLoginState, sdk]);return (<div className="App">
<p>
<button onClick={login}>login</button>
<button onClick={logout}>logout</button>
</p>
<p>
loginState:
<code>{JSON.stringify(loginState)}</code>
</p>
</div>);
}export default App;
转载自:https://juejin.cn/post/7127959563571036190