mirror of
https://gitee.com/dromara/sa-token.git
synced 2026-02-27 16:50:24 +08:00
新增 OIDC 协议实现
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user