新增 scope 等级划分,可指定哪些权限需要强制每次手动授权

This commit is contained in:
click33
2024-08-21 13:57:05 +08:00
parent 1bc59dc14c
commit 4aa4941598
16 changed files with 545 additions and 71 deletions

View File

@@ -66,7 +66,11 @@ public class SaOAuth2Config implements Serializable {
/** 默认 openid 生成算法中使用的摘要前缀 */
public String openidDigestPrefix = SaOAuth2Consts.OPENID_DEFAULT_DIGEST_PREFIX;
/** 指定高级权限,多个用逗号隔开 */
public String higherScope;
/** 指定低级权限,多个用逗号隔开 */
public String lowerScope;
/**
* @return enableCode
@@ -77,9 +81,11 @@ public class SaOAuth2Config implements Serializable {
/**
* @param enableCode 要设置的 enableCode
* @return /
*/
public void setEnableCode(Boolean enableCode) {
public SaOAuth2Config setEnableCode(Boolean enableCode) {
this.enableCode = enableCode;
return this;
}
/**
@@ -91,9 +97,11 @@ public class SaOAuth2Config implements Serializable {
/**
* @param enableImplicit 要设置的 enableImplicit
* @return /
*/
public void setEnableImplicit(Boolean enableImplicit) {
public SaOAuth2Config setEnableImplicit(Boolean enableImplicit) {
this.enableImplicit = enableImplicit;
return this;
}
/**
@@ -106,8 +114,9 @@ public class SaOAuth2Config implements Serializable {
/**
* @param enablePassword 要设置的 enablePassword
*/
public void setEnablePassword(Boolean enablePassword) {
public SaOAuth2Config setEnablePassword(Boolean enablePassword) {
this.enablePassword = enablePassword;
return this;
}
/**
@@ -119,9 +128,11 @@ public class SaOAuth2Config implements Serializable {
/**
* @param enableClient 要设置的 enableClient
* @return /
*/
public void setEnableClient(Boolean enableClient) {
public SaOAuth2Config setEnableClient(Boolean enableClient) {
this.enableClient = enableClient;
return this;
}
/**
@@ -133,9 +144,11 @@ public class SaOAuth2Config implements Serializable {
/**
* @param isNewRefresh 要设置的 isNewRefresh
* @return /
*/
public void setIsNewRefresh(Boolean isNewRefresh) {
public SaOAuth2Config setIsNewRefresh(Boolean isNewRefresh) {
this.isNewRefresh = isNewRefresh;
return this;
}
/**
@@ -229,13 +242,53 @@ public class SaOAuth2Config implements Serializable {
* @param openidDigestPrefix 要设置的 openidDigestPrefix
* @return 对象自身
*/
public SaOAuth2Config setOpenidMd5Prefix(String openidDigestPrefix) {
public SaOAuth2Config setOpenidDigestPrefix(String openidDigestPrefix) {
this.openidDigestPrefix = openidDigestPrefix;
return this;
}
// -------------------- SaOAuth2Handle 所有回调函数 --------------------
/**
* 获取 指定高级权限,多个用逗号隔开
*
* @return higherScope 指定高级权限,多个用逗号隔开
*/
public String getHigherScope() {
return this.higherScope;
}
/**
* 设置 指定高级权限,多个用逗号隔开
*
* @param higherScope 指定高级权限,多个用逗号隔开
* @return /
*/
public SaOAuth2Config setHigherScope(String higherScope) {
this.higherScope = higherScope;
return this;
}
/**
* 获取 指定低级权限,多个用逗号隔开
*
* @return lowerScope 指定低级权限,多个用逗号隔开
*/
public String getLowerScope() {
return this.lowerScope;
}
/**
* 设置 指定低级权限,多个用逗号隔开
*
* @param lowerScope 指定低级权限,多个用逗号隔开
* @return /
*/
public SaOAuth2Config setLowerScope(String lowerScope) {
this.lowerScope = lowerScope;
return this;
}
// -------------------- SaOAuth2Handle 所有回调函数 --------------------
/**
* OAuth-Server端未登录时返回的View
@@ -254,19 +307,20 @@ public class SaOAuth2Config implements Serializable {
@Override
public String toString() {
return "SaOAuth2Config [" +
"enableCode=" + enableCode
+ ", enableImplicit=" + enableImplicit
+ ", enablePassword=" + enablePassword
+ ", enableClient=" + enableClient
+ ", isNewRefresh=" + isNewRefresh
+ ", codeTimeout=" + codeTimeout
+ ", accessTokenTimeout=" + accessTokenTimeout
+ ", refreshTokenTimeout=" + refreshTokenTimeout
+ ", clientTokenTimeout=" + clientTokenTimeout
+ ", pastClientTokenTimeout=" + pastClientTokenTimeout
+ ", openidDigestPrefix=" + openidDigestPrefix
+"]";
return "SaOAuth2Config{" +
"enableCode=" + enableCode +
", enableImplicit=" + enableImplicit +
", enablePassword=" + enablePassword +
", enableClient=" + enableClient +
", isNewRefresh=" + isNewRefresh +
", codeTimeout=" + codeTimeout +
", accessTokenTimeout=" + accessTokenTimeout +
", refreshTokenTimeout=" + refreshTokenTimeout +
", clientTokenTimeout=" + clientTokenTimeout +
", pastClientTokenTimeout=" + pastClientTokenTimeout +
", openidDigestPrefix='" + openidDigestPrefix +
", higherScope='" + higherScope +
", lowerScope='" + lowerScope +
'}';
}
}

View File

@@ -57,6 +57,7 @@ public class SaOAuth2Consts {
public static String password = "password";
public static String name = "name";
public static String pwd = "pwd";
public static String build_redirect_uri = "build_redirect_uri";
}
/**

View File

@@ -112,5 +112,8 @@ public interface SaOAuth2ErrorCode {
/** 暂未开放凭证式模式 */
int CODE_30134 = 30134;
/** 无效的请求 Method */
int CODE_30141 = 30141;
}

View File

@@ -35,6 +35,7 @@ import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel;
import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.template.SaOAuth2Template;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
@@ -124,28 +125,7 @@ public class SaOAuth2ServerProcessor {
String responseType = req.getParamNotNull(Param.response_type);
// 1、先判断是否开启了指定的授权模式
// 模式一Code授权码
if(responseType.equals(ResponseType.code)) {
if(!cfg.enableCode) {
throwErrorSystemNotEnableModel();
}
if(!currClientModel().enableCode) {
throwErrorClientNotEnableModel();
}
}
// 模式二:隐藏式
else if(responseType.equals(ResponseType.token)) {
if(!cfg.enableImplicit) {
throwErrorSystemNotEnableModel();
}
if(!currClientModel().enableImplicit) {
throwErrorClientNotEnableModel();
}
}
// 其它
else {
throw new SaOAuth2Exception("无效 response_type: " + req.getParam(Param.response_type)).setCode(SaOAuth2ErrorCode.CODE_30125);
}
checkAuthorizeResponseType(responseType, req, cfg);
// 2、如果尚未登录, 则先去登录
if( ! getStpLogic().isLogin()) {
@@ -162,8 +142,8 @@ public class SaOAuth2ServerProcessor {
oauth2Template.checkContract(ra.clientId, ra.scopes);
// 6、判断如果此次申请的Scope该用户尚未授权则转到授权页面
boolean isGrant = oauth2Template.isGrant(ra.loginId, ra.clientId, ra.scopes);
if( ! isGrant) {
boolean isNeedCarefulConfirm = oauth2Template.isNeedCarefulConfirm(ra.loginId, ra.clientId, ra.scopes);
if(isNeedCarefulConfirm) {
return cfg.confirmView.apply(ra.clientId, ra.scopes);
}
@@ -306,7 +286,7 @@ public class SaOAuth2ServerProcessor {
SaRequest req = SaHolder.getRequest();
SaOAuth2Config cfg = SaOAuth2Manager.getConfig();
return cfg.doLoginHandle.apply(req.getParamNotNull(Param.name), req.getParamNotNull(Param.pwd));
return cfg.doLoginHandle.apply(req.getParam(Param.name), req.getParam(Param.pwd));
}
/**
@@ -316,13 +296,51 @@ public class SaOAuth2ServerProcessor {
public Object doConfirm() {
// 获取变量
SaRequest req = SaHolder.getRequest();
String clientId = req.getParamNotNull(Param.client_id);
Object loginId = getStpLogic().getLoginId();
String scope = req.getParamNotNull(Param.scope);
List<String> scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(scope);
Object loginId = getStpLogic().getLoginId();
SaOAuth2DataGenerate dataGenerate = SaOAuth2Manager.getDataGenerate();
// 此请求只允许 POST 方式
if(!req.isMethod(SaHttpMethod.POST)) {
throw new SaOAuth2Exception("无效请求方式:" + req.getMethod()).setCode(SaOAuth2ErrorCode.CODE_30141);
}
// 确认授权
oauth2Template.saveGrantScope(clientId, loginId, scopes);
return SaResult.ok();
// 判断所需的返回结果模式
boolean buildRedirectUri = req.isParam(Param.build_redirect_uri, "true");
// -------- 情况1只返回确认结果即可
if( ! buildRedirectUri ) {
oauth2Template.saveGrantScope(clientId, loginId, scopes);
return SaResult.ok();
}
// -------- 情况2需要返回最终的 redirect_uri 地址
// s3、构建请求 Model
RequestAuthModel ra = SaOAuth2Manager.getDataResolver().readRequestAuthModel(req, loginId);
// 7、判断授权类型构建不同的重定向地址
// 如果是 授权码式开始重定向授权下放code
if(ResponseType.code.equals(ra.responseType)) {
CodeModel codeModel = dataGenerate.generateCode(ra);
String redirectUri = dataGenerate.buildRedirectUri(ra.redirectUri, codeModel.code, ra.state);
return SaResult.ok().set(Param.redirect_uri, redirectUri);
}
// 如果是 隐藏式,则:开始重定向授权,下放 token
if(ResponseType.token.equals(ra.responseType)) {
AccessTokenModel at = dataGenerate.generateAccessToken(ra, false);
String redirectUri = dataGenerate.buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state);
return SaResult.ok().set(Param.redirect_uri, redirectUri);
}
// 默认返回
throw new SaOAuth2Exception("无效response_type: " + ra.responseType).setCode(SaOAuth2ErrorCode.CODE_30125);
}
/**
@@ -408,6 +426,9 @@ public class SaOAuth2ServerProcessor {
return SaOAuth2Manager.getDataResolver().buildClientTokenReturnValue(ct);
}
// ----------- 代码块封装 --------------
/**
* 根据当前请求提交的 client_id 参数获取 SaClientModel 对象
* @return /
@@ -417,6 +438,34 @@ public class SaOAuth2ServerProcessor {
return oauth2Template.checkClientModel(clientIdAndSecret.clientId);
}
/**
* 校验 authorize 路由的 ResponseType 参数
*/
public void checkAuthorizeResponseType(String responseType, SaRequest req, SaOAuth2Config cfg) {
// 模式一Code授权码
if(responseType.equals(ResponseType.code)) {
if(!cfg.enableCode) {
throwErrorSystemNotEnableModel();
}
if(!currClientModel().enableCode) {
throwErrorClientNotEnableModel();
}
}
// 模式二:隐藏式
else if(responseType.equals(ResponseType.token)) {
if(!cfg.enableImplicit) {
throwErrorSystemNotEnableModel();
}
if(!currClientModel().enableImplicit) {
throwErrorClientNotEnableModel();
}
}
// 其它
else {
throw new SaOAuth2Exception("无效 response_type: " + req.getParam(Param.response_type)).setCode(SaOAuth2ErrorCode.CODE_30125);
}
}
/**
* 获取底层使用的会话对象
*

View File

@@ -123,6 +123,7 @@ public class SaOAuth2Template {
// ------------------- check 数据校验
/**
* 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope
* @param loginId 账号id
@@ -135,6 +136,39 @@ public class SaOAuth2Template {
List<String> grantScopeList = dao.getGrantScope(clientId, loginId);
return scopes.isEmpty() || new HashSet<>(grantScopeList).containsAll(scopes);
}
/**
* 判断:指定 loginId 在指定 Client 请求指定 Scope 时,是否需要手动确认授权
* @param loginId 账号id
* @param clientId 应用id
* @param scopes 权限
* @return 是否已经授权
*/
public boolean isNeedCarefulConfirm(Object loginId, String clientId, List<String> scopes) {
// 如果请求的权限为空,则不需要确认
if(scopes == null || scopes.isEmpty()) {
return false;
}
// 如果包含高级权限,则必须手动确认授权
List<String> higherScopeList = getHigherScopeList();
if(SaFoxUtil.list1ContainList2AnyElement(scopes, higherScopeList)) {
return true;
}
// 如果包含低级权限,则先将低级权限剔除掉
List<String> lowerScopeList = getLowerScopeList();
scopes = SaFoxUtil.list1RemoveByList2(scopes, lowerScopeList);
// 如果剔除后的权限为空,则不需要确认
if(scopes.isEmpty()) {
return false;
}
// 根据近期授权记录,判断是否需要确认
return !isGrant(loginId, clientId, scopes);
}
/**
* 校验该Client是否签约了指定的Scope
* @param clientId 应用id
@@ -362,6 +396,24 @@ public class SaOAuth2Template {
SaOAuth2Manager.getDao().saveGrantScope(clientId, loginId, scopes);
}
/**
* 获取高级权限列表
* @return /
*/
public List<String> getHigherScopeList() {
String higherScope = SaOAuth2Manager.getConfig().getHigherScope();
return SaOAuth2Manager.getDataConverter().convertScopeStringToList(higherScope);
}
/**
* 获取低级权限列表
* @return /
*/
public List<String> getLowerScopeList() {
String lowerScope = SaOAuth2Manager.getConfig().getLowerScope();
return SaOAuth2Manager.getDataConverter().convertScopeStringToList(lowerScope);
}