wso2~通过三方IDP的token置换wso2的token
<p>参数:https://datatracker.ietf.org/doc/html/rfc7523</p><h1 id="oauth20中的三方另类授权">oauth2.0中的三方另类授权</h1>
<p>了解OAuth 2.0中特定的授权类型(Grant Type)对于构建安全的认证流程至关重要。下面为你详细介绍这三种基于URN声明的扩展授权类型。</p>
<h3 id="-设备代码授权-urnietfparamsoauthgrant-typedevice_code">🔐 设备代码授权 (<code>urn:ietf:params:oauth:grant-type:device_code</code>)</h3>
<p>这种授权模式专为输入能力受限或没有浏览器的设备设计,比如智能电视、游戏机、IoT设备或命令行工具(CLI)。</p>
<ul>
<li>
<p><strong>工作原理</strong>:其核心是一个两步过程。</p>
<ol>
<li><strong>设备获取代码</strong>:设备上的应用首先向授权服务器(如 Microsoft Entra ID)的 <code>/devicecode</code> 端点发起请求。服务器会返回一组信息,包括一个简短的 <code>user_code</code>(用户代码)和一个 <code>verification_uri</code>(验证网址)。</li>
<li><strong>用户授权</strong>:设备将 <code>user_code</code> 和 <code>verification_uri</code> 显示给用户,并提示用户在其他设备(如个人手机或电脑)上打开浏览器,访问该网址并输入代码。用户在授权服务器的正规页面上完成身份验证(可能包括多因素认证)并同意授权。在此期间,设备应用会定期轮询授权服务器的令牌端点,直到用户完成操作后获取访问令牌和刷新令牌。</li>
</ol>
</li>
<li>
<p><strong>安全风险与防御</strong>:需要注意的是,此流程可能被用于进行高迷惑性的网络钓鱼攻击(称为“设备代码钓鱼”)。攻击者会诱导用户在真实的微软登录页面输入由攻击者生成的设备代码,从而授权一个恶意应用。防御措施包括在服务器端严格限制应用同意策略、实施条件访问策略(如要求来自合规设备),以及对用户进行安全教育。</p>
</li>
</ul>
<h3 id="-令牌交换授权-urnietfparamsoauthgrant-typetoken-exchange">🔄 令牌交换授权 (<code>urn:ietf:params:oauth:grant-type:token-exchange</code>)</h3>
<p>令牌交换授权提供了强大的 interoperability(互操作性),允许将一个凭证或令牌交换为另一个不同的令牌,常用于服务之间的安全调用或身份映射。</p>
<ul>
<li>
<p><strong>核心概念</strong>:它遵循 OAuth Token Exchange 规范,使得客户端能够使用一个现有的 <code>subject_token</code>(主体令牌)来换取一个新的、针对不同受众或资源的 <code>access_token</code>(访问令牌)。</p>
</li>
<li>
<p><strong>常见场景</strong>:</p>
<ul>
<li><strong>服务间调用</strong>:后端服务A可以使用自己持有的令牌,换取一个具有适当权限的、用于调用服务B的访问令牌。</li>
<li><strong>权限降级</strong>:一个高权限的应用在需要调用一个低信任度的服务时,可以换一个权限受限的令牌,以提升安全性。</li>
<li><strong>外部身份提供商集成</strong>:将外部IDP(如Google、Facebook)签发的令牌交换为内部系统的令牌,从而实现跨域身份联合。</li>
<li><strong>用户模拟</strong>:在严格管控下,服务可以换取一个代表特定用户身份的令牌(即模拟用户)。</li>
</ul>
</li>
<li>
<p><strong>实施要点</strong>:令牌交换功能通常默认不开启,需要在服务器端(如Red Hat Single Sign-On)为客户端显式配置精细的权限策略。</p>
</li>
</ul>
<h3 id="️-jwt持有者授权-urnietfparamsoauthgrant-typejwt-bearer">⚙️ JWT持有者授权 (<code>urn:ietf:params:oauth:grant-type:jwt-bearer</code>)</h3>
<p>这种授权类型允许客户端直接使用一个预先签名的JWT(JSON Web Token)作为断言(assertion)来获取访问令牌。</p>
<ul>
<li>
<p><strong>基本流程</strong>:客户端向授权服务器的令牌端点发起POST请求,在请求体中,<code>grant_type</code> 参数设置为 <code>urn:ietf:params:oauth:grant-type:jwt-bearer</code>,并同时提供用作断言的 <code>assertion</code> 参数(即JWT本身)。授权服务器会验证该JWT的签名、有效期、颁发者等信息,验证通过后即颁发所请求的访问令牌。</p>
</li>
<li>
<p><strong>典型应用场景</strong>:</p>
<ul>
<li><strong>服务账户认证</strong>:在机器对机器(M2M)的通信中,一个服务可以使用事先配置好的JWT(通常基于私钥签名)来获取访问令牌,无需用户交互。这在CI/CD流水线或后台服务中非常常见。</li>
<li><strong>微服务架构</strong>:在微服务网络中,一个服务在收到访问令牌后,可以利用此流程向认证服务器换取一个范围(scope)更窄、专用于访问另一个特定微服务的令牌。</li>
</ul>
</li>
</ul>
<p>下面的表格清晰地对比了这三种授权类型的关键差异。</p>
<table>
<thead>
<tr>
<th style="text-align: left">特性</th>
<th style="text-align: left">设备代码授权 (<code>device_code</code>)</th>
<th style="text-align: left">令牌交换授权 (<code>token-exchange</code>)</th>
<th style="text-align: left">JWT持有者授权 (<code>jwt-bearer</code>)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><strong>主要目的</strong></td>
<td style="text-align: left">方便输入受限设备上的用户授权</td>
<td style="text-align: left">实现令牌之间的安全转换和身份委托</td>
<td style="text-align: left">客户端使用已有的JWT直接获取访问令牌</td>
</tr>
<tr>
<td style="text-align: left"><strong>令牌流</strong></td>
<td style="text-align: left">轮询机制</td>
<td style="text-align: left">直接交换</td>
<td style="text-align: left">断言式请求</td>
</tr>
<tr>
<td style="text-align: left"><strong>典型应用</strong></td>
<td style="text-align: left">智能电视、IoT设备、CLI工具</td>
<td style="text-align: left">服务间调用、权限降级、身份联合</td>
<td style="text-align: left">机器对机器通信、服务账户、微服务</td>
</tr>
</tbody>
</table>
<h3 id="-总结与安全考量">💎 总结与安全考量</h3>
<p>选择哪种授权类型完全取决于你的具体应用场景。设备代码授权优化了受限设备的用户体验,令牌交换授权为复杂的服务间信任链提供了灵活性,而JWT持有者授权则是机器对机器通信的简洁高效方案。</p>
<p>在实施这些授权流程时,务必关注以下安全最佳实践:</p>
<ul>
<li><strong>严格控制权限</strong>:遵循最小权限原则,只为应用授予其必需的最少权限。</li>
<li><strong>验证与监控</strong>:服务器端必须严格验证所有令牌和断言(如JWT的签名和有效期),并建立日志审计和异常行为监控机制。</li>
<li><strong>保护令牌</strong>:访问令牌和刷新令牌是敏感凭证,在传输和存储过程中必须加以保护。</li>
</ul>
<h1 id="wso2中的实战">wso2中的实战</h1>
<h2 id="wso2-sp的配置">wso2 sp的配置</h2>
<p>Keycloak中客户端授权范围wso2-role中,添加aud的硬编码</p>
<p><img src="https://img2024.cnblogs.com/blog/118538/202603/118538-20260317143201055-529967220.png" alt="图片" loading="lazy"></p>
<p>配置认证grant_type类型</p>
<p><img src="https://img2024.cnblogs.com/blog/118538/202601/118538-20260126132653488-1051084105.png" alt="图片" loading="lazy"></p>
<h2 id="keycloak-idp的配置">keycloak idp的配置</h2>
<blockquote>
<p>keycloak中为客户端开启roles之后,如果有用户有客户端的角色,会在jwt中多出来aud数组字段,也可以为wso2客户端添加自定义的client scope,然后为它添加aud的cliams</p>
</blockquote>
<p>IDP名称必须与IDP中token的Issuer相同</p>
<p><img src="https://img2024.cnblogs.com/blog/118538/202601/118538-20260126162025923-968169912.png" alt="图片" loading="lazy"></p>
<h2 id="测试">测试</h2>
<pre><code>curl -X POST 'https://test-apim.xxx.com/oauth2/token' -H 'Content-Type: application/json' -H 'Content-Type: application/json' -u 'wso2-sp-client-id:wso2-sp-client-secret' --basic -d '{
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": "abc.abc.abc",
"scope": "apim:subscribe"
}'
</code></pre>
<blockquote>
<p>scope参数如果不传,默认是default,对于apim的开发者平台,这个值必须传,否则无权限添加应用和订阅api</p>
</blockquote>
<p>这块功能scope使用你认证能数里的<code>scope</code>,还是使用默认的<code>default</code>,主要在IDP的Claim Configuration配置中进行设置,如图:</p>
<p><img src="https://img2024.cnblogs.com/blog/118538/202603/118538-20260318110136058-2094885007.png" alt="" loading="lazy"></p>
<p>如果keycloak_token过期,就返回这个400错误</p>
<pre><code>{
"error_description": "JSON Web Token is expired., Expiration Time(ms) : 1769413736000, TimeStamp Skew : 0, Current Time : 1769413748585. JWT Rejected and validation terminated",
"error": "invalid_grant"
}
</code></pre>
<p>如果成功,会返wso2平台的token,需要注意这个返回值里scope,如果它是<code>default</code>,那是没有操作application的权限的</p>
<pre><code>{
"access_token": "8b23bf56-f8d2-33fa-9962-f298a797ce94",
"refresh_token": "aad2555-30b0-3591-8c70-b2cdc042cc41",
"scope": "apim:subscribe openid",
"id_token": "",
"token_type": "Bearer",
"expires_in": 3185
}
</code></pre>
<p>用户登录成功后,会初始化用户表,需要注意的是,这种<code>urn:ietf:params:oauth:grant-type:jwt-bearer</code>首次登录添加的用户,它位于<code>wso2am_db.idn_auth_user</code>表,user_name同样是三方社区token中的<code>sub</code>字段;而标准oauth的授权码认证后,除了在<code>wso2am_db.idn_auth_user</code>表初始化外,还在<code>wso2am_share_db.um_user</code>表也会添加一份用户数据。</p>
<p><strong><font color="red">【添加的用户没有订阅者角色,导致用户建立的应用无法订阅API】</font></strong></p>
<ul class="contains-task-list">
<li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"><label> 对于上面的um_user中的用户,它在carbon平台是可以查询到的,它默认是没有subscribe权限的,只有everyone权限</label></li>
<li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"><label> 这时,它建立的应用,应用是无法订阅api的,我们需要同时为um_user的用户初始化角色<code>subscribe</code></label></li>
<li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"><label> 这块功能由product-is系统提供,所以在共享库wso2am_shared_db里,数据表为<code>um_hybrid_role</code>和<code>um_hybrid_user_role</code>。</label></li>
<li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"><label> 调整源码cargin-apimgt项目org.wso2.carbon.apimgt.impl.issuers.configureForJWTGrantOrExchangeGrant()方法</label></li>
<li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"><label> 再执行FederatedUserSyncUtil.syncFederatedUserToUMUser(tenantDomain, userName)方法,为期添加初始化um_hybrid_user_role的脚本</label></li>
</ul>
<blockquote>
<p>注意,当<code>wso2am_share_db.um_user表</code>没有这个用户数据时,你再为应用生成consumer_key时调用generate-keys接口,会出现错误,所以,我们需要想办法,在<code>wso2am_share_db.um_user表</code>表初始化一条用户数据,um_user_name与wso2am_db.idn_auth_user表的user_name保持一致即可,即三方IDP的user_id即可</p>
</blockquote>
<h1 id="默认应用如何创建">默认应用如何创建</h1>
<p>通过获取用户应用的接口,完成后,如果没有默认应用,会为用户自动创建,即初始化下面两张表</p>
<ul>
<li>am_subscriber</li>
<li>am_application</li>
</ul>
<h3 id="首次无订阅者和应用时需要调用获取应用接口">首次无订阅者和应用时,需要调用获取应用接口</h3>
<ul>
<li>GET https://test-apim.pkulaw.com/api/am/devportal/v3/applications
<ul>
<li>header Authorization bearer <token></li>
</ul>
</li>
<li>响应结果,没有应该会自动添加默认应用</li>
</ul>
<pre><code> {
"count": 1,
"list": [
{
"applicationId": "46fd899d-5612-4708-82f7-a768c9d94678",
"name": "默认应用",
"throttlingPolicy": "Unlimited",
"description": "系统为您创建的默认应用,xxxx",
"status": "APPROVED",
"groups": [],
"subscriptionCount": 0,
"attributes": {},
"owner": "819b3483-d859-4f41-854a-907aec27a068",
"tokenType": "DEFAULT",
"createdTime": "1771910083000",
"updatedTime": "1771910083000"
}
],
"pagination": {
"offset": 0,
"limit": 25,
"total": 1,
"next": "",
"previous": ""
}
}
</code></pre>
<h3 id="为应用生成consumer_key和consumer_secret接口">为应用生成consumer_key和consumer_secret接口</h3>
<p>前提,先为用户初始化wso2am_share_db.um_user表,否则会出现错误,consumer_key为空等问题</p>
<pre><code>{
"code": 900967,
"message": "General Error",
"description": "Server Error Occurred",
"moreInfo": "",
"error": []
}
</code></pre>
<p>当接口执行成功后,会初始化下面三个数据表</p>
<ul>
<li>
<p>am_application_registration</p>
</li>
<li>
<p>am_application_key_mapping</p>
</li>
<li>
<p>idn_oauth_consumer_apps</p>
</li>
<li>
<p>GET /applications/application_uuid/generate-keys</p>
</li>
</ul>
<pre><code>{
"keyType": "PRODUCTION",
"grantTypesToBeSupported": [
"password",
"client_credentials"
],
"callbackUrl": "",
"additionalProperties": {
"application_access_token_expiry_time": "36000",
"user_access_token_expiry_time": "36000",
"refresh_token_expiry_time": "36000",
"id_token_expiry_time": "36000",
"pkceMandatory": "false",
"pkceSupportPlain": "false",
"bypassClientCredentials": "false"
},
"keyManager": "aefd5cd3-c18a-47f8-91c9-2fa42767b4bf",
"validityTime": 3600,
"scopes": [
"default"
]
}
</code></pre>
<p>生成的am_application_key_mapping表中,consumer_key是非空的,这个很重要。</p>
<h3 id="自动添加默认应用的原理这些在源码里已经实现这块不需要调整的源码已支持应用添加consumer_key的生成需要手动调用接口"><s>自动添加默认应用的原理[这些在源码里已经实现],这块不需要调整的源码已支持应用添加,consumer_key的生成需要手动调用接口</s></h3>
<blockquote>
<p>为用户添加了订阅者,默认应用,应用的consumerKey和consumerSecret等,用户可以手动为应该生成token</p>
</blockquote>
<p><strong>SubscriberRegistrationInterceptor是添加订阅者的入口</strong></p>
<ul>
<li>org.wso2.carbon.apimgt.rest.api.util.interceptors.SubscriberRegistrationInterceptor
<ul>
<li>handleMessage方法,订阅被注册后,如果没有应用,会添加默认认证
<ul>
<li>apiConsumer.addSubscriber(username, groupId);
<ul>
<li>org.wso2.carbon.apimgt.impl.AbstractAPIManager.addSubscriber()</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>添加默认应用的方法优化</strong></p>
<ul>
<li>org.wso2.carbon.apimgt.impl.AbstractAPIManager.addDefaultApplicationForSubscriber()方法</li>
</ul>
</div>
<div id="MySignature" role="contentinfo">
<p></p>
<div class="navgood">
<p>作者:仓储大叔,张占岭,<br>
荣誉:微软MVP<br>QQ:853066980</p>
<p><strong>支付宝扫一扫,为大叔打赏!</strong>
<br><img src="https://images.cnblogs.com/cnblogs_com/lori/237884/o_IMG_7144.JPG"></p>
</div><br><br>
来源:https://www.cnblogs.com/lori/p/19532862
頁:
[1]