前言
1.配置服务认证授权
订单服务(Ordering.API)配置了认证授权,所以我们使用Scalar调试接口的时候也需要认证服务(Identity.API)颁发的token,上面的测试就是401无权限
首先我们再回忆下我们认证授权配置了那些:
- 认证服务(
Authority):我们本地启动的Identity.API,其他服务认证授权配置Identity.API的地址即可。 - 使用
Https协议获取认证中心(Authority)元数据(RequireHttpsMetadata):原来这里是false,访问不验证https,为了多学习我们设置为true,本身Identity.API启动就是使用的https协议 - 订阅人(
Audience):确保认证服务Identity.API颁发的token是发给我的(订单服务),就是我们在appsettings.json中配置"Audience": "orders"。简单理解:什么服务就配置什么服务的标识,但是认证服务同时也要有此标识。确保这个Token是发给当前 API 的,而不是其他服务。验证签发token的aud。 - 签发者(
Issuers):确保Token是由受信任的认证中心签发。验证签发token的iss。

还需要再回忆一下认证服务(Identity.API)中的客户端配置,找到orderingswaggerui配置

然后我们启动认证服务,在..\eShop\src\Identity.API目录下运行以下命令:
dotnet run --urls https://localhost:5243/

2.配置授权码模式(Authorization Code + PKCE)
隐式模式(Implicit)已经被淘汰了,eShop 官方示例用 Implicit,是为了演示方便(快速看到授权效果),让 Swagger UI(或浏览器)/Scalar能够直接拿到 Access Token,方便开发和测试。
在 OAuth 2.1 规范中(2023 年正式发布),Implicit 模式已被标记为弃用:
- Access Token 暴露在 URL;
- 无法安全刷新;
- 浏览器中易被脚本窃取;
- 无法防止中间人攻击。
所以现代标准中 强制推荐改用 Authorization Code + PKCE。
既然学习,我们也看看授权码模式(Authorization Code + PKCE),我们就以购物车服务(Basket.API)为例,因为购物车的Scalar也是我们为了学习加上的,购物车服务(Basket.API)因为使用了Grpc,所以只能使用https协议,我们把接口也加上了api版本

先添加以下配置:
// 授权类型:Authorization Code + PKCEAllowedGrantTypes = GrantTypes.Code,// SPA/Scalar 客户端不保存 secret,所以不要求 client_secretRequireClientSecret = false,// 强制使用 PKCE(安全性必须)RequirePkce = true,// 是否显示用户同意授权页面RequireConsent = true, // 强制每次授权确认// token 生命周期和刷新控制(可选)AllowOfflineAccess = false, // 如果暂时不需要 refresh token// 允许跨域请求的 originAllowedCorsOrigins ={"https://localhost:5221" // Scalar / Swagger UI 所在的域名},
为什么授权码模式(
Authorization Code + PKCE)会触发跨域?隐式模式(Implicit)不需要(这是我调试过程遇到的问题,我提前记录一下)在 Implicit 流 下:
- 浏览器通过 302 重定向 + URL fragment 获取 token
- 不走 JS Ajax,所以不会触发 CORS
在 Authorization Code + PKCE 下:
- 前端 SPA 需要用 JS 通过 POST
/connect/token来交换 code → access_token- 浏览器 JS 发起的是跨域 AJAX 请求
- IdentityServer 如果没有允许该 Origin,就报
CorsPolicyService did not allow origin: ...

然后配置回调地址(RedirectUris)和允许访问的的API范围(AllowedScopes)
为什么需要配置
RedirectUris?
OAuth2 / OIDC 协议要求每个客户端注册 回调地址(redirect_uri)。
当用户在 IdentityServer 授权成功后,浏览器会重定向到这个地址,并带上 code 或 token。
严格匹配:IdentityServer 会检查请求中的
redirect_uri是否在客户端注册列表里,否则会报:Invalid redirect_uri为什么还要配置
openid/profilescopes?这些 scope 来自 OpenID Connect (OIDC) 协议:
openid→ 表示这是一个 OIDC 请求(IdentityServer 会返回 id_token)profile→ 请求用户信息(如名字、头像等)区别于普通 API scope:
- API scope(比如
basket)只控制访问后端 APIopenid/profile用于获取用户身份信息(JWT 中的 claims)如果你的前端只需要访问 API,不关心用户信息,可以不用
openid/profile,openid/profile可以根据需要决定是否保留。

定义一个名为 "basket" 的受保护 API,并指定访问它需要 "basket" 这个 scope。

OAuth2新增授权码模式(Authorization Code + PKCE )

RequireAuthorization:给这个 API 分组下的所有端点应用授权策略,也就是需要用户已经登录或具备某些权限才能访问这些接口。
var api = vApi.MapGroup("api/Basket").HasApiVersion(1, 0).HasApiVersion(2, 0).RequireAuthorization();

如果配置RequireAuthorization()没有token访问接口返回401(无权限)

