Ory Kratos 用户认证

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

了解 Kratos

获取代码

1git clone -b v0.7.0-alpha.1 --depth 1 https://github.com/ory/kratos.git

查看 API

go-swagger 查看:

1cd kratos 2swagger serve -F=swagger ./spec/swagger.json

运行服务

docker-compose 运行:

1cd kratos 2docker-compose -f quickstart.yml -f quickstart-postgres.yml -f quickstart-standalone.yml up --build --force-recreate 3# If you have SELinux, run: -f quickstart-selinux.yml

运行了官方 Quickstart 例子,可以访问 http://127.0.0.1:4455/dashboard 体验。

查看 DB

pgAdmin 打开(DB 信息见 quickstart-postgres.yml):

查看表:

查看配置

1cd kratos 2cat contrib/quickstart/kratos/email-password/kratos.yml

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

<!-- docker exec -it kratos_kratos_1 sh cat /etc/config/kratos/kratos.yml -->

Self-Service 流程

浏览器流程

客户端流程

动手配置: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)

配置文件

启动文件

运行服务

1cd ory-kratos 2docker-compose -f start.yml up --build --force-recreate

如果想运行官方 Self-Service UI 例子,那么:

1docker-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 接收邮件。

<!-- admin@mail.com, ad@WSXcde3$, admin, ad, min -->

动手实现:浏览器流程

React + Ant Design

