mirror of
https://gitee.com/dromara/sa-token.git
synced 2026-02-27 16:50:24 +08:00
新增 scope 等级划分,可指定哪些权限需要强制每次手动授权
This commit is contained in:
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -112,5 +112,8 @@ public interface SaOAuth2ErrorCode {
|
||||
|
||||
/** 暂未开放凭证式模式 */
|
||||
int CODE_30134 = 30134;
|
||||
|
||||
|
||||
/** 无效的请求 Method */
|
||||
int CODE_30141 = 30141;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取底层使用的会话对象
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user