所有代码配置完成后,启动购物车服务(Basket.API),打开Scalar,选择OAuth2,发现多了一种授权方案(Authorization Code)
跟之前一样配置:Client ID、Use PKCE、Scopes
Client Secret 可选
对于 SPA 或前端客户端(比如 Scalar 文档 UI、Swagger UI),不存储密钥是推荐做法,因为前端代码无法安全保管 secret。
所以在IdentityServer中,你可以配置
RequireClientSecret = false。如果你在 Scalar 上看到
Client Secret: XYZ123,那只是生成的示例,并不是必须的。PKCE 强制使用 SHA-256
- PKCE(Proof Key for Code Exchange)是 Authorization Code 流的增强安全机制,用于前端应用防止授权码被截获。
- PKCE 流程里有一个
code_challenge,必须经过 SHA-256 哈希(S256)生成,原始的plain方法被现代安全标准弃用。- 因此
IdentityServer会要求code_challenge_method = S256,不能用 plain,否则报错:code_challenge_method of plain is not allowed

输入用户名和密码,然后点击登录

再点击Yes,Allow(允许授权)

打开/api/Basket/{customerId},测试一下,查询正常

3.授权码模式流程(Authorization Code + PKCE)
在查看 OpenAPI 规范文档时,我们为 oauth2 安全方案配置了两种授权模式:Implicit 和 Authorization Code,分别对应浏览器直接获取 token 和通过授权码交换 token 的安全流程,并指定了 basket API 的访问范围。
https://localhost:5221/openapi/v1.json

先关闭认证服务(Identity.API),然后设置Client ID、Use PKCE、Scopes,点击Authorize
查看Scalar发起的授权请求:
response_type=code:授权码模式。scope:请求访问的范围。code_challenge&code_challenge_method:PKCE,客户端生成随机字符串和散列,服务器用来校验。redirect_uri:授权成功后回调地址。state:防 CSRF,同时保持请求上下文client_id:当前客户端的唯一标识
# 原请求
https://localhost:5243/connect/authorize?response_type=code&code_challenge=3_V716AyAbS7uKOkhK6jDTW7Ho8As2_zq6EOBA_i7jQ&code_challenge_method=S256&redirect_uri=https%3A%2F%2Flocalhost%3A5221%2Fscalar%2Fv1&client_id=basketswaggerui&state=78jzgb2i&scope=basket
# 解码后 redirect_uri加密了
https://localhost:5243/connect/authorize?
response_type=code&
code_challenge=3_V716AyAbS7uKOkhK6jDTW7Ho8As2_zq6EOBA_i7jQ&
code_challenge_method=S256&
redirect_uri=https://localhost:5221/scalar/v1&
client_id=basketswaggerui&
state=78jzgb2i&
scope=basket

启动认证服务(Identity.API)和购物车服务(Basket.API),设置Client ID、Use PKCE、Scopes,点击Authorize
没登录前:Showing login: User is not authenticated(显示登录页,用户没有认证)

登陆成功后:Showing consent: User has not yet consented(显示授权:用户未授权)

登录&授权后:Scalar发送 POST 请求到 Token 端点
client_id:客户端唯一标识redirect_uri:回调地址,必须匹配注册信息code:授权码,用于换取 Access Tokengrant_type:授权模式,这里是授权码模式(authorization_code)code_verifier:PKCE 验证码,保证授权码安全
POST https://localhost:5243/connect/token
client_id=basketswaggerui&
redirect_uri=https%3A%2F%2Flocalhost%3A5221%2Fscalar%2Fv1&
code=32BAD9BF9537F37C142CAC44066BFC80F5A81DDBB61B74E06445FA61881D0A85-1&
grant_type=authorization_code&
code_verifier=5JddRWZjBe72ixVPwZMxIQblPyhe_MivmcNUQFa2LEI
为什么授权码模式(
Authorization Code + PKCE)会触发跨域?隐式模式(Implicit)不需要(这是我调试过程遇到的问题,我提前记录一下)上面记录了这个问题,就是因为
Scalar客户端发起了Ajax请求获取token,所以认证服务(Identity.API)需要配置AllowedCorsOrigins

Scalar设置Access Token,所有api调用就可以使用这个Access Token

SPA/Scalar 通常不使用 Refresh Token,是因为浏览器无法安全存储长期凭证,短期 Access Token + PKCE 重新授权比使用 Refresh Token 更安全。

流程说明
| 步骤 | Authorization Code + PKCE Flow |
|---|---|
| 1 | 用户点击 Authorize |
| 2 | 跳转 IdentityServer 登录 |
| 3 | 如果未登录 → 输入用户名/密码 |
| 4 | 用户同意授权 |
| 5 | IdentityServer 重定向回客户端(带 授权码 code) |
| 6 | 客户端使用 code + code_verifier 向 IdentityServer Token Endpoint 交换 Access Token |
| 7 | IdentityServer 返回 Access Token(可选 ID Token) |
| 8 | 客户端使用 Access Token 调用受保护 API |
📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!
💬 加入交流群(QQ群):576434538