<!-- - [Create React App](https://create-react-app.dev/) -->

新建 React 应用

<!-- sudo npm update -g npm npm-check -gu yarn global upgrade yarn global add eslint eslint --init -->
1yarn create react-app my-web --template typescript 2cd my-web 3yarn start

访问 http://localhost:3000/ ,可见 React 欢迎页。

引入 AntD

1yarn add antd

修改 src/App.tsx,引入 antd 组件:

1import React, { Component } from 'react' 2import { Button } from 'antd'; 3import logo from './logo.svg'; 4import './App.css'; 5 6class App extends Component { 7 render() { 8 return ( 9 <div className="App"> 10 <header className="App-header"> 11 <img src={logo} className="App-logo" alt="logo" /> 12 <Button type="primary">Button</Button> 13 </header> 14 </div> 15 ); 16 } 17} 18 19export default App;

修改 src/App.css,引入 antd 样式:

1@import '~antd/dist/antd.css';

可见 antd 蓝色按钮组件。

引入 Sass

1yarn add node-sass

后缀 css 改为 scsstsx 里的 import 也改下。

引入 Router

1yarn add react-router-dom @types/react-router-dom

pages 目录下实现如下页面 UI:

1src/pages 功能 路由 2├── dashboard.tsx 主页 /, /dashboard 3├── error.tsx 错误 /error 4├── login.tsx 登录 /auth/login 5├── recovery.tsx 恢复 /recovery 6├── registration.tsx 注册 /auth/registration 7├── settings.tsx 设置 /settings 8└── verification.tsx 验证 /verify

引入 SDK

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

注册

APIs:

  • GET /self-service/registration/browser: 初始化注册流程
  • GET /self-service/registration/flows: 获取注册流程
  • POST /self-service/registration: 提交注册流程

页面加载后的处理流程:

1componentDidMount() { 2 // 获取 flow id 参数 3 const flowId = utils.parseUrlQuery("flow", this.props.location) as string; 4 5 // 没有 flow id,初始化注册流程 6 if (!flowId || !utils.isString(flowId)) { 7 console.log("No flow ID found in URL, initializing registration flow."); 8 utils.redirectToSelfService("/self-service/registration/browser"); 9 return; 10 } 11 12 // 根据 flow id,获取注册流程信息 13 authPublicApi 14 .getSelfServiceRegistrationFlow(flowId, undefined, { 15 withCredentials: true, 16 }) 17 .then((res: AxiosResponse<SelfServiceRegistrationFlow>) => { 18 if (utils.assertResponse(res)) { 19 utils.redirectToSelfService("/self-service/registration/browser"); 20 return; 21 } 22 this.setState({ flowId: flowId, flow: res.data }); 23 }) 24 .catch(utils.redirectOnError); 25}

流程信息 this.state.flow,如下:

1{ 2 "id": "74c643a1-f302-45c9-a760-1ad7b1157e1c", 3 "type": "browser", 4 "expires_at": "2021-07-20T05:22:30.958717Z", 5 "issued_at": "2021-07-20T05:12:30.958717Z", 6 "request_url": "http://127.0.0.1:4433/self-service/registration/browser", 7 "ui": { 8 "action": "http://127.0.0.1:4433/self-service/registration?flow=74c643a1-f302-45c9-a760-1ad7b1157e1c", 9 "method": "POST", 10 "nodes": [{ 11 "type": "input", 12 "group": "default", 13 "attributes": { 14 "name": "csrf_token", 15 "type": "hidden", 16 "value": "QQyUDHa4KJ3M6mowHHN4pboN4iaUOZL+4gYVtKYRWzSdWjSNcW5dG/SNzocyqqqAtV48KzQVMIC6X+Pv3tNPNw==", 17 "required": true, 18 "disabled": false 19 }, 20 "messages": [], 21 "meta": {} 22 }, { 23 "type": "input", 24 "group": "password", 25 "attributes": { 26 "name": "traits.email", 27 "type": "email", 28 "disabled": false 29 }, 30 "messages": [], 31 "meta": { 32 "label": { 33 "id": 1070002, 34 "text": "E-Mail", 35 "type": "info" 36 } 37 } 38 }, { 39 ... 40 }] 41 } 42}

之后,依据流程信息创建表单:

1<Card title="Register new account" bordered={false}> 2 {/* 流程消息展示 */} 3 {this.state.flow.ui.messages && 4 this.state.flow.ui.messages.map((m: UiText, index) => ( 5 <Alert 6 key={index} 7 message={m.text} 8 type={m.type as AlertProps["type"]} 9 style={{ marginBottom: 16 }} 10 showIcon 11 /> 12 ))} 13 {/* 流程表单创建 */} 14 <Form 15 name="register" 16 ref={this.formRef} 17 encType="application/x-www-form-urlencoded" 18 action={this.state.flow.ui.action} 19 method={this.state.flow.ui.method} 20 onFinish={onFinish} 21 > 22 {this.state.flow.ui.nodes.map((node, index) => { 23 return React.cloneElement(ui.toUiNodeAntd(node)!, { 24 key: index, 25 }); 26 })} 27 </Form> 28</Card>

其中表单 onFinish 里处理提交:

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

登录

  • GET /self-service/login/browser: 初始化登录流程
  • GET /self-service/login/flows: 获取登录流程
  • POST /self-service/login: 提交登录流程

与注册流程一样。

登录后,可通过 whoami 获取授权信息:

  • GET /sessions/whoami: 获取授权信息
1authPublicApi 2 .toSession(undefined, undefined, { 3 withCredentials: true, 4 }) 5 .then((res: AxiosResponse<Session>) => { 6 if (utils.assertResponse(res)) { 7 utils.redirectToSelfService("/self-service/login/browser"); 8 return; 9 } 10 this.setState({ session: res.data }); 11 }) 12 .catch((err: AxiosError) => utils.redirectOnError(err, "/auth/login"));

Dashboard 页展示了授权信息:

验证

  • 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 构建多个表单:

1const nodesGroup: Record< 2 string, 3 { 4 title?: string; 5 nodes?: Array<UiNode>; 6 } 7> = { 8 default: {}, 9 profile: { title: "Profile" }, 10 password: { title: "Password" }, 11 oidc: { title: "Social Sign In" }, 12}; 13for (const [k, v] of Object.entries(nodesGroup)) { 14 nodesGroup[k] = { 15 title: v.title, 16 nodes: ui.onlyNodes(this.state.flow!.ui.nodes, k), 17 }; 18}
1<Card title="Settings" bordered={false}> 2 {this.state.flow.ui.messages && 3 this.state.flow.ui.messages.map((m: UiText, index) => ( 4 <Alert 5 key={index} 6 message={m.text} 7 type={m.type as AlertProps["type"]} 8 style={{ marginBottom: 16 }} 9 showIcon 10 /> 11 ))} 12 {/* Split Form by group here. Otherwise, one AntD Form method conflicts. */} 13 {Object.entries(nodesGroup) 14 .filter(([k, v]) => k !== "default" && v && v.nodes!.length > 0) 15 .map(([k, v], index) => ( 16 <Form 17 key={index} 18 name={k} 19 encType="application/x-www-form-urlencoded" 20 action={this.state.flow!.ui.action} 21 method={this.state.flow!.ui.method} 22 onFinish={onFinish} 23 > 24 <Form.Item> 25 <div>{v.title}</div> 26 </Form.Item> 27 {v 28 .nodes!.concat(nodesGroup["default"].nodes!) 29 .map((node, index) => { 30 return React.cloneElement(ui.toUiNodeAntd(node)!, { 31 key: index, 32 }); 33 })} 34 </Form> 35 ))} 36</Card>

登出

  • GET /self-service/logout/browser: 创建登出 URL
  • POST /self-service/logout: 完成登出流程

页面加载后创建登出 URL ,

1authPublicApi 2 .createSelfServiceLogoutFlowUrlForBrowsers(undefined, { 3 withCredentials: true, 4 }) 5 .then((res: AxiosResponse<SelfServiceLogoutUrl>) => { 6 this.setState({ logoutUrl: res.data.logout_url }); 7 }) 8 .catch((err) => { 9 // console.log(err); 10 });

之后,页面加上登出按钮:

1{this.state.logoutUrl && ( 2 <Button 3 type="link" 4 shape="circle" 5 href={this.state.logoutUrl} 6 icon={<LogoutOutlined />} 7 /> 8)}

参考

GoCoding 个人实践的经验分享,可关注公众号!

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Ory Kratos 用户认证 - HelloWorld