Ory Kratos 用户认证

Ory Kratos 为用户认证与管理系统。本文将动手实现浏览器(React+AntD)的完整流程,实际了解下它的 API 。

  • 代码: https://github.com/ikuokuo/st...
了解 Kratos 获取代码
git clone -b v0.7.0-alpha.1 --depth 1 https://github.com/ory/kratos.git

查看 API
go-swagger 查看:
cd kratos swagger serve -F=swagger ./spec/swagger.json

Ory Kratos 用户认证
文章图片

运行服务
docker-compose 运行:
cd kratos docker-compose -f quickstart.yml -f quickstart-postgres.yml -f quickstart-standalone.yml up --build --force-recreate # If you have SELinux, run: -f quickstart-selinux.yml

运行了官方 Quickstart 例子,可以访问 http://127.0.0.1:4455/dashboard 体验。
查看 DB
pgAdmin 打开(DB 信息见 quickstart-postgres.yml):
Ory Kratos 用户认证
文章图片

查看表:
Ory Kratos 用户认证
文章图片

查看配置
cd kratos cat contrib/quickstart/kratos/email-password/kratos.yml

设置环境变量可以覆盖。以 _ 表示层级,如 SELFSERVICE_FLOWS_SETTINGS_UI_URL= 覆盖 selfservice.flows.settings.ui_url

Self-Service 流程
  • Registration
  • Login
  • Logout
  • User Settings
  • Account Recovery
  • Address Verification
  • User-Facing Error
  • 2FA / MFA
浏览器流程 Ory Kratos 用户认证
文章图片

客户端流程 Ory Kratos 用户认证
文章图片

动手配置:Kratos 服务
  • Ory Kratos
    • Public API (port 4433)
    • Admin API (port 4434)
    • Postgres DB (port 5432)
    • Browser Return URL (port 3000)
  • MailSlurper: a development SMTP server
    • Server UI (port 4436)
配置文件
  • ory-kratos/config/kratos.yml: 配置文件
  • ory-kratos/config/identity.schema.json: 认证 JSON 模式
启动文件
  • ory-kratos/start.yml: Docker Compose 文件
运行服务
cd ory-kratos docker-compose -f start.yml up --build --force-recreate

如果想运行官方 Self-Service UI 例子,那么:
docker-compose -f start.yml -f start-ui-node.yml up --build --force-recreate

之后,访问 http://127.0.0.1:3000/ 体验。在 Register new account / Reset password 时,可访问虚拟 SMTP 服务 http://127.0.0.1:4436 接收邮件。

动手实现:浏览器流程 React + Ant Design
新建 React 应用

yarn create react-app my-web --template typescript cd my-web yarn start

访问 http://localhost:3000/ ,可见 React 欢迎页。
引入 AntD
yarn add antd

