作者段清华,「最懂金融的 AI 工程师,微软 AI 领域最有价值专家(MVP),谷歌开发者专家(GDE),希望加速人类的生产力,让智能比电力与宽带更普及。」

为什么需要云身份验证和单点登录

简单来说是为了降低维护用户注册登录系统、权限、统计等各方面的成本。

应用结构简述

通过 Authing 实现身份验证和单点登录,有很多种方法,这篇文章的例子是根据自身软件架构,实现了其中一种相对简单的方法,并不适用所有情况,Authing 本身还提供了多种的登录解决方案,包括直接嵌入到网站上、APP 上的等等。
前端采用纯 React/React-router/Ant.design 开发,没用 Redux/Server Rendering 之类比较复杂的东西,就使用 create-react-app 的最基本方案,没用 TypeScript(因为懒,我有罪)。
后端采用 Python + FastAPI 的简单 API。

登录流程

第一阶段,前端

通过检测本地 localStorage,未发现保存的登录 token 信息时,提示用户需要登录,给出登录链接,用 HTML 的 a 标签直接跳转到 Authing 提供的 SSO 网址上,例如 xxxx.authing.cn ,其中 xxxx 是可以用户自定义的。

第二阶段,Authing SSO 网站

完成登录,可以自由配置,例如注册方式,登录方式比如游记验证码,微信小程序,微信扫码,邮箱密码等。登录成功后,会自动跳转到你配置的回调地址上,回调时可以选择直接提供 token。
例如你配置的回调地址是 xxxx.cn/login ,Authing 可以通过配置,在登录成功后自动跳转到 xxxx.cn/login/#/token=xxxxxxxx 。

第三阶段,通过回调返回前端

这样就可以直接在前端,即 React 部分通过对 window.location 或 document.URL 的解析获取到这个 token。
前端获取到这个 token,就可以通过 Authing 提供的 JavaScript 的 SDK,验证 token,获取用户信息。如果获取用户信息成功,则说明用户登录成功。
如果在第一阶段中,通过 localStorage 检测到了本地的 token,可以直接跳转到这一阶段,通过 Authing 的 SDK 进行 token 验证,这样就跳过了第二阶段。
前端对后端的每个 API 调用都要提交 token,可以通过设置 header 的方式实现。

第四阶段,后端

API 拿到前端的 token 之后,通过 Authing 提供的 python SDK,验证这个 token 和获取用户当前信息,通过后端再次验证这个 token 是否合法,如果不合法可以返回 401 未授权登录,如果合法,可以继续实现 API 本身的功能。

用户的体验流程

  • 未登录时:
    • 用户打开网站,前端提示未登录,用户点击登录链接(或按钮),跳转到 Authing 的 SSO 网址。
  • 用户在 Authing 网站上实现统一的注册/登录,成功后跳转回网站。
  • 跳转回的回调地址通过 Token 可以验证用户登录成功,所以这里用户可以直接看到登录成功的提示。
  • 用户开始使用应用。
  • 登录后时:
    • 用户打开网站,因为前端已经检测到了保存的 token,并且通过 SDK 验证了前端 token 的基本有效性(实际有效性是又后端验证的),所以直接跳转到应用部分。
  • 用户开始使用应用。

开发的体验

  • 前端:
    • 使用 Authing-js-SDK 验证 token。
  • 使用 Authing-sso-SDK 实现彻底退出 SSO 登录。
  • 后端:
    • 使用 Authing-python SDK 验证前端传过来的 token。
  • 其他:
    • 理论上用户可以通过伪造 token,骗过前端程序,但是因为后端每次 API 调用都会验证 token,后端的 token 合法性验证是对前端透明的,所以无法欺骗后端程序,只要后端验证不通过,就可以不给前端返回机密信息。
  • 根据是否允许用户在多个地方登录(如多个电脑、浏览器登录),可以有两种策略,一种是允许用户多个地方登录,那不需要做太多测试;另一种是只允许用户在最后登录的设备中使用,这个时候可以通过对比从 Authing SDK 拿到的 token(最后登录的 token)和用户前端提交的 token 来实现这个能力。

Authing 实现的云身份验证和 SSO 的优点

  • 不用实现与维护自己的用户信息系统,包括用户注册、登录、找回密码等。
  • 可以快速实现多种登录方式,如邮箱登录、手机验证码登录、微信扫码登录等。
  • 可以通过 Authing 直接实现用户权限控制功能,通过用户分组等等方法。
  • 用户登录信息保存在 Authing 的单点登录(SSO)上,只要登录信息没过期,就可以让用户继续快速登录,提高用户体验,而这些都可以通过配置实现。
  • 直接对接 Authing 的用户统计功能,包括活跃用户,登录日志等等,不需要额外的实现。

代码

