@cicctencent/base-server
v0.1.3
Published
企业级基础服务组件 - 基于 MidwayJS 的 SSO 单点登录系统后端,提供用户认证、会话管理、应用管理、微信生态集成等核心能力
Readme
@fefeding/base-server
企业级基础服务组件 — MidwayJS 实现
简介
@fefeding/base-server 是基于 MidwayJS 框架的企业级基础服务,提供完整的 OAuth 2.0 / OIDC 标准认证、用户认证、会话管理、应用管理、微信生态集成等核心能力。作为 SSO 单点登录系统的后端 API 服务,处理所有认证逻辑和数据库操作。
核心协议:OAuth 2.0 + OpenID Connect(OIDC)标准,同时兼容旧版自定义 auth_code 协议(仅用于存量应用过渡)。
双重部署模式:
- 作为独立微服务部署,提供 HTTP API 接口
- 作为 MidwayJS 组件(
@fefeding/base-server)集成到其他项目(合并部署)
部署模式对比
| 特性 | 独立部署 | 合并部署(组件模式) | |------|---------|-------------------| | 部署方式 | 独立进程,HTTP API 调用 | 作为 npm 包导入,直接调用 Service | | 网络开销 | 有 HTTP 请求开销 | 零网络开销(同进程调用) | | 扩展性 | 可独立扩展 | 随主应用扩展 | | 数据库 | 独立配置 | 共享主应用的数据库连接 | | 中间件 | 全部启用(限流等) | 中间件由主应用控制 | | 适用场景 | 微服务架构、多团队协作 | 单体应用、快速开发、简单运维 |
系统架构
┌───────────────────────────────────────────────────────────────┐
│ SSO Server (Web 层) │
│ 登录页面渲染 / 会话接口代理 / 管理端代理 │
└───────────────────────────┬───────────────────────────────────┘
│ HTTP API (API_KEY 鉴权)
┌───────────────────────────┼───────────────────────────────────┐
│ Base Server (本项目) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Controller 层 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Session │ │ Account │ │ App │ │ │
│ │ │ 会话管理 │ │ 账户管理 │ │ 应用管理 │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Wx │ │ Address │ │ │
│ │ │ 微信生态 │ │ IP地址 │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ │ │
│ └─────────┼────────────────┼────────────────┼────────────┘ │
│ │ │ │ │
│ ┌─────────┼────────────────┼────────────────┼────────────┐ │
│ │ │ Service 层 │ │ │
│ │ SessionService AccountService AppService │ │
│ │ UserService AuthMapService WxService │ │
│ │ WkService VerificationCodeService │ │
│ │ AccessTokenService WxMessageService │ │
│ │ BaiduOcrService PermissionService │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────┼──────────────────────────────┐ │
│ │ Model 层 (TypeORM) │ │
│ │ Account | User | Session | App | AuthMap │ │
│ │ VerificationCode | AppAccessToken | WxMessage │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────┴──────────────────────────────┐ │
│ │ MySQL (db_account) │ │
│ └──────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────┘核心功能
OAuth2 + OIDC 标准支持(核心协议)
- 标准端点:Token、UserInfo、JWKS、Discovery、Revocation
- 客户端管理:注册、查询、密钥重新生成
- 安全增强:PKCE(防授权码拦截)、nonce(防重放攻击)、state(防 CSRF)
- Token 类型:access_token、id_token、refresh_token
- 签名算法:RS256(RSA 密钥对)
- 自动发现:OIDC Discovery 文档
- 标准库支持:兼容主流 OAuth2 客户端库
多方式登录认证
| 登录方式 | 接口 | 说明 |
|---------|------|------|
| 账号密码 | POST /api/session/loginByAccount | 传统账号密码认证 |
| 微信小程序 | POST /api/session/loginByWx | appType=WxMiniApp,通过 code 换取 openid |
| 微信公众号 | POST /api/session/loginByWx | appType=WxGzh,OAuth2 授权码模式 |
| 企业微信 Web | POST /api/session/loginByWx | appType=WkWeb,企业微信 OAuth2 |
| 企业微信小程序 | POST /api/session/loginByWx | appType=WkMiniApp |
| 公众号扫码 | POST /api/session/createWxLoginQrcode | 生成带参数二维码,扫码事件触发登录 |
| 验证码登录 | POST /api/session/loginByVerifyCode | 通过验证码(如公众号扫码验证码)登录 |
| 授权码登录 | POST /api/session/loginByAuthCode | 旧协议,用临时授权码换 Session(将废弃) |
会话管理
- Session 创建与生命周期管理(自动续期)
- Session 缓存(内存缓存 + 数据库持久化)
- 登出与状态管理(ACTIVE / OFFLINE / INACTIVATED)
账户与用户管理
- 账户注册、查询、修改、删除
- 密码修改与验证
- 用户信息管理(头像、昵称等)
- 微信 openid / unionid 自动关联
应用管理
- 多应用配置(支持微信小程序、公众号、企业微信等类型)
- App AccessToken 自动管理与刷新
- 应用密钥安全管理
微信生态集成
- 微信公众号:消息接收、用户信息获取、菜单管理、素材管理、草稿与发布
- 微信小程序:code2Session、手机号获取
- 企业微信:OAuth2 登录、用户身份获取
安全防护
- API Token 验证(
@decorators.checkApiToken) - IP 白名单控制(
@decorators.checkIP) - 登录态校验(
@decorators.checkLogin) - 腾讯云短信验证码
OAuth2 + OIDC 标准支持(v0.0.42 新增)
- 标准端点:Token、UserInfo、JWKS、Discovery、Revocation
- 客户端管理:注册、查询、密钥重新生成
- 安全增强:PKCE(防授权码拦截)、nonce(防重放攻击)
- Token 类型:access_token、id_token、refresh_token
- 签名算法:RS256(RSA 密钥对)
- 自动发现:OIDC Discovery 文档
- 标准库支持:兼容主流 OAuth2 客户端库
项目结构
base-server/
├── src/
│ ├── config/ # 配置文件
│ │ ├── config.default.ts # 默认配置
│ │ ├── config.local.ts # 本地开发配置
│ │ └── devops.config.ts # 部署配置
│ ├── controller/ # 控制器
│ │ ├── session.controller.ts # 会话管理(登录/登出/授权码)
│ │ ├── account.controller.ts # 账户管理(CRUD/密码修改)
│ │ ├── app.controller.ts # 应用管理(CRUD/AccessToken)
│ │ ├── oauth.controller.ts # OAuth2/OIDC 标准端点
│ │ ├── wx.controller.ts # 微信生态(消息/素材/菜单/手机号)
│ │ └── address.controller.ts # IP 地址查询
│ ├── service/ # 服务层
│ │ ├── session.service.ts # 会话核心服务
│ │ ├── account.service.ts # 账户服务
│ │ ├── user.service.ts # 用户服务
│ │ ├── app.service.ts # 应用服务
│ │ ├── authMap.service.ts # 授权码映射服务(支持OAuth2)
│ │ ├── oauthClient.service.ts # OAuth2 客户端管理服务
│ │ ├── jwt.service.ts # JWT 签名与验证服务(RSA)
│ │ ├── accessToken.service.ts # App AccessToken 服务
│ │ ├── verificationCode.service.ts # 验证码服务
│ │ ├── wxMessage.service.ts # 微信消息服务
│ │ ├── platformSdk/ # 第三方平台 SDK 封装
│ │ │ ├── wx.service.ts # 微信 API
│ │ │ ├── wk.service.ts # 企业微信 API
│ │ │ └── baiduOcr.service.ts # 百度 OCR 服务
│ │ └── permission/ # 权限服务
│ │ └── psermission.service.ts
│ ├── model/ # TypeORM 实体模型
│ │ ├── account.entity.ts # 账户表
│ │ ├── user.entity.ts # 用户表
│ │ ├── session.entity.ts # 会话表
│ │ ├── app.entity.ts # 应用表
│ │ ├── authMap.entity.ts # 授权码映射表(支持OAuth2)
│ │ ├── oauthClient.entity.ts # OAuth2 客户端表
│ │ ├── appAccessToken.entity.ts # 应用 AccessToken 表
│ │ ├── verificationCode.entity.ts # 验证码表
│ │ └── wxMessage.entity.ts # 微信消息记录表
│ ├── dto/ # 数据传输对象
│ │ ├── session.dto.ts # 会话相关DTO
│ │ ├── oauth.dto.ts # OAuth2相关DTO
│ │ └── ...
│ ├── middleware/ # 中间件
│ ├── filter/ # 过滤器
│ ├── configuration.ts # MidwayJS 配置入口
│ └── index.ts # 导出(组件模式入口)
├── data/
│ ├── db_account.sql # 数据库初始化脚本
│ └── t_oauth_client.sql # OAuth2客户端表初始化脚本
├── bootstrap.js # 生产启动入口
├── server.js # TARS 配置加载入口
└── Dockerfile # Docker 构建文件快速开始
作为独立服务部署
1. 安装依赖
npm install2. 环境配置
复制 .tpl.env 为 .env,修改以下关键配置:
# ==================== 服务配置 ====================
IP=0.0.0.0 # 监听地址
PORT=8089 # 服务端口
PREFIX=base-server # 路由前缀(如 /base-server)
NODE_ENV=production # 运行环境
# ==================== API 鉴权 ====================
API_KEY="2024@fefeding#" # API 鉴权密钥(调用方需在请求头 x-api-key 中携带)
APP_ID=2 # 自身应用ID
# ==================== SSO 地址 ====================
SSO_URL=https://base.ycnull.com/sso # SSO 服务地址(用于生成回调链接)
# ==================== Session 配置 ====================
SESSION_TIMEOUT=86400000 # Session 超时时间(毫秒,默认 24 小时)
# ==================== OAuth2 + OIDC 配置 ====================
OAUTH_ISSUER=sso-server # OAuth2 Issuer 标识
OAUTH_TOKEN_VALIDITY=3600 # access_token 有效期(秒,默认 1 小时)
OAUTH_REFRESH_VALIDITY=2592000 # refresh_token 有效期(秒,默认 30 天)
JWT_PRIVATE_KEY= # RSA 私钥(PEM 格式字符串,推荐)
JWT_PUBLIC_KEY= # RSA 公钥(PEM 格式字符串,推荐)
# 或使用文件路径
JWT_PRIVATE_KEY_PATH=/path/to/private.pem
JWT_PUBLIC_KEY_PATH=/path/to/public.pem
# ==================== 数据库配置 ====================
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=123456
DB_DATABASE=db_account
# ==================== IP 白名单 ====================
IP_WHITE_LIST=58.251.36.54 # 允许访问的 IP,逗号分隔
# ==================== 腾讯云配置 ====================
TENCENT_CREDENTIAL_SECRETID=
TENCENT_CREDENTIAL_SECRETKEY=
# 腾讯云 COS 配置
COS_DEFAULT_BUCKET=base-1331355487
COS_DEFAULT_REGION=ap-guangzhou
# 腾讯云短信配置
TENCENT_SECRET_ID=
TENCENT_SECRET_KEY=
SMS_SDK_APP_ID=
SMS_SIGN_NAME=
# ==================== 日志配置 ====================
LOG_LEVEL=info # 日志级别(debug/info/warn/error)
REMOTE_LOGGER_URL= # 远程日志服务地址(可选)
# ==================== TARS 配置(生产环境) ====================
# TARS_CONFIG= # TARS 配置服务地址环境变量详细说明
服务配置
| 变量 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| IP | string | 0.0.0.0 | 服务监听地址 |
| PORT | number | 8089 | 服务端口 |
| PREFIX | string | base-server | 全局路由前缀 |
| NODE_ENV | string | - | 运行环境(production/development/local) |
API 鉴权
| 变量 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| API_KEY | string | - | API 鉴权密钥,调用方需在请求头 x-api-key 中携带 |
| APP_ID | number | - | 自身应用ID |
Session 与安全
| 变量 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| SESSION_TIMEOUT | number | 86400000 | Session 无活动过期时间(毫秒),默认 24 小时 |
| IP_WHITE_LIST | string | - | IP 白名单,逗号分隔 |
| SSO_URL | string | - | SSO 服务地址,用于生成回调链接 |
数据库
| 变量 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| DB_HOST | string | - | MySQL 主机地址 |
| DB_PORT | number | 3306 | MySQL 端口 |
| DB_USERNAME | string | - | 数据库用户名 |
| DB_PASSWORD | string | - | 数据库密码 |
| DB_DATABASE | string | db_account | 数据库名 |
OAuth2 / OIDC
| 变量 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| OAUTH_ISSUER | string | sso-server | OAuth2 Issuer 标识 |
| OAUTH_TOKEN_VALIDITY | number | 3600 | access_token 有效期(秒) |
| OAUTH_REFRESH_VALIDITY | number | 2592000 | refresh_token 有效期(秒),默认 30 天 |
| JWT_PRIVATE_KEY | string | - | RSA 私钥(PEM 格式字符串,优先于文件路径) |
| JWT_PUBLIC_KEY | string | - | RSA 公钥(PEM 格式字符串,优先于文件路径) |
| JWT_PRIVATE_KEY_PATH | string | - | RSA 私钥文件路径 |
| JWT_PUBLIC_KEY_PATH | string | - | RSA 公钥文件路径 |
腾讯云服务
| 变量 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| TENCENT_CREDENTIAL_SECRETID | string | - | 腾讯云 SecretId |
| TENCENT_CREDENTIAL_SECRETKEY | string | - | 腾讯云 SecretKey |
| COS_DEFAULT_BUCKET | string | - | COS 存储桶名称 |
| COS_DEFAULT_REGION | string | ap-guangzhou | COS 存储桶地域 |
| TENCENT_SECRET_ID | string | - | 短信服务 SecretId |
| TENCENT_SECRET_KEY | string | - | 短信服务 SecretKey |
| SMS_SDK_APP_ID | string | - | 短信应用 ID |
| SMS_SIGN_NAME | string | - | 短信签名 |
其他
| 变量 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| LOG_LEVEL | string | info | 日志级别 |
| REMOTE_LOGGER_URL | string | - | 远程日志服务地址 |
| TARS_CONFIG | string | - | TARS 配置中心地址(生产环境) |
3. 数据库初始化
# 创建数据库
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS db_account;"
# 导入基础表结构
mysql -u root -p db_account < data/db_account.sql
# 导入 OAuth2 客户端表(v0.0.42 新增)
mysql -u root -p db_account < data/t_oauth_client.sql4. 启动服务
# 开发模式
npm run dev
# 生产模式
npm start作为组件集成使用(合并部署)
1. 安装依赖
npm install @fefeding/base-server2. 配置导入
在 src/configuration.ts 中导入 base-server 组件:
import { Configuration, App } from '@midwayjs/core';
import * as codeDye from '@midwayjs/code-dye';
import * as staticFile from '@midwayjs/static-file';
import * as view from '@midwayjs/view-nunjucks';
import * as cacheManager from '@midwayjs/cache-manager';
import { join } from 'path';
import * as baseMidwayjs from '@cicctencent/midwayjs-base';
import * as baseServer from '@fefeding/base-server';
@Configuration({
imports: [
{
component: codeDye,
enabledEnvironment: ['local'],
},
staticFile,
view,
cacheManager,
baseMidwayjs,
baseServer, // 导入 base-server 组件
// 注意:不需要再导入 busboy、orm、validate,
// base-server 已包含这些组件
],
importConfigs: [join(__dirname, './config')],
})
export class ContainerLifeCycle {
@App()
app: baseMidwayjs.koa.Application;
async onReady() {
// 添加自定义过滤器
}
async onServerReady() {
if (process.env.NODE_ENV !== 'production') {
console.log(
`服务已启动: http://${process.env.IP || '127.0.0.1'}:${process.env.PORT || 7001}`
);
}
}
}3. 配置文件
在 config/config.default.ts 中添加 base-server 所需的配置:
export default (appInfo: MidwayAppInfo) => {
return {
// ... 其他配置
// 数据库配置(base-server 组件需要)
typeorm: {
dataSource: {
default: {
type: 'mysql',
host: process.env.DB_HOST || '',
port: process.env.DB_PORT || 3306,
username: process.env.DB_USERNAME || '',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_DATABASE || 'db_account',
synchronize: false,
logging: false,
entities: ['**/model/**/*{.ts,.js}'],
},
},
},
// Session 配置
session: {
timeout: Number(process.env.SESSION_TIMEOUT) || 20 * 60 * 1000,
serviceKey: 'session:service',
},
// 授权码配置
authCodeOption: {
timeout: 5 * 60 * 1000,
},
// OAuth2 + OIDC 配置
oauth: {
issuer: process.env.OAUTH_ISSUER || 'https://sso.ycnull.com',
tokenValidity: Number(process.env.OAUTH_TOKEN_VALIDITY) || 3600,
refreshValidity: Number(process.env.OAUTH_REFRESH_VALIDITY) || 2592000,
jwtPrivateKeyPath: process.env.JWT_PRIVATE_KEY_PATH || '',
jwtPublicKeyPath: process.env.JWT_PUBLIC_KEY_PATH || '',
},
// 验证码配置
verificationCode: {
timeout: 5 * 60 * 1000,
phoneLimit: 3,
},
// 缓存配置
cacheManager: {
clients: {
default: { store: 'memory', options: { max: 10000, ttl: 600000 } },
session: { store: 'memory', options: { max: 10000, ttl: 600000 } },
app: { store: 'memory', options: { max: 10000, ttl: 600000 } },
application: { store: 'memory', options: { max: 10000, ttl: 600000 } },
},
},
};
};4. 使用服务
组件模式下,所有 Service 和 Model 均可直接注入使用:
import { Inject, Controller, All, Provide, Body } from '@midwayjs/core';
import { BaseController, decorators } from '@cicctencent/midwayjs-base';
import { AccountService, UserService, SessionService } from '@fefeding/base-server';
@Provide()
@Controller('/api/account')
export class AccountController extends BaseController {
@Inject()
userService: UserService;
@Inject()
accountService: AccountService;
@All('/myInfo')
@decorators.checkLogin(true)
async myInfo() {
const userId = this.ctx.currentSession.userId;
const userInfo = await this.userService.getUser(userId);
return userInfo;
}
}5. 组件模式注意事项
- 不需要重复导入组件:base-server 已包含
busboy、orm、validate,主应用无需重复导入 - 数据库配置:主应用需要配置
typeorm数据库连接,base-server 共享该连接 - 中间件:base-server 的
RateLimitMiddleware在组件模式下自动跳过,如需限流由主应用控制 - 路由前缀:base-server 的 Controller 路由前缀为
/api/*,确保不与主应用路由冲突 - 环境变量:组件模式下不需要配置
BASE_SERVER_URL,直接进程内调用
API 接口文档
OAuth2 / OIDC 标准端点(核心协议)
| 路径 | 方法 | 鉴权 | 说明 |
|------|------|------|------|
| /.well-known/openid-configuration | GET | 无 | OIDC Discovery 文档 |
| /oauth/jwks | GET | 无 | JWKS 公钥发布 |
| /oauth/token | POST | client_secret | Token 端点(授权码换Token/刷新Token) |
| /oauth/userinfo | GET/POST | Bearer Token | OIDC UserInfo 端点 |
| /oauth/revoke | POST | client_secret | Token 撤销端点(RFC 7009) |
| /oauth/authorize | POST | API_KEY + IP | 创建OAuth2授权码(SSO调用) |
| /oauth/client/validate | POST | API_KEY + IP | 验证OAuth2客户端(SSO调用) |
| /oauth/client/register | POST | API_KEY + Login | 注册OAuth2客户端 |
| /oauth/client/search | POST | API_KEY + Login | 查询OAuth2客户端列表 |
| /oauth/client/regenerateSecret | POST | API_KEY + Login | 重新生成client_secret |
会话管理 (/api/session)
| 路径 | 方法 | 鉴权 | 说明 |
|------|------|------|------|
| /loginByAccount | POST | API_KEY + IP | 账号密码登录 |
| /loginByWx | POST | - | 微信/企业微信登录 |
| /loginByAuthCode | POST | API_KEY | 授权码换取 Session(旧协议) |
| /loginByVerifyCode | POST | API_KEY + IP | 验证码登录 |
| /getLoginSession | POST | API_KEY | 获取 Session 信息 |
| /createAuthCode | POST | API_KEY + IP | 生成临时授权码(旧协议) |
| /createWxLoginQrcode | POST | API_KEY | 生成公众号登录二维码 |
| /logout | POST | API_KEY | 登出 |
账户管理 (/api/account)
| 路径 | 方法 | 鉴权 | 说明 |
|------|------|------|------|
| /search | POST | API_KEY + IP + Login | 搜索账户 |
| /queryUser | POST | API_KEY + Login | 查询用户 |
| /createAccount | POST | API_KEY + Login | 创建账户 |
| /getUser | POST | API_KEY + Login | 获取用户信息 |
| /getByLoginId | POST | API_KEY + Login | 通过 loginId 获取账户 |
| /updatePassword | POST | API_KEY + Login | 修改密码 |
| /updateUser | POST | API_KEY + Login | 更新用户信息 |
| /remove | POST | API_KEY + Login | 删除账户 |
应用管理 (/api/app)
| 路径 | 方法 | 鉴权 | 说明 |
|------|------|------|------|
| /search | POST | API_KEY + IP + Login | 搜索应用 |
| /getApp | POST/GET | API_KEY + IP | 获取应用信息 |
| /create | POST | API_KEY + IP | 创建应用 |
| /update | POST | API_KEY + IP | 修改应用 |
| /remove | POST | API_KEY + IP + Login | 删除应用 |
| /getAppAccessToken | POST/GET | API_KEY + IP | 获取应用 AccessToken |
| /invalidateAppAccessToken | POST | API_KEY + IP | 失效应用 AccessToken |
| /invalidateToken | POST | API_KEY + IP | 失效指定 Token |
微信接口 (/api/wx)
| 路径 | 方法 | 鉴权 | 说明 |
|------|------|------|------|
| /getUserPhoneNumber | POST | API_KEY | 获取微信用户手机号 |
| /addDraft | POST | API_KEY | 创建公众号草稿 |
| /publishArticle | POST | API_KEY | 发布文章 |
| /uploadimg | POST | API_KEY | 上传图文消息图片 |
| /addMaterial | POST | API_KEY | 上传永久素材 |
| /clreaQuota | POST | API_KEY | 清除调用次数 |
| /createMenus | POST | API_KEY | 创建菜单 |
| /msg | POST | API_KEY | 微信消息回调处理 |
地址接口 (/api/address)
| 路径 | 方法 | 鉴权 | 说明 |
|------|------|------|------|
| /getIPAddress | ALL | 无 | 获取客户端 IP 地址 |
SSO 单点登录协议
Base Server 是 SSO 单点登录的核心,提供两种接入方式:
方式一:OAuth2 + OIDC 标准协议(推荐)
符合 OAuth2 和 OpenID Connect 国际标准,适合所有新应用接入。详见上方 OAuth2 / OIDC 标准端点。
方式二:传统 SSO 协议(仅兼容存量应用,将废弃)
⚠️ 仅用于存量应用过渡,新应用请使用 OAuth2/OIDC 标准协议。
1. 用户访问应用 → 应用重定向到 SSO 登录页
2. SSO 调用 /api/session/loginByWx 或 /api/session/loginByAccount 完成登录
3. SSO 调用 /api/session/createAuthCode 生成临时授权码
4. SSO 将 auth_code 回调给应用
5. 应用调用 /api/session/loginByAuthCode 用授权码换取完整 Session
6. 应用调用 /api/session/getLoginSession 验证 token 有效性特点:
- 使用 Session 机制,服务端维护登录态
- 授权码有效期:5 分钟,一次性使用
- 无标准客户端库支持,需自行实现
两种方式对比
| 特性 | OAuth2 + OIDC(推荐) | 传统 SSO 协议(将废弃) | |------|----------------------|------------------------| | 标准化 | 国际标准(RFC 6749、OIDC) | 自定义协议 | | 客户端管理 | client_id + client_secret | 仅 appId | | Token 类型 | access_token + id_token + refresh_token | Session Token(UUID) | | Token 格式 | JWT(RS256签名) | UUID | | 安全增强 | PKCE、nonce、签名验证 | 基础 | | 客户端库 | 标准库支持 | 需自行实现 | | 离线访问 | 支持(refresh_token) | 不支持 |
数据库
数据库表
| 表名 | 说明 |
|------|------|
| t_account | 账户表(loginId, account, password, openId, unionId, appId) |
| t_user | 用户表(name, avatar, alias, gender, status) |
| t_session | 会话表(id, loginId, userId, appId, status, clientIP, ext) |
| t_app | 应用表(name, type, appId, secret, appKey) |
| t_auth_map | 授权码映射表(code, token, clientId, redirectUri, scope, 过期时间) |
| t_oauth_client | OAuth2 客户端表(clientId, clientSecret, name, redirectUris, scopes, grantTypes) |
| t_app_access_token | 应用 AccessToken 表(accessToken, 过期时间) |
| t_verification_code | 验证码表(code, type, status, loginId) |
| t_wx_message | 微信消息记录表 |
应用类型 (EAppType)
| 值 | 类型 | 说明 | |----|------|------| | 1 | WxGzh | 微信公众号 | | 2 | WxMiniApp | 微信小程序 | | 3 | Own | 自有应用 | | 5 | WkWeb | 企业微信 Web 应用 | | 6 | WkMiniApp | 企业微信小程序 |
配置说明
核心配置项
// config/config.default.ts
{
koa: {
port: 8089, // 服务端口
globalPrefix: '/base-server', // 全局路由前缀
},
typeorm: { // MySQL 数据库配置
dataSource: {
default: { type: 'mysql', ... }
}
},
session: {
serviceKey: 'session:service', // Session 服务标识
timeout: 86400000, // Session 超时时间(毫秒)
},
authCodeOption: {
timeout: 5 * 60 * 1000, // 授权码有效期 5 分钟
},
oauth: { // OAuth2 + OIDC 配置
issuer: 'sso-server', // Issuer 标识
tokenValidity: 3600, // access_token 有效期(秒)
refreshValidity: 2592000, // refresh_token 有效期(秒)
jwtPrivateKeyPath: '/path/to/private.pem', // RSA 私钥路径
jwtPublicKeyPath: '/path/to/public.pem', // RSA 公钥路径
},
verificationCode: {
timeout: 5 * 60 * 1000, // 验证码有效期
phoneLimit: 3, // 每号码 5 分钟内最多发送次数
},
tencentCloudSMSConfig: { ... }, // 腾讯云短信配置
tencentCosOption: { ... }, // 腾讯云 COS 配置
}RSA 密钥配置(OAuth2 JWT 签名)
生产环境必须配置 RSA 密钥对,用于 JWT Token 签名和验证。
生成密钥对:
# 生成私钥(2048位)
openssl genrsa -out private.pem 2048
# 从私钥提取公钥
openssl rsa -in private.pem -pubout -out public.pem配置方式(三选一):
# 方式1:环境变量(推荐,PEM格式字符串)
export JWT_PRIVATE_KEY="$(cat private.pem)"
export JWT_PUBLIC_KEY="$(cat public.pem)"
# 方式2:环境变量(文件路径)
export JWT_PRIVATE_KEY_PATH=/path/to/private.pem
export JWT_PUBLIC_KEY_PATH=/path/to/public.pem
# 方式3:配置文件
// config/config.default.ts
{
oauth: {
jwtPrivateKeyPath: '/path/to/private.pem',
jwtPublicKeyPath: '/path/to/public.pem',
}
}注意事项:
- 私钥必须妥善保管,不可泄露
- 开发环境会自动生成临时密钥对(重启后失效)
- 生产环境必须配置固定密钥对
部署说明
独立部署
Docker 部署
docker build . -t base-server
docker run -p 8089:8089 --env-file .env base-serverPM2 部署
npm run deploy # 安装依赖 + 构建 + 清理开发依赖
npm start # pm2 start bootstrap.js --name base-server
npm run restart # pm2 restart base-server
npm stop # pm2 stop base-server合并部署(组件模式)
作为 npm 包安装到主应用(如 SSO Server):
# 在主应用中安装
npm install @fefeding/base-server
# 主应用的 configuration.ts 中导入
import * as baseServer from '@fefeding/base-server';详见 作为组件集成使用 章节。
NPM 发布
npm run build # 构建
npm publish # 发布到 npm(public access)开发指南
npm run dev # 本地开发
npm test # 运行测试
npm run cov # 测试覆盖率
npm run lint # 代码检查
npm run lint:fix # 自动修复版本信息
当前版本: 0.0.42
常见问题
1. Session 过期时间如何配置?
在配置文件中设置 session.timeout,单位为毫秒:
// config/config.default.ts
{
session: {
timeout: 86400000, // 24小时
}
}2. 如何调试微信登录?
- 本地开发时,可以使用 微信开发者工具 进行调试
- 公众号登录需要配置回调域名白名单
- 企业微信登录需要在企业微信后台配置可信域名
3. API_KEY 验证失败怎么办?
确保请求头中包含正确的 x-api-key:
curl -H "x-api-key: 2024@fefeding#" http://localhost:8089/base-server/api/session/getLoginSession4. 如何查看 Session 缓存状态?
Session 使用 MidwayJS Cache Manager 管理,默认使用内存缓存。可通过以下方式查看:
// 在服务中注入缓存实例
@InjectClient(CachingFactory, 'session')
sessionCache: MidwayCache;
// 获取缓存中的 Session
const session = await this.sessionCache.get(sessionId);5. 数据库连接失败如何排查?
- 检查数据库配置是否正确(host、port、username、password)
- 确认数据库已创建:
CREATE DATABASE db_account; - 检查数据库用户是否有足够权限
- 查看日志中的具体错误信息
6. 如何自定义 Session 状态?
Session 状态定义在 @fefeding/common 包中:
enum EStatus {
ACTIVED = 1, // 活跃
OFFLINE = 2, // 离线
INACTIVATED = 3, // 未激活
}7. 授权码登录流程是什么?
SSO 单点登录核心流程:
用户 → 应用A → 重定向到 SSO → 登录成功 → 生成 auth_code → 回调应用A → 用 auth_code 换取 Session授权码特性:
- 有效期:5 分钟
- 一次性使用:使用后立即失效
- 安全性:auth_code 不包含用户信息,必须通过 API 换取 Session
8. 如何使用 OAuth2 标准协议接入?
推荐使用 OAuth2 + OIDC 标准协议接入,步骤如下:
- 注册客户端:调用
/oauth/client/register获取 client_id 和 client_secret - 配置回调地址:在注册时指定
redirect_uris(必须精确匹配) - 用户登录:引导用户到 SSO 登录页,携带 client_id、redirect_uri、scope 等参数
- 获取授权码:用户登录成功后,SSO 会回调应用并携带授权码
- 换取 Token:调用
/oauth/token用授权码换取 access_token 和 id_token - 获取用户信息:调用
/oauth/userinfo获取用户详细信息
详细流程请参考 OAuth2 + OIDC 标准协议 章节。
9. 如何验证 ID Token 的签名?
ID Token 使用 RS256 算法签名,验证步骤:
// 1. 获取公钥
const jwksResponse = await fetch('https://base.ycnull.com/oauth/jwks');
const jwks = await jwksResponse.json();
// 2. 使用 JWT 库验证
const jwt = require('jsonwebtoken');
const decoded = jwt.verify(idToken, jwks.keys[0], {
algorithms: ['RS256'],
issuer: 'sso-server',
audience: 'your_client_id'
});
// 3. 验证 nonce(防重放攻击)
if (decoded.nonce !== storedNonce) {
throw new Error('Nonce mismatch');
}10. client_secret 泄露了怎么办?
立即调用 /oauth/client/regenerateSecret 重新生成:
POST /oauth/client/regenerateSecret
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"clientId": "cli_xxxxxxxx"
}
# 响应(新的 client_secret)
{
"clientId": "cli_xxxxxxxx",
"clientSecret": "新的密钥"
}注意:重新生成后,旧的 client_secret 立即失效。
11. 如何实现 PKCE 安全增强?
PKCE 适用于移动应用和 SPA,防止授权码被拦截:
// 1. 生成 code_verifier(43-128位随机字符串)
const crypto = require('crypto');
const codeVerifier = crypto.randomBytes(32).toString('base64url');
// 2. 生成 code_challenge(S256方法)
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// 3. 授权请求时携带 code_challenge
// SSO 调用 /oauth/authorize 时传递 codeChallenge 和 codeChallengeMethod: 'S256'
// 4. Token 请求时携带 code_verifier
// 应用调用 /oauth/token 时传递 code_verifier12. Token 过期后如何刷新?
使用 refresh_token 刷新 access_token:
POST /oauth/token
Content-Type: application/json
{
"grant_type": "refresh_token",
"refresh_token": "your_refresh_token",
"client_id": "cli_xxxxxxxx",
"client_secret": "xxxxxxxxxx"
}
# 响应(新的 access_token)
{
"access_token": "新的access_token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile"
}注意:刷新后不会返回新的 refresh_token,原有的 refresh_token 可继续使用。
更新日志
v0.0.42 (当前版本)
- 重大更新:新增 OAuth2 + OIDC 标准端点支持
- 新增
OAuthController:提供标准 OAuth2/OIDC 端点(Token、UserInfo、JWKS、Discovery) - 新增
OAuthClientService:OAuth2 客户端注册与管理 - 新增
JwtService:JWT 签名与验证服务(RSA 密钥对) - 新增
t_oauth_client数据表:存储 OAuth2 客户端信息 - 支持 PKCE(Proof Key for Code Exchange)安全增强
- 支持 ID Token(OIDC 标准,包含用户身份声明)
- 支持 Refresh Token(长期离线访问)
- 支持 OIDC Discovery(自动发现配置)
- 授权码映射表扩展:支持 client_id、redirect_uri、scope、PKCE、nonce
- 完善文档:新增 OAuth2 + OIDC 接入指南
v0.0.39
- 优化文档结构和注释
- 完善 API 接口说明
v0.0.38
- 新增企业微信小程序登录支持
- 优化 Session 缓存机制
v0.0.37
- 新增公众号扫码登录功能
- 新增验证码登录接口
v0.0.36
- 新增 IP 白名单控制
- 优化错误处理机制
v0.0.35
- 新增 SSO 单点登录核心功能
- 支持多种登录方式(微信/账号密码/授权码)
贡献指南
开发环境搭建
- Fork 本仓库
- 克隆到本地:
git clone https://github.com/your-username/base-server.git - 安装依赖:
npm install - 启动开发服务:
npm run dev
代码规范
- 使用 TypeScript 编写代码
- 遵循 ESLint 规则:
npm run lint - 提交前自动检查:
npm run lint:fix
提交规范
提交信息格式:
<type>(<scope>): <subject>
<body>
<footer>类型(type):
feat: 新功能fix: 修复 Bugdocs: 文档更新style: 代码格式调整refactor: 重构test: 测试相关chore: 构建/工具相关
示例:
feat(session): 新增公众号扫码登录功能
- 新增 createWxLoginQrcode 接口
- 新增 loginByVerifyCode 接口
- 支持验证码状态管理
Closes #123Pull Request 流程
- 创建特性分支:
git checkout -b feature/your-feature - 提交更改:
git commit -m 'feat: your feature' - 推送分支:
git push origin feature/your-feature - 创建 Pull Request
许可证
MIT License
