授权类型
涵盖 Identity Server 支持的所有授权模式——密码、授权码、客户端凭证、刷新令牌、扩展授权,以及各自的校验器实现和适用场景。
适用场景
- 需要为不同客户端选择最合适的授权模式
- 需要实现自定义授权类型(如短信验证码、邮箱验证码登录)
- 需要理解授权码流程中登录页和同意页的作用
- 需要对接需要刷新令牌的客户端
前置条件
授权类型的使用依赖 Identity Server 核心注册和 Client.allowedGrantTypes 配置,详见 快速开始。
| 授权类型 | 是否必须实现校验器 | 是否需要登录页 |
|---|---|---|
password | 必须:IResourceOwnerPasswordValidator | 否 |
authorization_code | 否(内置) | 是 |
client_credentials | 否(内置) | 否 |
refresh_token | 否(内置) | 否 |
| 扩展授权 | 必须:IExtensionGrantValidator | 否 |
快速开始
每种授权类型对应一个 grant_type 参数值。客户端向 /connect/token(或 /connect/authorize)发起请求并指定 grant_type,Identity Server 根据类型调用对应的校验器。
先看密码模式的完整请求——这是最简单的上手方式,只需一个 HTTP POST:
POST http://localhost:5000/connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=admin&password=1024&client_id=client&client_secret=secret成功响应:
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600
}核心概念
密码模式(password)
密码模式信任客户端能安全收集用户凭证。客户端直接将用户名和密码与令牌请求一起发送给 Identity Server。
必须实现 IResourceOwnerPasswordValidator:
import soulsoft_identity_server.validation.*
class MyPasswordValidator <: IResourceOwnerPasswordValidator {
func validate(context: ResourceOwnerPasswordValidationContext): Unit {
if (context.username != "admin" || context.password != "1024") {
throw ValidationException("invalid_grant", "用户名或密码错误")
}
}
}
// 注册
builder.services.addIdentityServer()
.addPasswordGrantValidator<MyPasswordValidator>()校验通过后,Identity Server 自动调用 IProfileService.getProfileData() 获取 claims 并签发 access_token。
适用场景:受信任的应用(如官方移动端、内部工具)。不适合 SPA——客户端代码中处理用户凭证不安全。
授权码模式(authorization_code)
授权码模式是 OAuth 2.0 最安全的授权方式。用户凭据只在 Identity Server 的登录页提交,客户端永远接触不到密码。
流程分三步:
① 客户端重定向到 /connect/authorize
└── 未登录 → 重定向到 loginUrl(你实现的登录页)
└── 登录成功后 → Cookie 写入登录态 → 重定向回 /connect/authorize
│
② 未同意 → 重定向到 consentUrl(你实现的同意页)
└── 用户确认作用域 → 记录同意 → 重定向回 /connect/authorize
│
③ 生成授权码 → 重定向到客户端的 redirect_uri?code=xxx
└── 客户端用 code 向 /connect/token 换取 access_token客户端配置要求:
Client("interactive.public", []).also { c =>
c.requireSecret = false // 公开客户端,不要求密钥
c.requireConsent = true // 需要用户同意
c.allowOfflineAccess = true // 允许刷新令牌
c.requirePkce = true // 强制 PKCE
c.allowedGrantTypes = [GrantTypes.AuthorizationCode, GrantTypes.RefreshToken]
c.allowedRedirectUris = ["http://127.0.0.1:3000/callback.html"]
c.allowedScopes = ["openid", "profile", "api"]
}重要配置:
requirePkce = true— 公开客户端应强制要求 PKCE,防止授权码拦截攻击allowedRedirectUris— 白名单校验,授权码只会重定向到此处列出的地址requireConsent = true— 是否要求用户确认授权的作用域
适用场景:SPA、移动端、第三方应用。有 PKCE 加持的授权码模式是当前安全标准推荐的方式。
客户端凭证模式(client_credentials)
客户端凭证模式用于服务间通信——没有用户参与,客户端以自己的身份获取 access_token。
client_credentials 模式虽然不涉及用户(subject 为空主体),但令牌生成流程仍会统一调用 IProfileService.getProfileData()——你仍需注册 IProfileService 实现。客户端通过 client_id + client_secret 证明身份:
POST http://localhost:5000/connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=service-a&client_secret=service-secret&scope=api适用场景:微服务间调用、后台任务、CI/CD 工具。不需要用户上下文。
刷新令牌模式(refresh_token)
access_token 过期后,客户端用 refresh_token 获取新的 access_token,无需用户重新登录:
POST http://localhost:5000/connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=xxx&client_id=client&client_secret=secret刷新令牌行为由 Client 的三个参数控制:
| 参数 | 默认值 | 说明 |
|---|---|---|
refreshTokenExpiration | Absolute | Absolute:绝对过期(30 天),Sliding:滑动过期(每次刷新重置 15 天) |
refreshTokenUsage | ReUse | ReUse:刷新后旧 token 仍可用,Once:刷新后旧 token 立即失效 |
absoluteRefreshTokenLifetime | 2592000(30 天) | 刷新令牌的绝对生命周期,超期后必须重新登录 |
适用场景:需要长期访问的应用,配合授权码模式使用。
扩展授权类型
当内置的 4 种授权不够用时,可以实现 IExtensionGrantValidator 添加自定义授权。
下面是一个邮箱验证码授权的完整示例:
import soulsoft_identity_server.validation.*
class EmailExtensionGrantValidator <: IExtensionGrantValidator {
var grantType = "email"
func validate(context: ExtensionGrantValidationContext): Unit {
let email = context.parameters.get("email") ?? ""
let code = context.parameters.get("code") ?? ""
if (email.isEmpty()) {
throw ValidationException("invalid_grant", "邮箱不能为空")
}
if (code != "123456") {
throw ValidationException("invalid_grant", "验证码错误")
}
}
}
// 注册
builder.services.addIdentityServer()
.addExtensionGrantValidator<EmailExtensionGrantValidator>("email")客户端请求时指定自定义的 grant_type:
POST http://localhost:5000/connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=email&email=test@example.com&code=123456&client_id=client&client_secret=secretcontext.parameters 包含了请求体中的所有原始参数(包括 grant_type、client_id、client_secret),你可以按业务需要读取任意字段。框架不自动剔除任何参数。
适用场景:短信验证码登录、邮箱验证码登录、第三方社交登录(微信/支付宝)、生物识别认证等。
常见用法
授权类型与客户端组合
同一个 Identity Server 可以同时支持多种授权类型,不同客户端使用不同的授权方式:
builder.services.addIdentityServer() { options =>
options.issuer = "spire-idp"
options.issuerUri = "http://localhost:5000"
}
// 密码模式客户端——内部工具使用
.addInMemoryClients([
Client("internal-tool", [Secret("secret".sha256())]).also { c =>
c.allowedGrantTypes = [GrantTypes.Password]
c.allowedScopes = ["api"]
},
// 授权码模式客户端——SPA 使用
Client("spa-app", []).also { c =>
c.requireSecret = false
c.requirePkce = true
c.allowedGrantTypes = [GrantTypes.AuthorizationCode, GrantTypes.RefreshToken]
c.allowedScopes = ["openid", "profile", "api"]
c.allowedRedirectUris = ["http://localhost:3000/callback.html"]
},
// 客户端凭证模式——微服务使用
Client("service-a", [Secret("service-secret".sha256())]).also { c =>
c.allowedGrantTypes = [GrantTypes.ClientCredentials]
c.allowedScopes = ["api"]
}
])授权码模式的端点配置
interaction 配置控制授权码流程中各个跳转页面的地址:
builder.services.addIdentityServer() { options =>
options.interaction.loginUrl = "/account/login" // 未登录时跳转
options.interaction.consentUrl = "/account/consent" // 未同意时跳转
options.interaction.errorUrl = "/error" // 出错时跳转
options.interaction.returnUrlParameter = "returnUrl" // 回跳参数名
}returnUrlParameter 的值(默认 "returnUrl")会被自动拼接为 query string 参数,携带完整的授权请求 URL。你的登录页需在登录成功后读取这个参数并重定向回去。
生产建议
- 公开客户端(SPA、移动端)必须使用授权码模式 + PKCE,不允许用密码模式。 密码模式要求客户端持有用户凭证,对公开客户端而言这是不可接受的安全风险。
requirePkce = true可有效防止授权码拦截攻击。 refreshTokenUsage = Once更安全。 每次刷新后旧 token 立即失效——如果旧 token 被泄露,持有者在下一次刷新时会因为 token 已被消费而无法刷新,攻击被暴露。- 扩展授权中不要信任
context.parameters的原始值。 校验器应验证所有输入的有效性(非空、格式、长度),并通过ValidationException报告错误——框架会自动返回 400 和对应的error字段。
常见问题
密码模式和授权码模式怎么选
密码模式适合你控制的应用——官方移动端、内部管理工具,客户端代码在你的控制范围内。授权码模式适合你不控制的应用——第三方应用、SPA,用户凭证永远不应离开 Identity Server 的登录页。简单判断:如果需要 client_secret 能安全保管就用密码模式,否则用授权码模式 + PKCE。
客户端凭证模式是否调用 IProfileService
客户端凭证模式没有用户参与——客户端以自己的身份(而非用户身份)请求令牌。但由于令牌生成流程统一调用 IProfileService.getProfileData(),你仍需注册 IProfileService 实现。可以在 getProfileData() 中通过 request.subject 是否为空主体来判断是否为客户端凭证模式,从而返回空声明集合或不返回用户相关声明。令牌中的 claims 会包括客户端 allowedClaims、scope 声明以及 IProfileService 返回的声明。
扩展授权和密码验证器有什么区别
密码验证器 IResourceOwnerPasswordValidator 绑定固定的 grant_type=password,只接收 username 和 password 两个字段。扩展授权 IExtensionGrantValidator 可以自定义 grant_type 名称(如 "email")和任意参数(如 email、code),更灵活。如果你的"密码模式"需要额外字段(如图形验证码),用扩展授权替代。
相关专题
- 需要了解 Client 的完整配置 → 客户端与资源
- 需要了解 Identity Server 的整体架构 → Identity Server 总览
- 需要了解最小可运行示例 → 快速开始