mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-04-05 17:37:53 +08:00
新增 OIDC 协议实现
This commit is contained in:
parent
419ca3797c
commit
a7a3e8c14f
@ -48,11 +48,11 @@
|
||||
<span class="ps">当请求链接不包含 scope 权限,或请求的 scope 近期已授权时,将无需用户手动确认,做到静默授权</span>
|
||||
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/</code>
|
||||
|
||||
<a href="http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userid,userinfo">
|
||||
<a href="http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userid,userinfo,oidc">
|
||||
<button>授权登录(显式授权)</button>
|
||||
</a>
|
||||
<span class="ps">当请求链接包含具体的 scope 权限时,将需要用户手动确认,此时 OAuth-Server 会返回更多的数据</span>
|
||||
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userid,userinfo</code>
|
||||
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userid,userinfo,oidc</code>
|
||||
|
||||
<button onclick="refreshToken()">刷新令牌</button>
|
||||
<span class="ps">我们可以拿着 Refresh-Token 去刷新我们的 Access-Token,每次刷新后旧Token将作废</span>
|
||||
|
@ -59,6 +59,13 @@
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token-jwt 签发 OIDC id_token 令牌 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jwt</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 热刷新 -->
|
||||
<!--<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
@ -4,6 +4,8 @@ import cn.dev33.satoken.oauth2.SaOAuth2Manager;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
/**
|
||||
* 启动:Sa-OAuth2 Server端
|
||||
* @author click33
|
||||
@ -11,10 +13,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@SpringBootApplication
|
||||
public class SaOAuth2ServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
public static void main(String[] args) throws MalformedURLException {
|
||||
SpringApplication.run(SaOAuth2ServerApplication.class, args);
|
||||
System.out.println("\nSa-Token-OAuth2 Server端启动成功,配置如下:");
|
||||
System.out.println(SaOAuth2Manager.getConfig());
|
||||
System.out.println(SaOAuth2Manager.getServerConfig());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
|
||||
.setClientId("1001") // client id
|
||||
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥
|
||||
.addAllowRedirectUris("*") // 所有允许授权的 url
|
||||
.addContractScopes("openid", "userid", "userinfo") // 所有签约的权限
|
||||
.addContractScopes("openid", "userid", "userinfo", "oidc") // 所有签约的权限
|
||||
.addAllowGrantTypes( // 所有允许的授权模式
|
||||
GrantType.authorization_code, // 授权码式
|
||||
GrantType.implicit, // 隐式式
|
||||
|
@ -0,0 +1,32 @@
|
||||
package com.pj.oauth2.custom;
|
||||
|
||||
import cn.dev33.satoken.oauth2.data.model.oidc.IdTokenModel;
|
||||
import cn.dev33.satoken.oauth2.scope.handler.OidcScopeHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 扩展 OIDC 权限处理器,返回更多字段
|
||||
*
|
||||
* @author click33
|
||||
* @since 2024/8/24
|
||||
*/
|
||||
@Component
|
||||
public class CustomOidcScopeHandler extends OidcScopeHandler {
|
||||
|
||||
@Override
|
||||
public IdTokenModel workExtraData(IdTokenModel idToken) {
|
||||
Object userId = idToken.sub;
|
||||
System.out.println("----- 为 idToken 追加扩展字段 ----- ");
|
||||
|
||||
idToken.extraData.put("uid", userId); // 用户id
|
||||
idToken.extraData.put("nickname", "lin_xiao_lin"); // 昵称
|
||||
idToken.extraData.put("picture", "https://sa-token.cc/logo.png"); // 头像
|
||||
idToken.extraData.put("email", "456456@xx.com"); // 邮箱
|
||||
idToken.extraData.put("phone_number", "13144556677"); // 手机号
|
||||
// 更多字段 ...
|
||||
// 可参考:https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
|
||||
|
||||
return idToken;
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,8 @@ sa-token:
|
||||
token-name: sa-token-oauth2-server
|
||||
# 是否打印操作日志
|
||||
is-log: true
|
||||
# jwt 秘钥
|
||||
jwt-secret-key: saxsaxsaxsax
|
||||
# OAuth2.0 配置
|
||||
oauth2-server:
|
||||
# 是否全局开启授权码模式
|
||||
|
@ -61,6 +61,7 @@
|
||||
- [自定义 Scope 权限以处理器](/oauth2/oauth2-custom-scope)
|
||||
- [为 Scope 划分等级](/oauth2/oauth2-scope-level)
|
||||
- [自定义 grant_type](/oauth2/oauth2-custom-grant_type)
|
||||
- [开启 OIDC 协议](/oauth2/oauth2-oidc)
|
||||
- [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking)
|
||||
- [OAuth2 代码 API 参考](/oauth2/oauth2-dev)
|
||||
<!-- - [常见问题说明](/oauth2/8) -->
|
||||
|
154
sa-token-doc/oauth2/oauth2-oidc.md
Normal file
154
sa-token-doc/oauth2/oauth2-oidc.md
Normal file
@ -0,0 +1,154 @@
|
||||
# OAuth2 开启 OIDC 协议 (OpenID Connect)
|
||||
|
||||
---
|
||||
|
||||
### 1、开启步骤
|
||||
|
||||
1、引入 `sa-token-jwt` 依赖,用来签发 `id_token`
|
||||
|
||||
<!---------------------------- tabs:start ---------------------------->
|
||||
<!-------- tab:Maven 方式 -------->
|
||||
``` xml
|
||||
<!-- sa-token-jwt 签发 OIDC id_token 令牌 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jwt</artifactId>
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
<!-------- tab:Gradle 方式 -------->
|
||||
``` gradle
|
||||
// sa-token-jwt 签发 OIDC id_token 令牌
|
||||
implementation 'cn.dev33:sa-token-jwt:${sa.top.version}'
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
|
||||
2、在 `SaOAuth2DataLoader` 实现类中,返回的 `SaClientModel` 中添加 `oidc` 的签约权限。
|
||||
|
||||
``` java
|
||||
@Component
|
||||
public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
|
||||
@Override
|
||||
public SaClientModel getClientModel(String clientId) {
|
||||
// 此为模拟数据,真实环境需要从数据库查询
|
||||
if("1001".equals(clientId)) {
|
||||
return new SaClientModel()
|
||||
// ....
|
||||
.addContractScopes("openid", "userid", "userinfo", "oidc") // 此处添加上签约权限:oidc
|
||||
.addAllowGrantTypes(
|
||||
// ...
|
||||
)
|
||||
;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// 其它代码 ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 2、测试
|
||||
|
||||
1、在 OAuth2-Client 端申请授权码时,添加上 `oidc` 权限:
|
||||
``` url
|
||||
http://sa-oauth-server.com:8000/oauth2/authorize
|
||||
?response_type=code
|
||||
&client_id=1001
|
||||
&redirect_uri=http://sa-oauth-client.com:8002/
|
||||
&scope=oidc
|
||||
```
|
||||
|
||||
2、得到授权码后,然后拿着 `code` 换 `access_token`
|
||||
``` url
|
||||
http://sa-oauth-server.com:8000/oauth2/token
|
||||
?grant_type=authorization_code
|
||||
&client_id=1001
|
||||
&client_secret=aaaa-bbbb-cccc-dddd-eeee
|
||||
&code=${code}
|
||||
```
|
||||
|
||||
3、返回的结果中将包含 `id_token` 字段:
|
||||
``` js
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "ok",
|
||||
"data": null,
|
||||
"token_type": "bearer",
|
||||
"access_token": "WdpjZdGlXdOzsAcr7gqPwmLVInHrhpznQa2pDOVqZmLXQynBflkcWqE6f5o2",
|
||||
"refresh_token": "hKHwBm3eH6iqSHlXRGWQaziV8OoyHvzmUb97lKEEZnZJLt3NunBFx7rVZWbT",
|
||||
"expires_in": 7199,
|
||||
"refresh_expires_in": 2591999,
|
||||
"client_id": "1001",
|
||||
"scope": "oidc",
|
||||
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vc2Etb2F1dGgtc2VydmVyLmNvbTo4MDAwIiwic3ViIjoiMTAwMDEiLCJhdWQiOiIxMDAxIiwiZXhwIjoxNzI0NDI1OTg5LCJpYXQiOjE3MjQ0MjUzODksImF1dGhfdGltZSI6MTcyNDQwMDUyNiwibm9uY2UiOiJLTHlNR08zZ1R0YVdhMEFRcHF0RUNpTk9SWkY1QkhvRCIsImF6cCI6IjEwMDEifQ.gP3UYMexaQ9v0huKUuqhV9-dPxPpaEuFPIlPb2UZaOI"
|
||||
}
|
||||
```
|
||||
|
||||
4、解析 `id_token` 将得到以下载荷
|
||||
``` js
|
||||
{
|
||||
"iss": "http://sa-oauth-server.com:8000", // 签发人
|
||||
"sub": "10001", // userId
|
||||
"aud": "1001", // clientId
|
||||
"exp": 1724425989, // 令牌到期时间,10位时间戳
|
||||
"iat": 1724425389, // 签发此令牌的时间,10位时间戳
|
||||
"auth_time": 1724400526, // 用户认证时间,10位时间戳
|
||||
"nonce": "KLyMGO3gTtaWa0AQpqtECiNORZF5BHoD", // 随机数,防止重放攻击
|
||||
"azp": "1001" // clientId
|
||||
}
|
||||
```
|
||||
|
||||
如果默认携带的载荷无法满足你的业务需求,你还可以自定义追加扩展字段,让 `id_token` 返回更多信息
|
||||
|
||||
|
||||
### 3、扩展 id_token 载荷
|
||||
|
||||
新建 `CustomOidcScopeHandler` 集成 `OidcScopeHandler`,扩展 OIDC 权限处理器,返回更多字段:
|
||||
``` java
|
||||
/**
|
||||
* 扩展 OIDC 权限处理器,返回更多字段
|
||||
*/
|
||||
@Component
|
||||
public class CustomOidcScopeHandler extends OidcScopeHandler {
|
||||
|
||||
@Override
|
||||
public IdTokenModel workExtraData(IdTokenModel idToken) {
|
||||
Object userId = idToken.sub;
|
||||
System.out.println("----- 为 idToken 追加扩展字段 ----- ");
|
||||
|
||||
idToken.extraData.put("uid", userId); // 用户id
|
||||
idToken.extraData.put("nickname", "lin_xiao_lin"); // 昵称
|
||||
idToken.extraData.put("picture", "https://sa-token.cc/logo.png"); // 头像
|
||||
idToken.extraData.put("email", "456456@xx.com"); // 邮箱
|
||||
idToken.extraData.put("phone_number", "13144556677"); // 手机号
|
||||
// 更多字段 ...
|
||||
// 可参考:https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
|
||||
|
||||
return idToken;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
重启项目,再次请求授权,返回的 `id_token` 载荷将包含更多字段:
|
||||
|
||||
``` js
|
||||
{
|
||||
"iss": "http://sa-oauth-server.com:8000",
|
||||
"sub": "10001",
|
||||
"aud": "1001",
|
||||
"exp": 1724430149,
|
||||
"iat": 1724429549,
|
||||
"auth_time": 1724400526,
|
||||
"nonce": "SBRLOcfeo9FFmLTB8OINvuulam5FMOre",
|
||||
"azp": "1001",
|
||||
"uid": "10001",
|
||||
"nickname": "lin_xiao_lin",
|
||||
"picture": "https://sa-token.cc/logo.png",
|
||||
"email": "456456@xx.com",
|
||||
"phone_number": "13144556677"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -309,4 +309,24 @@ public class SaJwtTemplate {
|
||||
return (effTime - System.currentTimeMillis()) / 1000;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -------------- 其它方法
|
||||
|
||||
/**
|
||||
* 创建 jwt (Map 参数方式)
|
||||
*
|
||||
* @param map 扩展数据
|
||||
* @param keyt 秘钥
|
||||
* @return jwt-token
|
||||
*/
|
||||
public String createToken(Map<String, Object> map, String keyt) {
|
||||
// 创建
|
||||
JWT jwt = JWT.create().addPayloads(map);
|
||||
|
||||
// 返回
|
||||
return generateToken(jwt, keyt);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -15,11 +15,11 @@
|
||||
*/
|
||||
package cn.dev33.satoken.jwt;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.jwt.JWT;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* jwt 操作工具类封装
|
||||
*
|
||||
@ -195,4 +195,17 @@ public class SaJwtUtil {
|
||||
return saJwtTemplate.getTimeout(token, loginType, keyt);
|
||||
}
|
||||
|
||||
|
||||
// -------------- 其它方法
|
||||
|
||||
/**
|
||||
* 创建 jwt (Map 参数方式)
|
||||
*
|
||||
* @param map 扩展数据
|
||||
* @param keyt 秘钥
|
||||
* @return jwt-token
|
||||
*/
|
||||
public static String createToken(Map<String, Object> map, String keyt) {
|
||||
return saJwtTemplate.createToken(map, keyt);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,12 @@
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-core</artifactId>
|
||||
</dependency>
|
||||
<!-- sa-token-jwt 签发 OIDC id_token 令牌 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jwt</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
@ -41,20 +41,20 @@ public class SaOAuth2Manager {
|
||||
/**
|
||||
* OAuth2 配置 Bean
|
||||
*/
|
||||
private static volatile SaOAuth2ServerConfig config;
|
||||
public static SaOAuth2ServerConfig getConfig() {
|
||||
if (config == null) {
|
||||
private static volatile SaOAuth2ServerConfig serverConfig;
|
||||
public static SaOAuth2ServerConfig getServerConfig() {
|
||||
if (serverConfig == null) {
|
||||
// 初始化默认值
|
||||
synchronized (SaOAuth2Manager.class) {
|
||||
if (config == null) {
|
||||
setConfig(new SaOAuth2ServerConfig());
|
||||
if (serverConfig == null) {
|
||||
setServerConfig(new SaOAuth2ServerConfig());
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
return serverConfig;
|
||||
}
|
||||
public static void setConfig(SaOAuth2ServerConfig config) {
|
||||
SaOAuth2Manager.config = config;
|
||||
public static void setServerConfig(SaOAuth2ServerConfig serverConfig) {
|
||||
SaOAuth2Manager.serverConfig = serverConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.oauth2.config;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Sa-Token OAuth2 Server 端 Oidc 配置类 Model
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.39.0
|
||||
*/
|
||||
public class SaOAuth2OidcConfig implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6541180061782004705L;
|
||||
|
||||
/** iss 值,如不配置则自动计算 */
|
||||
public String iss;
|
||||
|
||||
/** idToken 有效期(单位秒) 默认十分钟 */
|
||||
public long idTokenTimeout = 60 * 10;
|
||||
|
||||
public String getIss() {
|
||||
return iss;
|
||||
}
|
||||
|
||||
public SaOAuth2OidcConfig setIss(String iss) {
|
||||
this.iss = iss;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getIdTokenTimeout() {
|
||||
return idTokenTimeout;
|
||||
}
|
||||
|
||||
public SaOAuth2OidcConfig setIdTokenTimeout(long idTokenTimeout) {
|
||||
this.idTokenTimeout = idTokenTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaOAuth2OidcConfig{" +
|
||||
"iss='" + iss + '\'' +
|
||||
", idTokenTimeout=" + idTokenTimeout +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
@ -72,6 +72,11 @@ public class SaOAuth2ServerConfig implements Serializable {
|
||||
/** 指定低级权限,多个用逗号隔开 */
|
||||
public String lowerScope;
|
||||
|
||||
/**
|
||||
* oidc 相关配置
|
||||
*/
|
||||
SaOAuth2OidcConfig oidc = new SaOAuth2OidcConfig();
|
||||
|
||||
/**
|
||||
* @return enableCode
|
||||
*/
|
||||
@ -287,6 +292,26 @@ public class SaOAuth2ServerConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 oidc 相关配置
|
||||
*
|
||||
* @return oidc 相关配置
|
||||
*/
|
||||
public SaOAuth2OidcConfig getOidc() {
|
||||
return this.oidc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 oidc 相关配置
|
||||
*
|
||||
* @param oidc /
|
||||
* @return /
|
||||
*/
|
||||
public SaOAuth2ServerConfig setOidc(SaOAuth2OidcConfig oidc) {
|
||||
this.oidc = oidc;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// -------------------- SaOAuth2Handle 所有回调函数 --------------------
|
||||
|
||||
@ -321,6 +346,7 @@ public class SaOAuth2ServerConfig implements Serializable {
|
||||
", openidDigestPrefix='" + openidDigestPrefix +
|
||||
", higherScope='" + higherScope +
|
||||
", lowerScope='" + lowerScope +
|
||||
", oidc='" + oidc +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ public interface SaOAuth2Dao {
|
||||
if(c == null) {
|
||||
return;
|
||||
}
|
||||
getSaTokenDao().setObject(splicingCodeSaveKey(c.code), c, SaOAuth2Manager.getConfig().getCodeTimeout());
|
||||
getSaTokenDao().setObject(splicingCodeSaveKey(c.code), c, SaOAuth2Manager.getServerConfig().getCodeTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,7 +56,7 @@ public interface SaOAuth2Dao {
|
||||
if(c == null) {
|
||||
return;
|
||||
}
|
||||
getSaTokenDao().set(splicingCodeIndexKey(c.clientId, c.loginId), c.code, SaOAuth2Manager.getConfig().getCodeTimeout());
|
||||
getSaTokenDao().set(splicingCodeIndexKey(c.clientId, c.loginId), c.code, SaOAuth2Manager.getServerConfig().getCodeTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,7 +151,7 @@ public interface SaOAuth2Dao {
|
||||
default void saveGrantScope(String clientId, Object loginId, List<String> scopes) {
|
||||
if( ! SaFoxUtil.isEmpty(scopes)) {
|
||||
// TODO ttl 计算规则优化
|
||||
long ttl = SaOAuth2Manager.getConfig().getAccessTokenTimeout();
|
||||
long ttl = SaOAuth2Manager.getServerConfig().getAccessTokenTimeout();
|
||||
// long ttl = checkClientModel(clientId).getAccessTokenTimeout();
|
||||
String value = SaOAuth2Manager.getDataConverter().convertScopeListToString(scopes);
|
||||
getSaTokenDao().set(splicingGrantScopeKey(clientId, loginId), value, ttl);
|
||||
|
@ -60,7 +60,7 @@ public interface SaOAuth2DataLoader {
|
||||
* @return 此账号在此Client下的openid
|
||||
*/
|
||||
default String getOpenid(String clientId, Object loginId) {
|
||||
return SaSecureUtil.md5(SaOAuth2Manager.getConfig().getOpenidDigestPrefix() + "_" + clientId + "_" + loginId);
|
||||
return SaSecureUtil.md5(SaOAuth2Manager.getServerConfig().getOpenidDigestPrefix() + "_" + clientId + "_" + loginId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ public class SaClientModel implements Serializable {
|
||||
|
||||
|
||||
public SaClientModel() {
|
||||
SaOAuth2ServerConfig config = SaOAuth2Manager.getConfig();
|
||||
SaOAuth2ServerConfig config = SaOAuth2Manager.getServerConfig();
|
||||
this.isNewRefresh = config.getIsNewRefresh();
|
||||
this.accessTokenTimeout = config.getAccessTokenTimeout();
|
||||
this.refreshTokenTimeout = config.getRefreshTokenTimeout();
|
||||
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.oauth2.data.model.oidc;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* OIDC IdToken Model
|
||||
*
|
||||
* <br/> 参考:
|
||||
* <br/> <a href="https://openid.net/specs/openid-connect-core-1_0.html#IDToken">IDToken</a>
|
||||
* <br/> <a href="https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims">StandardClaims</a>
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.23.0
|
||||
*/
|
||||
public class IdTokenModel implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6541180061782004705L;
|
||||
|
||||
/**
|
||||
* 必填:发行者标识符,例如:https://server.example.com
|
||||
*/
|
||||
public String iss;
|
||||
|
||||
/**
|
||||
* 必填:用户标识符,用户id,例如:10001
|
||||
*/
|
||||
public Object sub;
|
||||
|
||||
/**
|
||||
* 必填:客户端标识符,clientId,例如:s6BhdRkqt3
|
||||
*/
|
||||
public String aud;
|
||||
|
||||
/**
|
||||
* 必填:令牌到期时间,10位时间戳,例如:1723341795
|
||||
*/
|
||||
public long exp;
|
||||
|
||||
/**
|
||||
* 必填:签发此令牌的时间,10位时间戳,例如:1723339995
|
||||
*/
|
||||
public long iat;
|
||||
|
||||
/**
|
||||
* 用户认证时间,10位时间戳,例如:1723339988
|
||||
*/
|
||||
public long authTime;
|
||||
|
||||
/**
|
||||
* 随机数,客户端提供,防止重放攻击,例如:e9a3f4d9
|
||||
*/
|
||||
public String nonce;
|
||||
|
||||
/**
|
||||
* 身份验证上下文类引用
|
||||
*/
|
||||
public String acr;
|
||||
|
||||
/**
|
||||
* 身份验证方法参考
|
||||
*/
|
||||
public String amr;
|
||||
|
||||
/**
|
||||
* 授权方 - 签发 ID 令牌的一方,如果存在,它必须包含此方的 OAuth 2.0 客户端 ID。
|
||||
*/
|
||||
public String azp;
|
||||
|
||||
/**
|
||||
* 扩展数据
|
||||
*/
|
||||
public Map<String, Object> extraData;
|
||||
|
||||
}
|
@ -29,7 +29,15 @@ public class SaOAuth2Exception extends SaTokenException {
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130114L;
|
||||
|
||||
|
||||
/**
|
||||
* 一个异常:代表OAuth2认证流程错误
|
||||
* @param cause 根异常原因
|
||||
*/
|
||||
public SaOAuth2Exception(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个异常:代表OAuth2认证流程错误
|
||||
* @param message 异常描述
|
||||
|
@ -70,7 +70,7 @@ public class PasswordGrantTypeHandler implements SaOAuth2GrantTypeHandlerInterfa
|
||||
* @param password /
|
||||
*/
|
||||
public void loginByUsernamePassword(String username, String password) {
|
||||
SaOAuth2Manager.getConfig().doLoginHandle.apply(username, password);
|
||||
SaOAuth2Manager.getServerConfig().doLoginHandle.apply(username, password);
|
||||
}
|
||||
|
||||
}
|
@ -113,7 +113,7 @@ public class SaOAuth2ServerProcessor {
|
||||
// 获取变量
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaResponse res = SaHolder.getResponse();
|
||||
SaOAuth2ServerConfig cfg = SaOAuth2Manager.getConfig();
|
||||
SaOAuth2ServerConfig cfg = SaOAuth2Manager.getServerConfig();
|
||||
SaOAuth2DataGenerate dataGenerate = SaOAuth2Manager.getDataGenerate();
|
||||
SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate();
|
||||
String responseType = req.getParamNotNull(Param.response_type);
|
||||
@ -218,7 +218,7 @@ public class SaOAuth2ServerProcessor {
|
||||
public Object doLogin() {
|
||||
// 获取变量
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaOAuth2ServerConfig cfg = SaOAuth2Manager.getConfig();
|
||||
SaOAuth2ServerConfig cfg = SaOAuth2Manager.getServerConfig();
|
||||
|
||||
return cfg.doLoginHandle.apply(req.getParam(Param.name), req.getParam(Param.pwd));
|
||||
}
|
||||
@ -285,7 +285,7 @@ public class SaOAuth2ServerProcessor {
|
||||
public Object clientToken() {
|
||||
// 获取变量
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
SaOAuth2ServerConfig cfg = SaOAuth2Manager.getConfig();
|
||||
SaOAuth2ServerConfig cfg = SaOAuth2Manager.getServerConfig();
|
||||
SaOAuth2Template oauth2Template = SaOAuth2Manager.getTemplate();
|
||||
|
||||
String grantType = req.getParamNotNull(Param.grant_type);
|
||||
|
@ -15,9 +15,25 @@
|
||||
*/
|
||||
package cn.dev33.satoken.oauth2.scope.handler;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.jwt.SaJwtUtil;
|
||||
import cn.dev33.satoken.jwt.error.SaJwtErrorCode;
|
||||
import cn.dev33.satoken.jwt.exception.SaJwtException;
|
||||
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
|
||||
import cn.dev33.satoken.oauth2.data.model.AccessTokenModel;
|
||||
import cn.dev33.satoken.oauth2.data.model.ClientTokenModel;
|
||||
import cn.dev33.satoken.oauth2.data.model.oidc.IdTokenModel;
|
||||
import cn.dev33.satoken.oauth2.data.model.request.ClientIdAndSecretModel;
|
||||
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
|
||||
import cn.dev33.satoken.oauth2.scope.CommonScope;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* id_token 权限处理器:在 AccessToken 扩展参数中追加 id_token 字段
|
||||
@ -33,7 +49,31 @@ public class OidcScopeHandler implements SaOAuth2ScopeHandlerInterface {
|
||||
|
||||
@Override
|
||||
public void workAccessToken(AccessTokenModel at) {
|
||||
// TODO 追加参数 id_token
|
||||
SaRequest req = SaHolder.getRequest();
|
||||
ClientIdAndSecretModel client = SaOAuth2Manager.getDataResolver().readClientIdAndSecret(req);
|
||||
|
||||
// 基础参数
|
||||
IdTokenModel idToken = new IdTokenModel();
|
||||
idToken.iss = getIss();
|
||||
idToken.sub = at.loginId;
|
||||
idToken.aud = client.clientId;
|
||||
idToken.iat = System.currentTimeMillis() / 1000;
|
||||
idToken.exp = idToken.iat + SaOAuth2Manager.getServerConfig().getOidc().getIdTokenTimeout();
|
||||
idToken.authTime = SaOAuth2Manager.getStpLogic().getSessionByLoginId(at.loginId).getCreateTime() / 1000;
|
||||
idToken.nonce = getNonce();
|
||||
idToken.acr = null;
|
||||
idToken.amr = null;
|
||||
idToken.azp = client.clientId;
|
||||
|
||||
// 额外参数
|
||||
idToken.extraData = new LinkedHashMap<>();
|
||||
idToken = workExtraData(idToken);
|
||||
|
||||
// 构建 jwtIdToken
|
||||
String jwtIdToken = generateJwtIdToken(idToken);
|
||||
|
||||
// 放入 AccessTokenModel
|
||||
at.extraData.put("id_token", jwtIdToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -41,4 +81,84 @@ public class OidcScopeHandler implements SaOAuth2ScopeHandlerInterface {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 iss
|
||||
* @return /
|
||||
*/
|
||||
public String getIss() {
|
||||
String urlString = SaHolder.getRequest().getUrl();
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
String iss = url.getProtocol() + "://" + url.getHost();
|
||||
if(url.getPort() != -1) {
|
||||
iss += ":" + url.getPort();
|
||||
}
|
||||
return iss;
|
||||
} catch (MalformedURLException e) {
|
||||
throw new SaOAuth2Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 nonce
|
||||
* @return /
|
||||
*/
|
||||
public String getNonce() {
|
||||
String nonce = SaHolder.getRequest().getParam("nonce");
|
||||
if(SaFoxUtil.isEmpty(nonce)) {
|
||||
nonce = SaFoxUtil.getRandomString(32);
|
||||
}
|
||||
SaManager.getSaSignTemplate().checkNonce(nonce);
|
||||
return nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加工 IdTokenModel
|
||||
* @return /
|
||||
*/
|
||||
public IdTokenModel workExtraData(IdTokenModel idToken) {
|
||||
//
|
||||
return idToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 IdTokenModel 转化为 Map 数据
|
||||
* @return /
|
||||
*/
|
||||
public Map<String, Object> convertIdTokenToMap(IdTokenModel idToken) {
|
||||
// 基础参数
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("iss", idToken.iss);
|
||||
map.put("sub", idToken.sub);
|
||||
map.put("aud", idToken.aud);
|
||||
map.put("exp", idToken.exp);
|
||||
map.put("iat", idToken.iat);
|
||||
map.put("auth_time", idToken.authTime);
|
||||
map.put("nonce", idToken.nonce);
|
||||
map.put("acr", idToken.acr);
|
||||
map.put("amr", idToken.amr);
|
||||
map.put("azp", idToken.azp);
|
||||
|
||||
// 移除 null 值
|
||||
idToken.extraData.entrySet().removeIf(entry -> entry.getValue() == null);
|
||||
|
||||
// 扩展参数
|
||||
map.putAll(idToken.extraData);
|
||||
|
||||
// 返回
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 jwt 格式的 id_token
|
||||
* @param idToken /
|
||||
* @return /
|
||||
*/
|
||||
public String generateJwtIdToken(IdTokenModel idToken) {
|
||||
Map<String, Object> dataMap = convertIdTokenToMap(idToken);
|
||||
String keyt = SaOAuth2Manager.getStpLogic().getConfigOrGlobal().getJwtSecretKey();
|
||||
SaJwtException.throwByNull(keyt, "请配置jwt秘钥", SaJwtErrorCode.CODE_30205);
|
||||
return SaJwtUtil.createToken(dataMap, keyt);
|
||||
}
|
||||
|
||||
}
|
@ -170,7 +170,7 @@ public final class SaOAuth2Strategy {
|
||||
}
|
||||
|
||||
// 看看全局是否开启了此 grantType
|
||||
SaOAuth2ServerConfig config = SaOAuth2Manager.getConfig();
|
||||
SaOAuth2ServerConfig config = SaOAuth2Manager.getServerConfig();
|
||||
if(grantType.equals(GrantType.authorization_code) && !config.getEnableAuthorizationCode() ) {
|
||||
throw new SaOAuth2Exception("系统未开放的 grant_type: " + grantType);
|
||||
}
|
||||
|
@ -401,7 +401,7 @@ public class SaOAuth2Template {
|
||||
* @return /
|
||||
*/
|
||||
public List<String> getHigherScopeList() {
|
||||
String higherScope = SaOAuth2Manager.getConfig().getHigherScope();
|
||||
String higherScope = SaOAuth2Manager.getServerConfig().getHigherScope();
|
||||
return SaOAuth2Manager.getDataConverter().convertScopeStringToList(higherScope);
|
||||
}
|
||||
|
||||
@ -410,7 +410,7 @@ public class SaOAuth2Template {
|
||||
* @return /
|
||||
*/
|
||||
public List<String> getLowerScopeList() {
|
||||
String lowerScope = SaOAuth2Manager.getConfig().getLowerScope();
|
||||
String lowerScope = SaOAuth2Manager.getServerConfig().getLowerScope();
|
||||
return SaOAuth2Manager.getDataConverter().convertScopeStringToList(lowerScope);
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,6 @@ package cn.dev33.satoken.solon.oauth2;
|
||||
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
|
||||
import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig;
|
||||
import cn.dev33.satoken.oauth2.template.SaOAuth2Template;
|
||||
import cn.dev33.satoken.oauth2.template.SaOAuth2Util;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Condition;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
@ -36,11 +35,11 @@ public class SaOAuth2AutoConfigure {
|
||||
@Bean
|
||||
public void init(AppContext appContext) throws Throwable {
|
||||
appContext.subBeansOfType(SaOAuth2Template.class, bean -> {
|
||||
SaOAuth2Util.saOAuth2Template = bean;
|
||||
SaOAuth2Manager.setTemplate(bean);
|
||||
});
|
||||
|
||||
appContext.subBeansOfType(SaOAuth2ServerConfig.class, bean -> {
|
||||
SaOAuth2Manager.setConfig(bean);
|
||||
SaOAuth2Manager.setServerConfig(bean);
|
||||
});
|
||||
}
|
||||
|
||||
@ -48,7 +47,7 @@ public class SaOAuth2AutoConfigure {
|
||||
* 获取 OAuth2配置Bean
|
||||
*/
|
||||
@Bean
|
||||
public SaOAuth2ServerConfig getConfig(@Inject(value = "${sa-token.oauth2}", required = false) SaOAuth2ServerConfig oAuth2Config) {
|
||||
public SaOAuth2ServerConfig getConfig(@Inject(value = "${sa-token.oauth2-server}", required = false) SaOAuth2ServerConfig oAuth2Config) {
|
||||
return oAuth2Config;
|
||||
}
|
||||
}
|
@ -53,7 +53,7 @@ public class SaOAuth2BeanInject {
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setSaOAuth2Config(SaOAuth2ServerConfig saOAuth2Config) {
|
||||
SaOAuth2Manager.setConfig(saOAuth2Config);
|
||||
SaOAuth2Manager.setServerConfig(saOAuth2Config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user