代码分为前端和后端两部分。

前端

前端分为四个主要部分:
  • 检测登录状态,未登录时跳转到 Authing SSO 的组件。
  • 接收 Authing 回调信息的 landing 页面,完成登录 token 验证的组件。
  • 退出登录功能。
  • 封装浏览器的 AJAX 接口,在提交时携带 token。
跳转到 Authing SSO。
/** * 本地先检测登录状态,如果没有则提示跳转到authing sso登录 */exportfunctioncheckLogin() {const userInfo = localStorage.getItem('userInfo')if (userInfo) {try {returnJSON.parse(userInfo) } catch (e) {console.error(e) } }}<a href='https://xxx.authing.cn' alt='login' style={{color: 'black', }}><Buttontype="primary"key="console"> 请点击这里,在新页面完成登录</Button></a>
登录成功后,Authing 调用设置的回调地址,在跳转过来的 landing 页面中,可以通过 URL 拿到 token。
import { AuthenticationClient } from"authing-js-SDK"const authenticationClient = new AuthenticationClient({appId: "XXXXXXXXXXXXXX",})// 从URL获取tokenconst m = window.location.hash.match(/id_token=([^$&]+)[$&]/)if (m) {const token = m[1]// 设置好客户端token之后获取用户信息 authenticationClient.setToken(token)const userInfo = await authenticationClient.getCurrentUser()if (!userInfo || !userInfo.id) {this.setState({loading: false,loginSuccess: false,text: '登录失败' })return }// 成功 localStorage.setItem('userInfo', JSON.stringify(userInfo))this.setState({loading: false,loginSuccess: true,text: '登录成功' })}
退出登录。
import AuthingSSO from"@authing/sso"exportconst authSSO = new AuthingSSO({ appId: "XXXXXXXXXXXXXX", appType: "oidc", appDomain: "xxx.authing.cn"})<Buttontype='link' onClick={async ()=> {try {// 调用接口,从SSO页面也退出登录console.log(await authSSO.logout()) } catch(e) { } localStorage.removeItem('userInfo') message.warn('您已经退出登录') }}> {checkLogin() ? checkLogin().nickname + ' 退出登录' : ''}</Button>
对 API 提交时,同时携带 token,以便于后端验证用户权限。
/** * 这个函数是用来代替原生的fetch函数 */exportasyncfunctionfetchGet(url) {const userInfo = checkLogin()const token = userInfo ? userInfo.token : ''const res = await fetch(url, {headers: {'token': token } })// 后端授权检测失败if (res.status === 401) { message.error('您已经退出登录') localStorage.removeItem('userInfo')return res }return res}

后端

后端主要是接收前端传过来的 token 并验证,这个组件对于不同框架来说可以是一个 middleware,也可以根据需要设计成一个装饰器。
以下代码针对 FastAPI 设计。
from fastapi import FastAPI# https://github.com/tiangolo/fastapi/issues/142#issuecomment-688566673from fastapi import Securityfrom fastapi.security.api_key import APIKeyHeaderfrom fastapi import HTTPExceptionfrom starlette import statusfrom authing.v2.authentication import AuthenticationClient, AuthenticationClientOptionsauthentication_client = AuthenticationClient( options=AuthenticationClientOptions( user_pool_id='XXXXXXXXXXXXXX'))defauth_testing(token):""" 测试authing的结果 """ user = authentication_client.get_current_user(token) user_id = user.get('id') user_token = user.get('token')if token == user_token:returnTrue, '成功'returnFalse, '验证失败'# 设置FastAPI要获取的header名称API_KEY_NAME = "token"api_key_header_auth = APIKeyHeader(name=API_KEY_NAME, auto_error=True)asyncdefget_api_key(api_key_header: str = Security(api_key_header_auth)): ret, detail = auth_testing(api_key_header)ifnot ret:raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=detail, )# 用法:# @app.get('/', dependencies=get_dependencies())defget_dependencies():return [Security(get_api_key)]app = FastAPI()@app.get('/', dependencies=get_dependencies())defmain():return { 'ok''hello' }
Authing 欢迎开发者朋友们积极投稿,精美周边小礼物等你来拿~
投稿请投递至:[email protected]
END
关于 Authing
Authing 是国内首款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务。Authing 被科技部认定为「2020 国家高新技术企业」,被中国信息通信研究院评选为「国内身份管理与访问控制领域创新企业」,并被录入《2019 网络安全产业白皮书》。Authing 已为中国石油、国家电网、招商银行、日本丰田、德高集团等国内外优秀企业打造了卓越的开发方式、高效的办公流程和安全的 IT 管理体系。
Authing 2021 招聘全面启动
点击下方
阅读原文
即刻访问博客。
让我知道你在看
继续阅读
阅读原文