随着互联网、物联网和移动终端等技术的迅猛发展,登录认证面临着新的挑战和需求。虽然登录认证在信息系统中是传统且古老的组成部分,但未来的发展前景依然广阔。不论是用户登录、PC 端、移动端还是智能设备的访问,身份认证在保障业务操作安全、资金安全、系统间通信和与外部系统集成等多个方面起到至关重要的作用。随着认证方式的不断演进,从最初的 Cookie 和 Session ,发展到如今的多端登录、多因素认证以及 API 令牌等多种认证手段。同时,用户终端设备的不断升级也推动着认证方式和手段的不断创新。但对于开发者来说,构建一个安全、高效的登录认证系统往往面临安全性设计复杂、用户体验优化难度、开发时间等问题。从加密算法设计到多平台兼容,再到用户体验的优化,都需要投入大量的时间和资源。开发者需要在短时间内轻松构建稳定、安全的登录认证体系。
01.开发者接入系统遇到的问题
登录认证系统自研复杂
开发者需要构建一个能够有效防御多种网络安全威胁(如身份盗用、数据泄露、暴力攻击破解、中间人攻击等)的登录认证系统,同时还需要考虑针对敏感操作添加多因素认证。开发者需要设计出安全性高效的解决方案,这也是为什么许多团队更倾向于使用专业认证平台作为认证。并且由于登录认证系统需要考虑安全性、兼容性和用户体验,开发者往往需要投入大量的时间和资源来设计和实现。尤其是在项目周期紧张的情况下,如何在有限的情况下期间构建一个既安全又稳定的认证系统,是开发者的一大挑战。
用户体验优化难
在当今的数字化时代,用户体验已经成为应用成功的关键因素之一。用户不仅仅关注应用功能是否强大,更关注交互是否畅通、使用是否便捷。尤其是在多端场景下,开发者需要兼顾不同设备的配置与用户。随着无密码认证等新技术的出现不断改变着用户对身份验证的需求,用户对应用的登录体验也提出了更高的要求。开发者需要的不仅仅是技术能力,更需要从用户角度出发,深入了解用户需求,权衡其中利弊。无论是直接的登录流程、快速的身份验证,还是避免繁琐操作,用户体验优化往往是技术挑战和设计挑战并存的问题。
多平台兼容性
随着认证技术的不断演进,从传统的基于 Cookie 和会话的认证方式逐步发展到如今广泛应用的多因素认证和 API 等新型技术,开发者不仅需要兼容多个平台,还需要保证这些平台间的认证机制能够无缝集成。在多平台环境下,用户可能会在不同的设备间频繁切换,例如从 PC 登录切换到移动端继续操作。系统能够在不同平台间同步认证状态,避免用户在设备切换时需要高效重复登录。开发者需要保证每个平台的用户界面都能够应答认证状态与交互信息,系统能够在不同的操作系统、浏览器和设备上稳定运行。
02.Authing 助力开发者快速接入认证体系
当使用 Authing 进行用户认证时,你不需要自己实现用户管理逻辑,所有的相关操作(如创建删除用户、配置登录流程、重置密码等)都可以通过 Authing 控制台托管登录页、API & SDK 完成。用户资料将被安全地存储在 Authing 云上的数据库中,你不需要额外保存一份用户资料,而是直接使用 Authing 中存储的用户信息实现你的业务需求。为此,需要先将你的业务数据与 Authing 用户联表。
接入用户认证方式
使用 Authing 接入用户认证流程,一共有以下几种方式:
使用 Authing 托管登录页
Authing 托管登录页是最简单、安全的集成方式。这是因为登录流程由 Authing 维护,并由 Authing 保证安全。对于应用集成,建议使用 Authing 托管的登录流程。你的业务系统将用户重定向到 Authing 登录页,在此用户进行身份验证,然后重定向回在控制台配置的应用登录回调 URL。此设计被认为是安全性最佳实践。在自定义配置方面,托管模式提供了登录注册表单自定义配置,可通过控制台配置和 CSS 进行界面自定义。
使用 Authing 提供的内嵌登录组件
内嵌登录组件(Guard)被认为是灵活性和集成之间的最佳平衡。如果集成需要更深入的自定义级别,或者在一些前后端分离的场景中无法使用托管模式,可以选择使用此模式。内嵌登录组件由 Authing 构建和更新,使用行业最佳实践安全性设计,仅需要几行 JavaScript 代码就可以集成到你开发的项目中。它可以直接从 CDN 或 NPM 加载,也可以从源代码构建。Authing 登录组件同时提供 Javascript 原生、React、Vue 和 Angular 的多种集成模式,在你的任何项目中都可以无缝集成并享有高度自定义灵活性。
使用 API & SDK
Authing 提供 RESTFul 和 GraphQL 两种形式的 API ,你可以基于此自定义 UI 和认证流程。同时 Authing 支持了 Java、JavaScript/Node.js、Python、PHP、C#、Swift、Go、Ruby、微信小程序等多种语言的 SDK,你可以选择自己熟悉的 SDK。
在不同场景下的接入 Authing
Authing 可以集成到标准 Web 应用、单页 Web 应用、客户端应用以及后端应用等各种场景中,你可以分别阅读不同场景的接入方式:
在标准 Web 应用中接入 Authing
本文以 Node.js Web 框架 Express 为例,介绍如何在传统的 Web 项目(如 Express MVC 、Django、PHP Laravel 等)中快速接入 Authing,实现登录、退出、获取用户信息等功能。
这里一共牵涉到三方:终端用户浏览器、应用服务器、 Authing 服务器,完整流程如下:
1、终端用户浏览器请求应用服务,发现用户未登录,跳转到 Authing 托管的登录页。
2、用户在此登录页完成登录之后,终端用户浏览器会在请求参数中携带授权码 (Authorization Code) 等数据跳转到应用服务器预先配置好的回调链。
3、应用服务器使用授权码 (Authorization Code) 向 Authing 服务器请求换取用户信息。
4、应用服务器获取到用户信息之后,建立与终端用户的会话。
5、终端用户得到登录成功提示,认证流程完成。
流程图如下所示:
在 Authing 中进行配置
在开始前,你需要在 Authing 中创建一个应用。你可以前往 Authing 控制台的应用列表页面创建应用。
配置回调链接
路径:应用->自建应用->应用详情页->应用配置->认证配置
当用户在 Authing 登录成功之后,浏览器会跳转到你配置的回调链接(Callback URL)。此回调链接应该是你应用中的一个路由,你需要在此路由中完成换取用户信息等操作。你必须配置此回调链接,否则用户将无法登录,而会显示 invalid_redirect_uri 错误提示。
此示例代码的回调链接为 https://console.authing.cn/console/get-started,将其复制到 登录回调 URL 配置项中,然后点击保存。
配置登出回调链接
你需要配置退出登录之后的回调地址(Logout URLs)。用户在 Authing 托管登录页退出登录之后,返回该地址。你必须配置此回调链接,否则用户将无法退出,而会显示 misconfiguration 错误提示。此示例代码的回调链接为 http://localhost:3000,将其复制到 登出回调 URL 配置项中,然后点击保存。
集成 Authing 到你的系统
安装依赖
此处是 node.js 示例,你需要安装支持标准 OIDC 协议的 openid-client 和 passportjs。
yarn add express express-session passport openid-client
初始化
在项目的最开始我们需要初始化 openid-client 的 Issuer,初始化参数如下:
client_id: OIDC Client ID,在 Authing 中即你的 应用 ID;
client_secret: OIDC Client Secret,在 Authing 中即你 应用的密钥;
issuer: OIDC Issuer,你可以在应用的端点信息中获取。获取方式如图所示,你需要保存好这些内容或记住获取方式,以后可能会频繁使用:
这里出于演示考虑,passport.serializeUser 中直接传 user 给回调函数 done,这会将用户信息存储在 req.session.passport.user 中,正式生产环境下不建议这么做,因为如果用户信息被修改而 session 没有更新,会造成数据不一致。passport.deserializeUser 传给回调函数 done 的第二个参数将会挂载到 req.user 上。
passport.serializeUser(function(user, done) {
console.log("serializeUser", user);done(null, user.sub);});
passport.deserializeUser(function(userId, done) {
console.log("deserializeUser", userId);done(null, userId);});
详细示例代码如下:
const express = require("express");const session = require("express-session");const passport = require("passport");const { Strategy, Issuer } = require("openid-client");const OIDC_CLIENT_ID = "YOUR_APPLICATION_ID";const OIDC_CLIENT_SECRET = "YOUR_APPLICATION_SECRET";const OIDC_ISSUER = "YOUR_OIDC_ISSUER";const REDIRECT_URI = "http://localhost:3000/auth/callback";(async () => {const issuer = await Issuer.discover(OIDC_ISSUER);const client = new issuer.Client({client_id: OIDC_CLIENT_ID,client_secret: OIDC_CLIENT_SECRET,id_token_signed_response_alg: "HS256",token_endpoint_auth_method: "client_secret_post",});
passport.use("oidc",new Strategy({ client,params: {redirect_uri: REDIRECT_URI,scope: "openid profile email phone",grant_type: "authorization_code",response_type: "code",},},(tokenset, userinfo, done) => {return done(null, userinfo);}));
passport.serializeUser(function(user, done) {done(null, user);}); passport.deserializeUser(function(user, done) {done(null, user);});const app = express(); app.use(session({secret: "secret",resave: true,saveUninitialized: true,})); app.use(passport.initialize()); app.use(passport.session()); app.listen(3010, () => console.log(`Example app listening at http://localhost:3010 🚀`));})();
完成登录逻辑
首先我们初始化一个登录路由:
app.get("/login", passport.authenticate("oidc"));app.get("/auth/callback", passport.authenticate("oidc", {successRedirect: "/user",failureRedirect: "/403",}));
使用其中任意一种登录方式登录之后,浏览器会跳转到 http://localhost:3000/auth/callback(这是我们第一步中在应用详情中配置的回调链接),在这里会向 Authing 服务器获取用户信息,获取用户信息成功之后再跳转到 /user 路由。
完成展示用户信息逻辑
接下来我们来实现 /user 路由的逻辑,前面介绍到用户登录成功之后用户信息会被挂载到 req.user 上,所以这里我们添加以下简单的逻辑:
app.get("/user", (req, res) => { res.send(req.user);});app.get("/session", (req, res) => { res.send(req.session);});
访问 /user 可以看到当前登录用户的用户信息:
访问 /session 可以看到当前登录用户的 session:
完成退出登录逻辑
最后我们实现退出登录逻辑:
1、首先通过 req.session.destroy() 清除当前应用的 session;
2、跳转到 OIDC 应用的退出登录链接。
const OIDC_LOGOUT_URL = "{{YOUR_APP_DOMAIN}}/login/profile/logout";const LOGOUT_REDIRECT_URL = "http://localhost:3000";
app.get("/logout", (req, res) => {
req.session.destroy();const logoutUrl = `${OIDC_LOGOUT_URL}?app_id=${OIDC_CLIENT_ID}&redirect_uri=${LOGOUT_REDIRECT_URL}`;
res.redirect(logoutUrl);});
在单页 Web 应用中接入 Authing
单页 Web 应用(Single Page Application,简称 SPA)指的是一种 Web 应用或者网站的模型,它通过动态重写当前页面来与用户交互,而非传统的从服务器重新加载整个新页面。这种方法避免了页面之间切换打断用户体验,使应用程序更像一个桌面应用程序。在单页 Web 应用中,所有必要的代码(HTML、JavaScript 和 CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态加载适当的资源并添加到页面。与单页 Web 应用的交互通常涉及到与后端服务器的动态通信。
在 SPA 应用中接入 Authing 最简单的方式是使用 Authing 提供的内嵌登录组件和 Javascript SDK 来进行登录和认证。本文以 React 项目为例。
获取应用 ID
登录 Authing 后,Authing 会为你创建一个默认用户池和应用,你也可以自己创建应用,在应用详情中,可以获取应用 ID,点击复制按钮复制即可:
安装 Authing 登录组件
yarn add @authing/react-ui-componentsORnpm i @authing/react-ui-components --save
@authing/react-ui-components 中有 Authing 提供的一些 React 组件和获取 AuthenticationClient 的 API,其中就包括 AuthingGuard 登录组件。
配置 AuthingGuard
import React from 'react'import ReactDOM from 'react-dom'import { Guard } from '@authing/react-ui-components'// 引入 css 文件import '@authing/react-ui-components/lib/index.min.css'const App = () => {const appId = 'AUTHING_APP_ID'// 登录成功const onLogin = (userInfo) => { console.log(userInfo)// 这里可以重定向到其他页面了// ...}return <AuthingGuard appId={appId} onLogin={onLogin} />}ReactDOM.render(<App />, root)
通过传入 appId,AuthingGuard 就可以展示登录框进行登录了。
退出登录
现在你已经可以登录了,同时需要一个方法使用户登出,可以通过 AuthenticationClient 实现。
// src/index.js
import { initAuthClient } from '@authing/react-ui-components'// 在项目入口文件中初始化 AuthenticationClientinitAuthClient({ appId: 'AUTHING_APP_ID',})
import React from 'react'import { getAuthClient } from '@authing/react-ui-components'
const LogoutButton = () => { return <button onClick={() => getAuthClient().logout()}>退出</button>}
export default LogoutButton
获取用户信息
用户登录后,你可能还需要获取当前登录用户的用户信息。
// src/index.js
import { initAuthClient } from '@authing/react-ui-components'// 在项目入口文件中初始化 AuthenticationClientinitAuthClient({ appId: 'AUTHING_APP_ID',})
import React, { useState, useEffect } from 'react'import { getAuthClient } from '@authing/react-ui-components'
const UserInfo = () => { const [user, setUser] = useState() const [isAuthenticated, setIsAuthenticated] = useState(true)
useEffect(() => { getAuthClient() .getCurrentUser() .then((userInfo) => { if (userInfo) { setUser(userInfo) } else { setIsAuthenticated(false) } }) }, [])
return isAuthenticated ? ( user ? ( <div> <img src={user.photo} alt={user.username} /> <h2>{user.username}</h2> <p>{user.email}</p> </div> ) : ( <div>Loading...</div> ) ) : ( <h3>暂未登录</h3> )}
export default UserInfo
getCurrentUser 能获取当前登录用户的信息,若未登录会返回 null。
在客户端应用中接入 Authing
Authing 提供 Android SDK 和 iOS SDK 帮助开发者在移动 APP 中快速集成 Authing。
下面以 Android 应用的集成方式为例。
安装
下载 jar 包并将 jar 包导入 lib
Jar 包下载地址:
https://download.authing.cn/packages/jar/commons-codec-1.15-rep.jar(opens new window)
https://download.authing.cn/packages/jar/core.jar(opens new window)
将 Jar 包导入 lib,如下图所示:
配置 build.gradle
implementation "com.google.code.gson:gson:2.8.6"implementation "com.squareup.okhttp3:okhttp:4.8.0"implementation files('libs/core.jar')implementation files('libs/commons-codec-1.15-rep.jar')
安装 Authing Java/Kotlin SDK。
用户注册登录
Authing Java SDK 支持手机号验证码、邮箱、用户名等多种注册登录方式,以手机号验证码登录为例:
发送验证码
String phone = "phone number";authenticationClient.sendSmsCode(phone).execute();
使用验证码登录
String phone = "phone number";String code = "1234";User user = authenticationClient.loginByPhoneCode(new LoginByPhoneCodeInput(phone, code)).execute();
集成微信登录
你可以使用 AuthenticationClient 的 loginByWechat 方法,所需四个参数均为微信返回的参数:
val authenticationClient = AuthenticationClient("YOUR_USERPOOL_ID")
val code = "#returned code from wechat#";
authenticationClient.loginByWechat(code).enqueue(object: cn.authing.core.http.Callback<User> { override fun onFailure(error: ErrorInfo?) {
}
override fun onSuccess(result: User) { val user = result }})