修改 src/App.tsx,引入 antd 组件:
import React, { Component } from 'react' import { Button } from 'antd'; import logo from './logo.svg'; import './App.css'; class App extends Component { render() { return (Ory Kratos 用户认证
文章图片

引入 Sass
yarn add node-sass

后缀 css 改为 scsstsx 里的 import 也改下。
引入 Router
yarn add react-router-dom @types/react-router-dom

pages 目录下实现如下页面 UI:
src/pages功能路由 ├── dashboard.tsx主页/, /dashboard ├── error.tsx错误/error ├── login.tsx登录/auth/login ├── recovery.tsx恢复/recovery ├── registration.tsx注册/auth/registration ├── settings.tsx设置/settings └── verification.tsx验证/verify

引入 SDK
yarn add @ory/kratos-client@0.7.0-alpha.1

注册
APIs:
  • GET /self-service/registration/browser: 初始化注册流程
  • GET /self-service/registration/flows: 获取注册流程
  • POST /self-service/registration: 提交注册流程
页面加载后的处理流程:
componentDidMount() { // 获取 flow id 参数 const flowId = utils.parseUrlQuery("flow", this.props.location) as string; // 没有 flow id,初始化注册流程 if (!flowId || !utils.isString(flowId)) { console.log("No flow ID found in URL, initializing registration flow."); utils.redirectToSelfService("/self-service/registration/browser"); return; }// 根据 flow id,获取注册流程信息 authPublicApi .getSelfServiceRegistrationFlow(flowId, undefined, { withCredentials: true, }) .then((res: AxiosResponse) => { if (utils.assertResponse(res)) { utils.redirectToSelfService("/self-service/registration/browser"); return; } this.setState({ flowId: flowId, flow: res.data }); }) .catch(utils.redirectOnError); }

流程信息 this.state.flow,如下:
{ "id": "74c643a1-f302-45c9-a760-1ad7b1157e1c", "type": "browser", "expires_at": "2021-07-20T05:22:30.958717Z", "issued_at": "2021-07-20T05:12:30.958717Z", "request_url": "http://127.0.0.1:4433/self-service/registration/browser", "ui": { "action": "http://127.0.0.1:4433/self-service/registration?flow=74c643a1-f302-45c9-a760-1ad7b1157e1c", "method": "POST", "nodes": [{ "type": "input", "group": "default", "attributes": { "name": "csrf_token", "type": "hidden", "value": "QQyUDHa4KJ3M6mowHHN4pboN4iaUOZL+4gYVtKYRWzSdWjSNcW5dG/SNzocyqqqAtV48KzQVMIC6X+Pv3tNPNw==", "required": true, "disabled": false }, "messages": [], "meta": {} }, { "type": "input", "group": "password", "attributes": { "name": "traits.email", "type": "email", "disabled": false }, "messages": [], "meta": { "label": { "id": 1070002, "text": "E-Mail", "type": "info" } } }, { ... }] } }

之后,依据流程信息创建表单:
{/* 流程消息展示 */} {this.state.flow.ui.messages && this.state.flow.ui.messages.map((m: UiText, index) => ())} {/* 流程表单创建 */}
{this.state.flow.ui.nodes.map((node, index) => { return React.cloneElement(ui.toUiNodeAntd(node)!, { key: index, }); })}

其中表单 onFinish 里处理提交:
const onFinish = (values: any) => { // 因 AntD Form 不提交原 HTML form,所以自己创建 from 提交 // - 不能直接 find form 提交,此时值已清空 // - 创建 from 提交,与 AntD From 相互无影响 ui.submitViaForm(this.state.flow!.ui, values); // 或者,用 `/self-service/registration/api` 提交 // this.submitViaApi(values); };

登录
  • GET /self-service/login/browser: 初始化登录流程
  • GET /self-service/login/flows: 获取登录流程
  • POST /self-service/login: 提交登录流程
与注册流程一样。
登录后,可通过 whoami 获取授权信息:
  • GET /sessions/whoami: 获取授权信息
authPublicApi .toSession(undefined, undefined, { withCredentials: true, }) .then((res: AxiosResponse) => { if (utils.assertResponse(res)) { utils.redirectToSelfService("/self-service/login/browser"); return; } this.setState({ session: res.data }); }) .catch((err: AxiosError) => utils.redirectOnError(err, "/auth/login"));

Dashboard 页展示了授权信息:
Ory Kratos 用户认证
文章图片

验证
  • GET /self-service/verification/browser: 初始化验证流程
  • GET /self-service/verification/flows: 获取验证流程
  • POST /self-service/verification: 提交验证流程
与注册流程一样。
恢复
  • GET /self-service/recovery/browser: 初始化恢复流程
  • GET /self-service/recovery/flows: 获取恢复流程
  • POST /self-service/recovery: 提交恢复流程
与注册流程一样。
设置
  • GET /self-service/settings/browser: 初始化设置流程
  • GET /self-service/settings/flows: 获取设置流程
  • POST /self-service/settings: 完成设置流程
与注册流程一样。
但要注意的是,依据流程信息创建表单时,请区分 group 构建多个表单:
const nodesGroup: Record< string, { title?: string; nodes?: Array; } > = { default: {}, profile: { title: "Profile" }, password: { title: "Password" }, oidc: { title: "Social Sign In" }, }; for (const [k, v] of Object.entries(nodesGroup)) { nodesGroup[k] = { title: v.title, nodes: ui.onlyNodes(this.state.flow!.ui.nodes, k), }; }

{this.state.flow.ui.messages && this.state.flow.ui.messages.map((m: UiText, index) => ())} {/* Split Form by group here. Otherwise, one AntD Form method conflicts. */} {Object.entries(nodesGroup) .filter(([k, v]) => k !== "default" && v && v.nodes!.length > 0) .map(([k, v], index) => (
{v.title} {v .nodes!.concat(nodesGroup["default"].nodes!) .map((node, index) => { return React.cloneElement(ui.toUiNodeAntd(node)!, { key: index, }); })}
))}

Ory Kratos 用户认证
文章图片

登出
  • GET /self-service/logout/browser: 创建登出 URL
  • POST /self-service/logout: 完成登出流程
页面加载后创建登出 URL ,
authPublicApi .createSelfServiceLogoutFlowUrlForBrowsers(undefined, { withCredentials: true, }) .then((res: AxiosResponse) => { this.setState({ logoutUrl: res.data.logout_url }); }) .catch((err) => { // console.log(err); });

【Ory Kratos 用户认证】之后,页面加上登出按钮:
{this.state.logoutUrl && (

参考
  • ORY Kratos ExpressJS Self-Service UI Reference
  • Kratos React Example
GoCoding 个人实践的经验分享,可关注公众号!

    推荐阅读