zoukankan      html  css  js  c++  java
  • Ory Kratos 用户认证

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

    了解 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
    

    运行服务

    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):

    查看表:

    查看配置

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

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

    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)

    配置文件

    启动文件

    运行服务

    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 (
          <div className="App">
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <Button type="primary">Button</Button>
            </header>
          </div>
        );
      }
    }
    
    export default App;
    

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

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

    可见 antd 蓝色按钮组件。

    引入 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<SelfServiceRegistrationFlow>) => {
          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"
            }
          }
        }, {
        ...
        }]
      }
    }
    

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

    <Card title="Register new account" bordered={false}>
      {/* 流程消息展示 */}
      {this.state.flow.ui.messages &&
        this.state.flow.ui.messages.map((m: UiText, index) => (
          <Alert
            key={index}
            message={m.text}
            type={m.type as AlertProps["type"]}
            style={{ marginBottom: 16 }}
            showIcon
          />
        ))}
      {/* 流程表单创建 */}
      <Form
        name="register"
        ref={this.formRef}
        encType="application/x-www-form-urlencoded"
        action={this.state.flow.ui.action}
        method={this.state.flow.ui.method}
        onFinish={onFinish}
      >
        {this.state.flow.ui.nodes.map((node, index) => {
          return React.cloneElement(ui.toUiNodeAntd(node)!, {
            key: index,
          });
        })}
      </Form>
    </Card>
    

    其中表单 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<Session>) => {
        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 页展示了授权信息:

    验证

    • 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<UiNode>;
      }
    > = {
      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),
      };
    }
    
    <Card title="Settings" bordered={false}>
      {this.state.flow.ui.messages &&
        this.state.flow.ui.messages.map((m: UiText, index) => (
          <Alert
            key={index}
            message={m.text}
            type={m.type as AlertProps["type"]}
            style={{ marginBottom: 16 }}
            showIcon
          />
        ))}
      {/* 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) => (
          <Form
            key={index}
            name={k}
            encType="application/x-www-form-urlencoded"
            action={this.state.flow!.ui.action}
            method={this.state.flow!.ui.method}
            onFinish={onFinish}
          >
            <Form.Item>
              <div>{v.title}</div>
            </Form.Item>
            {v
              .nodes!.concat(nodesGroup["default"].nodes!)
              .map((node, index) => {
                return React.cloneElement(ui.toUiNodeAntd(node)!, {
                  key: index,
                });
              })}
          </Form>
        ))}
    </Card>
    

    登出

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

    页面加载后创建登出 URL ,

    authPublicApi
      .createSelfServiceLogoutFlowUrlForBrowsers(undefined, {
        withCredentials: true,
      })
      .then((res: AxiosResponse<SelfServiceLogoutUrl>) => {
        this.setState({ logoutUrl: res.data.logout_url });
      })
      .catch((err) => {
        // console.log(err);
      });
    

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

    {this.state.logoutUrl && (
      <Button
        type="link"
        shape="circle"
        href={this.state.logoutUrl}
        icon={<LogoutOutlined />}
      />
    )}
    

    参考

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

  • 相关阅读:
    leetcode------Remove Element
    leetcode------Merge Two Sorted Lists
    [转载]Unity3D 游戏引擎之使用C#语言建立本地数据库(SQLITE)
    [转载]VS2012创建MVC3项目提示错误: 此模板尝试加载组件程序集 “NuGet.VisualStudio.Interop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”。
    [转载]Unity3D 访问Access数据库
    [转载]C#导入XLS数据到数据库
    [转载]Unity3D的断点调试功能
    [Unity3D]支持的视频格式
    unity 3d 获取鼠标当前坐标
    [转载]VS2012程序打包部署详解
  • 原文地址:https://www.cnblogs.com/gocodinginmyway/p/15076852.html
Copyright © 2011-2022 走看看