MaxKB v2 对话用户模拟登陆流程


飞致云 发布于 2025-12-31 / 0 阅读 / 0 评论 /
适用场景 需要自定义协议的单点登录的三方网站 不符合标准OIDC等协议的网站,但想要单点登录功能 对接自定义单点登录服务器 环境:v2.3.0 及以上专业版/企业版 同步账号 设置登录认证为账号登录,注册outer1账号作为测试。

适用场景

  • 需要自定义协议的单点登录的三方网站

  • 不符合标准OIDC等协议的网站,但想要单点登录功能

  • 对接自定义单点登录服务器

环境:v2.3.0 及以上专业版/企业版

同步账号

设置登录认证为账号登录,注册outer1账号作为测试。

实际应用中,请调用接口把三方平台的账号都同步到MaxKB v2,只需保证账号映射成功,密码等不一定必须与三方平台一致。

获取APIEKY

Python3创建用户参考代码(手机号、邮箱是可选项):batch_add_chatuser.py、

新增用户列表.xlsx

新建完毕后,赋予账号对应应用的权限,以正确访问应用。

参考代码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量新增聊天用户
"""
 
import json
import os
from typing import Any, Dict, List, Tuple
 
import requests
import urllib3
from openpyxl import load_workbook, Workbook
 
 
# 忽略证书校验告警(等价 --insecure)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
 
API_URL = 'http://192.168.1.102:8080/admin/api/system/chat_user'
INPUT_XLSX = '新增用户列表.xlsx'
OUTPUT_FAIL_XLSX = '入库失败列表.xlsx'
 
 
def ensure_iterable_group_ids(value: Any) -> List[str]:
    """将 user_group_ids 转换为字符串列表。
 
    支持以下输入:
    - 已是 list[str]
    - 逗号分隔字符串:"default,group2"
    - 单值字符串:"default"
    - JSON 字符串:"[\"default\"]"
    过滤空白项并去重,保持顺序。
    """
    if value is None:
        return []
    if isinstance(value, list):
        return [str(x).strip() for x in value if str(x).strip()]
    if isinstance(value, (set, tuple)):
        return [str(x).strip() for x in value if str(x).strip()]
 
    text = str(value).strip()
    if not text:
        return []
    # 尝试解析 JSON 数组
    if (text.startswith('[') and text.endswith(']')) or (text.startswith('{') and text.endswith('}')):
        try:
            parsed = json.loads(text)
            return ensure_iterable_group_ids(parsed)
        except Exception:
            pass
    # 逗号分隔
    if ',' in text:
        parts = [p.strip() for p in text.split(',') if p.strip()]
        # 去重保持顺序
        seen = set()
        result: List[str] = []
        for p in parts:
            if p not in seen:
                seen.add(p)
                result.append(p)
        return result
    return [text]
 
 
def read_rows_from_excel(path: str) -> List[Dict[str, Any]]:
    """读取 Excel 的所有行,返回字典列表。若缺少必要表头则抛出异常。"""
    if not os.path.exists(path):
        raise FileNotFoundError(f'未找到输入文件:{path}')
 
    wb = load_workbook(filename=path)
    ws = wb.active
 
    rows = list(ws.iter_rows(values_only=True))
    if not rows:
        return []
 
    headers = [str(h).strip() if h is not None else '' for h in rows[0]]
    required = ['username', 'email', 'password', 'phone', 'nick_name', 'user_group_ids']
    missing = [h for h in required if h not in headers]
    if missing:
        raise ValueError(f'表头缺失:{", ".join(missing)},实际表头:{headers}')
 
    header_index = {h: headers.index(h) for h in headers}
 
    records: List[Dict[str, Any]] = []
    for r in rows[1:]:
        if r is None:
            continue
        record: Dict[str, Any] = {}
        for h in headers:
            idx = header_index[h]
            value = r[idx] if idx < len(r) else None
            record[h] = value
        records.append(record)
    return records
 
 
def build_headers(bearer_token: str) -> Dict[str, str]:
    return {
        'AUTHORIZATION': f'Bearer {bearer_token}',
        'Accept': 'application/json, text/plain, */*',
        'Accept-Language': 'zh-CN',
        'Content-Type': 'application/json',
    }
 
 
def call_create_user(headers: Dict[str, str], payload: Dict[str, Any]) -> Tuple[bool, Dict[str, Any]]:
    """调用创建用户接口,返回(是否成功, 响应JSON或错误信息)。"""
    try:
        resp = requests.post(API_URL, headers=headers, json=payload, verify=False, timeout=30)
        resp.raise_for_status()
        try:
            data = resp.json()
        except json.JSONDecodeError:
            return False, {'message': '返回非JSON', 'raw': resp.text}
 
        if data.get('code') == 200:
            return True, data
        return False, {'code': data.get('code'), 'message': data.get('message'), 'raw': data}
    except requests.RequestException as e:
        return False, {'message': f'网络/请求异常: {e}'}
 
 
def export_failures(fail_items: List[Dict[str, Any]], path: str) -> None:
    """导出失败明细到 Excel。"""
    wb = Workbook()
    ws = wb.active
    ws.title = '失败列表'
 
    headers = ['row_number', 'username', 'email', 'message', 'code', 'raw']
    ws.append(headers)
    for item in fail_items:
        ws.append([
            item.get('row_number'),
            item.get('username'),
            item.get('email'),
            item.get('message'),
            item.get('code'),
            json.dumps(item.get('raw'), ensure_ascii=False) if isinstance(item.get('raw'), (dict, list)) else item.get('raw'),
        ])
 
    wb.save(path)
 
 
def main():
    # 在此填写登录获取的 token(纯 token,不带 "Bearer ")
    token = 'user-xxx'  # TODO: 将此处替换为实际 token
    if not token:
        print('请先在脚本中填写 token 变量(仅 token 值,不包含 "Bearer ")。')
        return
 
    try:
        records = read_rows_from_excel(INPUT_XLSX)
    except Exception as e:
        print(f'读取 Excel 失败:{e}')
        return
 
    if not records:
        print('Excel 无数据可处理。')
        return
 
    headers = build_headers(token)
 
    failures: List[Dict[str, Any]] = []
    success_count = 0
 
    for idx, rec in enumerate(records, start=2):  # 从第2行开始(Excel 人类可读行号)
        username = (rec.get('username') or '').strip()
        email = (rec.get('email') or '').strip()
        password = str(rec.get('password') or '').strip()
        phone = str(rec.get('phone') or '').strip()
        nick_name = (rec.get('nick_name') or '').strip()
        group_ids = ensure_iterable_group_ids(rec.get('user_group_ids'))
 
        if not username or not password:
            failures.append({
                'row_number': idx,
                'username': username,
                'email': email,
                'message': '缺少必填字段 username 或 password',
                'code': None,
                'raw': None,
            })
            continue
 
        payload = {
            'username': username,
            'email': email,
            'password': password,
            'phone': phone,
            'nick_name': nick_name or username,
            'user_group_ids': group_ids if group_ids else ['default'],
        }
 
        ok, resp = call_create_user(headers, payload)
        if ok:
            success_count += 1
        else:
            failures.append({
                'row_number': idx,
                'username': username,
                'email': email,
                'message': resp.get('message'),
                'code': resp.get('code'),
                'raw': resp.get('raw'),
            })
 
    print(f'导入完成:成功 {success_count} 条,失败 {len(failures)} 条。')
    if failures:
        export_failures(failures, OUTPUT_FAIL_XLSX)
        print(f'已导出失败明细:{OUTPUT_FAIL_XLSX}')
 
 
if __name__ == '__main__':
    main()

表结构:

username

email

password

phone

nick_name

user_group_ids

outer2

outer2@163.com

MaxKB@123..

outer2

default

outer3

outer3@163.com

MaxKB@123..

outer3

default

outer4

outer4@163.com

MaxKB@123..

outer4

default

outer5

outer5@163.com

MaxKB@123..

outer5

default

outer1

outer1@163.com

MaxKB@123..

outer1

default

模拟登录

(1)复制应用 access token:  034cd435f182cfd7

如下链接:

(2)调用接口/chat/api/auth/login/accessToken

http://192.168.1.102:8080请改为实际访问的域名/IP地址。账号密码改为实际填写。

curl 'http://192.168.1.102:8080/chat/api/auth/login/034cd435f182cfd7' \

   -H 'Accept: application/json, text/plain, /' \

   -H 'Accept-Language: zh-CN' \

   -H 'Content-Type: application/json' \

   --data-raw '{"password":"MaxKB@123","username":"outer1"}' \

   --insecure

正确获取:

{"code": 200, "message": "\u6210\u529f", "data": {"token": "xxxx"}}

密码错误:

{"code": 500, "message": "\u7528\u6237\u540d\u6216\u5bc6\u7801\u4e0d\u6b63\u786e", "data": null}

获取token,此token为对话用户登录标识。

使用实际语言来编写代码,将三方平台应用账号信息经过验证正确无误后,映射到MaxKB v2的对话用户中。后续根据此token来标识MaxKB对话用户。

 

Python3 获取token参考代码:

#!/usr/bin/env python3
 
# -*- coding: utf-8 -*-
 
"""
 
获取登录 token 的脚本
 
"""
 
  
 
import requests
 
import json
 
import urllib3
 
  
 
# 禁用 SSL 警告(因为使用了 --insecure 选项)
 
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
  
 
  
 
def get_token():
 
    """
 
    发送登录请求并获取 token
 
    """
 
    url = 'http://192.168.1.102:8080/chat/api/auth/login/034cd435f182cfd7'
 
     
 
    headers = {
 
        'Accept': 'application/json, text/plain, */*',
 
        'Accept-Language': 'zh-CN',
 
        'Content-Type': 'application/json'
 
    }
 
     
 
    data = {
 
        "password": "MaxKB@123",
 
        "username": "outer1"
 
    }
 
     
 
    try:
 
        # 发送 POST 请求,verify=False 对应 curl 的 --insecure 选项
 
        response = requests.post(
 
            url,
 
            headers=headers,
 
            json=data,
 
            verify=False  # 忽略 SSL 证书验证
 
        )
 
         
 
        # 检查响应状态码
 
        response.raise_for_status()
 
         
 
        # 解析 JSON 响应
 
        result = response.json()
 
         
 
        # 检查响应代码
 
        if result.get('code') == 200:
 
            token = result.get('data', {}).get('token')
 
            if token:
 
                print(f"登录成功!Token: {token}")
 
                return token
 
            else:
 
                print("响应中未找到 token")
 
                return None
 
        else:
 
            print(f"登录失败: {result.get('message', '未知错误')}")
 
            return None
 
             
 
    except requests.exceptions.RequestException as e:
 
        print(f"请求错误: {e}")
 
        return None
 
    except json.JSONDecodeError as e:
 
        print(f"JSON 解析错误: {e}")
 
        print(f"响应内容: {response.text}")
 
        return None
 
  
 
  
 
if __name__ == '__main__':
 
    token = get_token()
 
    if token:
 
        # 可以将 token 保存到文件或返回
 
        pass

(3)设置session storage (或local storage,session是会话级别)

Key命名格式:034cd435f182cfd7-accessToken 。其中034cd435f182cfd7替换为应用的accessToken

Value为token值

可使用nginx部署到MaxKB同域域名,设置全局缓存

(4)验证登录

地址:http://192.168.1.102:8080/chat/034cd435f182cfd7

无需账号密码,直接登录成功:

(5)验证通过并设置缓存后,编写代码,重定向到应用地址

http://192.168.1.102:8080/chat/034cd435f182cfd7



是否对你有帮助?