完善 SaOAuth2Util 相关方法

This commit is contained in:
click33
2024-08-25 10:32:52 +08:00
parent e0a609b128
commit 7f20c8d450
18 changed files with 1420 additions and 478 deletions

View File

@@ -85,12 +85,6 @@ public interface SaOAuth2DataGenerate {
*/
String buildImplicitRedirectUri(String redirectUri, String token, String state);
/**
* 回收 Access-Token
* @param accessToken Access-Token值
*/
void revokeAccessToken(String accessToken);
/**
* 检查 state 是否被重复使用
* @param state /

View File

@@ -126,7 +126,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate {
// 删除旧 Refresh-Token
dao.deleteRefreshToken(rt.refreshToken);
// 创建并保新的 Refresh-Token
// 创建并保新的 Refresh-Token
rt = SaOAuth2Manager.getDataConverter().convertRefreshTokenToRefreshToken(rt);
dao.saveRefreshToken(rt);
dao.saveRefreshTokenIndex(rt);
@@ -268,31 +268,6 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate {
return url;
}
/**
* 回收 Access-Token
* @param accessToken Access-Token值
*/
@Override
public void revokeAccessToken(String accessToken) {
SaOAuth2Dao dao = SaOAuth2Manager.getDao();
// 如果查不到任何东西, 直接返回
AccessTokenModel at = dao.getAccessToken(accessToken);
if(at == null) {
return;
}
// 删除 Access-Token
dao.deleteAccessToken(accessToken);
dao.deleteAccessTokenIndex(at.clientId, at.loginId);
// 删除对应的 Refresh-Token
String refreshToken = dao.getRefreshTokenValue(at.clientId, at.loginId);
dao.deleteRefreshToken(refreshToken);
dao.deleteRefreshTokenIndex(at.clientId, at.loginId);
}
/**
* 检查 state 是否被重复使用
* @param state /

View File

@@ -17,7 +17,8 @@ package cn.dev33.satoken.oauth2.data.loader;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode;
import cn.dev33.satoken.oauth2.exception.SaOAuth2ClientModelException;
import cn.dev33.satoken.secure.SaSecureUtil;
/**
@@ -47,7 +48,9 @@ public interface SaOAuth2DataLoader {
default SaClientModel getClientModelNotNull(String clientId) {
SaClientModel clientModel = getClientModel(clientId);
if(clientModel == null) {
throw new SaOAuth2Exception("未找到对应的 Client 信息");
throw new SaOAuth2ClientModelException("无效 client_id: " + clientId)
.setClientId(clientId)
.setCode(SaOAuth2ErrorCode.CODE_30105);
}
return clientModel;
}

View File

@@ -0,0 +1,73 @@
/*
* 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.exception;
/**
* 一个异常:代表 Access-Token 相关错误
*
* @author click33
* @since 1.39.0
*/
public class SaOAuth2AccessTokenException extends SaOAuth2Exception {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130114L;
/**
* 一个异常:代表 Access-Token 相关错误
* @param cause 根异常原因
*/
public SaOAuth2AccessTokenException(Throwable cause) {
super(cause);
}
/**
* 一个异常:代表 Access-Token 相关错误
* @param message 异常描述
*/
public SaOAuth2AccessTokenException(String message) {
super(message);
}
/**
* 具体引起异常的 Access-Token 值
*/
public String accessToken;
public String getAccessToken() {
return accessToken;
}
public SaOAuth2AccessTokenException setAccessToken(String accessToken) {
this.accessToken = accessToken;
return this;
}
/**
* 如果 flag==true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, int code) {
if(flag) {
throw new SaOAuth2AccessTokenException(message).setCode(code);
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.exception;
/**
* 一个异常:代表 Access-Token Scope 相关错误
*
* @author click33
* @since 1.39.0
*/
public class SaOAuth2AccessTokenScopeException extends SaOAuth2AccessTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130114L;
/**
* 一个异常:代表 Access-Token Scope 相关错误
* @param cause 根异常原因
*/
public SaOAuth2AccessTokenScopeException(Throwable cause) {
super(cause);
}
/**
* 一个异常:代表 Access-Token Scope 相关错误
* @param message 异常描述
*/
public SaOAuth2AccessTokenScopeException(String message) {
super(message);
}
/**
* 具体引起异常的 Access-Token 值
*/
public String accessToken;
/**
* 具体引起异常的 scope 值
*/
public String scope;
public String getAccessToken() {
return accessToken;
}
public SaOAuth2AccessTokenScopeException setAccessToken(String accessToken) {
this.accessToken = accessToken;
return this;
}
public String getScope() {
return scope;
}
public SaOAuth2AccessTokenScopeException setScope(String scope) {
this.scope = scope;
return this;
}
/**
* 如果 flag==true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, int code) {
if(flag) {
throw new SaOAuth2AccessTokenScopeException(message).setCode(code);
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.exception;
/**
* 一个异常:代表 ClientModel 相关错误
*
* @author click33
* @since 1.39.0
*/
public class SaOAuth2ClientModelException extends SaOAuth2Exception {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130114L;
/**
* 一个异常:代表 ClientModel 相关错误
* @param cause 根异常原因
*/
public SaOAuth2ClientModelException(Throwable cause) {
super(cause);
}
/**
* 一个异常:代表 ClientModel 相关错误
* @param message 异常描述
*/
public SaOAuth2ClientModelException(String message) {
super(message);
}
/**
* 具体引起异常的 ClientId 值
*/
public String clientId;
public String getClientId() {
return clientId;
}
public SaOAuth2ClientModelException setClientId(String clientId) {
this.clientId = clientId;
return this;
}
/**
* 如果 flag==true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, int code) {
if(flag) {
throw new SaOAuth2ClientModelException(message).setCode(code);
}
}
/**
* 如果 flag==true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param clientId 应用id
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, String clientId, int code) {
if(flag) {
throw new SaOAuth2ClientModelException(message).setClientId(clientId).setCode(code);
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.exception;
/**
* 一个异常:代表 ClientModel Scope 相关错误
*
* @author click33
* @since 1.39.0
*/
public class SaOAuth2ClientModelScopeException extends SaOAuth2ClientModelException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130114L;
/**
* 一个异常:代表 ClientModel Scope 相关错误
* @param cause 根异常原因
*/
public SaOAuth2ClientModelScopeException(Throwable cause) {
super(cause);
}
/**
* 一个异常:代表 ClientModel Scope 相关错误
* @param message 异常描述
*/
public SaOAuth2ClientModelScopeException(String message) {
super(message);
}
/**
* 具体引起异常的 ClientId 值
*/
public String clientId;
/**
* 具体引起异常的 scope 值
*/
public String scope;
public String getClientId() {
return clientId;
}
public SaOAuth2ClientModelScopeException setClientId(String clientId) {
this.clientId = clientId;
return this;
}
public String getScope() {
return scope;
}
public SaOAuth2ClientModelScopeException setScope(String scope) {
this.scope = scope;
return this;
}
/**
* 如果 flag==true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, int code) {
if(flag) {
throw new SaOAuth2ClientModelScopeException(message).setCode(code);
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.exception;
/**
* 一个异常:代表 Client-Token 相关错误
*
* @author click33
* @since 1.39.0
*/
public class SaOAuth2ClientTokenException extends SaOAuth2Exception {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130114L;
/**
* 一个异常:代表 Client-Token 相关错误
* @param cause 根异常原因
*/
public SaOAuth2ClientTokenException(Throwable cause) {
super(cause);
}
/**
* 一个异常:代表 Client-Token 相关错误
* @param message 异常描述
*/
public SaOAuth2ClientTokenException(String message) {
super(message);
}
/**
* 具体引起异常的 Client-Token 值
*/
public String clientToken;
public String getClientToken() {
return clientToken;
}
public SaOAuth2ClientTokenException setClientToken(String clientToken) {
this.clientToken = clientToken;
return this;
}
/**
* 如果 flag==true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, int code) {
if(flag) {
throw new SaOAuth2ClientTokenException(message).setCode(code);
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.exception;
/**
* 一个异常:代表 Client-Token Scope 相关错误
*
* @author click33
* @since 1.39.0
*/
public class SaOAuth2ClientTokenScopeException extends SaOAuth2ClientTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130114L;
/**
* 一个异常:代表 Client-Token Scope 相关错误
* @param cause 根异常原因
*/
public SaOAuth2ClientTokenScopeException(Throwable cause) {
super(cause);
}
/**
* 一个异常:代表 Client-Token Scope 相关错误
* @param message 异常描述
*/
public SaOAuth2ClientTokenScopeException(String message) {
super(message);
}
/**
* 具体引起异常的 Client-Token 值
*/
public String clientToken;
/**
* 具体引起异常的 scope 值
*/
public String scope;
public String getClientToken() {
return clientToken;
}
public SaOAuth2ClientTokenScopeException setClientToken(String clientToken) {
this.clientToken = clientToken;
return this;
}
public String getScope() {
return scope;
}
public SaOAuth2ClientTokenScopeException setScope(String scope) {
this.scope = scope;
return this;
}
/**
* 如果 flag==true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, int code) {
if(flag) {
throw new SaOAuth2ClientTokenScopeException(message).setCode(code);
}
}
}

View File

@@ -18,7 +18,7 @@ package cn.dev33.satoken.oauth2.exception;
import cn.dev33.satoken.exception.SaTokenException;
/**
* 一个异常代表OAuth2认证流程错误
* 一个异常:代表 OAuth2 认证流程错误
*
* @author click33
* @since 1.33.0
@@ -31,7 +31,7 @@ public class SaOAuth2Exception extends SaTokenException {
private static final long serialVersionUID = 6806129545290130114L;
/**
* 一个异常代表OAuth2认证流程错误
* 一个异常:代表 OAuth2 认证流程错误
* @param cause 根异常原因
*/
public SaOAuth2Exception(Throwable cause) {
@@ -39,7 +39,7 @@ public class SaOAuth2Exception extends SaTokenException {
}
/**
* 一个异常代表OAuth2认证流程错误
* 一个异常:代表 OAuth2 认证流程错误
* @param message 异常描述
*/
public SaOAuth2Exception(String message) {
@@ -47,7 +47,7 @@ public class SaOAuth2Exception extends SaTokenException {
}
/**
* 如果flag==true则抛出message异常
* 如果 flag==true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码

View File

@@ -0,0 +1,86 @@
/*
* 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.exception;
/**
* 一个异常:代表 Refresh-Token 相关错误
*
* @author click33
* @since 1.39.0
*/
public class SaOAuth2RefreshTokenException extends SaOAuth2Exception {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130114L;
/**
* 一个异常:代表 Refresh-Token 相关错误
* @param cause 根异常原因
*/
public SaOAuth2RefreshTokenException(Throwable cause) {
super(cause);
}
/**
* 一个异常:代表 Refresh-Token 相关错误
* @param message 异常描述
*/
public SaOAuth2RefreshTokenException(String message) {
super(message);
}
/**
* 具体引起异常的 Refresh-Token 值
*/
public String refreshToken;
public String getRefreshToken() {
return refreshToken;
}
public SaOAuth2RefreshTokenException setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
return this;
}
/**
* 如果 flag==true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, int code) {
if(flag) {
throw new SaOAuth2RefreshTokenException(message).setCode(code);
}
}
/**
* 如果 flag==true则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param refreshToken refreshToken
* @param code 异常细分码
*/
public static void throwBy(boolean flag, String message, String refreshToken, int code) {
if(flag) {
throw new SaOAuth2RefreshTokenException(message).setRefreshToken(refreshToken).setCode(code);
}
}
}

View File

@@ -37,6 +37,7 @@ import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy;
import cn.dev33.satoken.oauth2.template.SaOAuth2Template;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
import java.util.List;
@@ -130,10 +131,10 @@ public class SaOAuth2ServerProcessor {
RequestAuthModel ra = SaOAuth2Manager.getDataResolver().readRequestAuthModel(req, SaOAuth2Manager.getStpLogic().getLoginId());
// 4、校验重定向域名是否合法
oauth2Template.checkRightUrl(ra.clientId, ra.redirectUri);
oauth2Template.checkRedirectUri(ra.clientId, ra.redirectUri);
// 5、校验此次申请的Scope该Client是否已经签约
oauth2Template.checkContract(ra.clientId, ra.scopes);
oauth2Template.checkContractScope(ra.clientId, ra.scopes);
// 6、判断如果此次申请的Scope该用户尚未授权则转到授权页面
boolean isNeedCarefulConfirm = oauth2Template.isNeedCarefulConfirm(ra.loginId, ra.clientId, ra.scopes);
@@ -205,7 +206,7 @@ public class SaOAuth2ServerProcessor {
oauth2Template.checkAccessTokenParam(clientId, clientSecret, accessToken);
// 回收 Access-Token
SaOAuth2Manager.getDataGenerate().revokeAccessToken(accessToken);
oauth2Template.revokeAccessToken(accessToken);
// 返回
return SaOAuth2Manager.getDataResolver().buildRevokeTokenReturnValue();
@@ -306,7 +307,7 @@ public class SaOAuth2ServerProcessor {
List<String> scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(req.getParam(Param.scope));
//校验 ClientScope
oauth2Template.checkContract(clientId, scopes);
oauth2Template.checkContractScope(clientId, scopes);
// 校验 ClientSecret
oauth2Template.checkClientSecret(clientId, clientSecret);

View File

@@ -23,11 +23,10 @@ import cn.dev33.satoken.oauth2.data.model.CodeModel;
import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel;
import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel;
import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.exception.*;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.HashSet;
import java.util.List;
/**
@@ -38,91 +37,206 @@ import java.util.List;
*/
public class SaOAuth2Template {
// ------------------- 数据加载
// ----------------- ClientModel 相关 -----------------
/**
* 根据id获取Client信息
* @param clientId 应用id
* @return ClientModel
* 获取 ClientModel根据 clientId
*
* @param clientId /
* @return /
*/
public SaClientModel getClientModel(String clientId) {
return SaOAuth2Manager.getDataLoader().getClientModel(clientId);
}
// ------------------- 资源校验API
/**
* 根据id获取Client信息, 如果Client为空,则抛出异常
* @param clientId 应用id
* @return ClientModel
* 校验 clientId 信息并返回 ClientModel如果找不到对应 Client 信息则抛出异常
* @param clientId /
* @return /
*/
public SaClientModel checkClientModel(String clientId) {
SaClientModel clientModel = getClientModel(clientId);
if(clientModel == null) {
throw new SaOAuth2Exception("无效client_id: " + clientId).setCode(SaOAuth2ErrorCode.CODE_30105);
throw new SaOAuth2ClientModelException("无效 client_id: " + clientId)
.setClientId(clientId)
.setCode(SaOAuth2ErrorCode.CODE_30105);
}
return clientModel;
}
/**
* 获取 Access-Token如果AccessToken为空则抛出异常
* @param accessToken .
* @return .
* 校验clientId 与 clientSecret 是否正确
* @param clientId 应用id
* @param clientSecret 秘钥
* @return SaClientModel对象
*/
public AccessTokenModel checkAccessToken(String accessToken) {
AccessTokenModel at = SaOAuth2Manager.getDao().getAccessToken(accessToken);
SaOAuth2Exception.throwBy(at == null, "无效access_token" + accessToken, SaOAuth2ErrorCode.CODE_30106);
return at;
public SaClientModel checkClientSecret(String clientId, String clientSecret) {
SaClientModel cm = checkClientModel(clientId);
SaOAuth2ClientModelException.throwBy(cm.clientSecret == null || ! cm.clientSecret.equals(clientSecret), "无效client_secret: " + clientSecret,
clientId, SaOAuth2ErrorCode.CODE_30115);
return cm;
}
/**
* 获取 Client-Token如果ClientToken为空则抛出异常
* @param clientToken .
* @return .
* 校验clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes
* @param clientId 应用id
* @param clientSecret 秘钥
* @param scopes 权限
* @return SaClientModel对象
*/
public ClientTokenModel checkClientToken(String clientToken) {
ClientTokenModel ct = SaOAuth2Manager.getDao().getClientToken(clientToken);
SaOAuth2Exception.throwBy(ct == null, "无效client_token" + clientToken, SaOAuth2ErrorCode.CODE_30107);
return ct;
public SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List<String> scopes) {
SaClientModel cm = checkClientSecret(clientId, clientSecret);
checkContractScope(cm, scopes);
return cm;
}
/**
* 获取 Access-Token 所代表的LoginId
* @param accessToken Access-Token
* @return LoginId
* 判断:该 Client 是否签约了指定的 Scope返回 true 或 false
* @param clientId 应用id
* @param scopes 权限
* @return /
*/
public Object getLoginIdByAccessToken(String accessToken) {
return checkAccessToken(accessToken).loginId;
}
/**
* 校验:指定 Access-Token 是否具有指定 Scope
* @param accessToken Access-Token
* @param scopes 需要校验的权限列表
*/
public void checkScope(String accessToken, String... scopes) {
if(scopes == null || scopes.length == 0) {
return;
}
AccessTokenModel at = checkAccessToken(accessToken);
List<String> scopeList = at.scopes;
for (String scope : scopes) {
SaOAuth2Exception.throwBy( ! scopeList.contains(scope), "该 Access-Token 不具备 Scope" + scope, SaOAuth2ErrorCode.CODE_30108);
}
}
/**
* 校验:指定 Client-Token 是否具有指定 Scope
* @param clientToken Client-Token
* @param scopes 需要校验的权限列表
*/
public void checkClientTokenScope(String clientToken, String... scopes) {
if(scopes == null || scopes.length == 0) {
return;
}
ClientTokenModel ct = checkClientToken(clientToken);
List<String> scopeList = ct.scopes;
for (String scope : scopes) {
SaOAuth2Exception.throwBy( ! scopeList.contains(scope), "该 Client-Token 不具备 Scope" + scope, SaOAuth2ErrorCode.CODE_30109);
public boolean isContractScope(String clientId, List<String> scopes) {
try {
checkContractScope(clientId, scopes);
return true;
} catch (SaOAuth2ClientModelException e) {
return false;
}
}
/**
* 校验:该 Client 是否签约了指定的 Scope如果没有则抛出异常
* @param clientId 应用id
* @param scopes 权限列表
* @return /
*/
public SaClientModel checkContractScope(String clientId, List<String> scopes) {
return checkContractScope(checkClientModel(clientId), scopes);
}
// ------------------- check 数据校验
/**
* 校验:该 Client 是否签约了指定的 Scope如果没有则抛出异常
* @param cm 应用
* @param scopes 权限列表
* @return /
*/
public SaClientModel checkContractScope(SaClientModel cm, List<String> scopes) {
if(SaFoxUtil.isEmptyList(scopes)) {
return cm;
}
for (String scope : scopes) {
if(! cm.contractScopes.contains(scope)) {
throw new SaOAuth2ClientModelScopeException("该 client 暂未签约 scope: " + scope)
.setClientId(cm.clientId)
.setScope(scope)
.setCode(SaOAuth2ErrorCode.CODE_30112);
}
}
return cm;
}
// --------- redirect_uri 相关
/**
* 校验:该 Client 使用指定 url 作为回调地址,是否合法
* @param clientId 应用id
* @param url 指定url
*/
public void checkRedirectUri(String clientId, String url) {
// 1、是否是一个有效的url
if( ! SaFoxUtil.isUrl(url)) {
throw new SaOAuth2ClientModelException("无效redirect_url" + url)
.setClientId(clientId)
.setCode(SaOAuth2ErrorCode.CODE_30113);
}
// 2、截取掉?后面的部分
int qIndex = url.indexOf("?");
if(qIndex != -1) {
url = url.substring(0, qIndex);
}
// 3、不允许出现@字符
if(url.contains("@")) {
// 为什么不允许出现 @ 字符呢,因为这有可能导致 redirect_url 参数绕过 AllowUrl 列表的校验
//
// 举个例子 SaClientModel 配置:
// allow-url=http://sa-oauth-client.com*
//
// 开发者原意是为了允许 sa-oauth-client.com 下的所有地址都可以下放 code
//
// 但是如果攻击者精心构建一个url
// http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com@sa-token.cc
//
// 那么这个url就会绕过 allow-url 的校验code 被下发到了第三方服务器地址:
// http://sa-token.cc/?code=i8vDfbpqBViMe01QoLY1kHROJWYvv9plBtvTZ6kk77KK0e0U4Xj99NPfSZEYjRul
//
// 造成了 code 参数劫持
// 所以此处需要禁止在 url 中出现 @ 字符
//
// 这么一刀切的做法可能会导致一些特殊的正常url也无法通过校验例如
// http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com/@getInfo
//
// 但是为了安全起见,这么做还是有必要的
throw new SaOAuth2ClientModelException("无效 redirect_url不允许出现@字符):" + url)
.setClientId(clientId);
}
// 4、是否在[允许地址列表]之中
SaClientModel clientModel = checkClientModel(clientId);
checkRedirectUriListNormal(clientModel.allowRedirectUris);
if( ! SaStrategy.instance.hasElement.apply(clientModel.allowRedirectUris, url)) {
throw new SaOAuth2ClientModelException("非法 redirect_url: " + url)
.setClientId(clientId)
.setCode(SaOAuth2ErrorCode.CODE_30114);
}
}
/**
* 校验配置的 allowRedirectUris 是否合规,如果不合规则抛出异常
* @param redirectUriList 待校验的 allow-url 地址列表
*/
public void checkRedirectUriListNormal(List<String> redirectUriList){
checkRedirectUriListNormalStaticMethod(redirectUriList);
}
/**
* 校验配置的 allowRedirectUris 是否合规,如果不合规则抛出异常,静态方法内部实现
* @param redirectUriList 待校验的 allow-url 地址列表
*/
public static void checkRedirectUriListNormalStaticMethod(List<String> redirectUriList){
for (String url : redirectUriList) {
int index = url.indexOf("*");
// 如果配置了 * 字符,则必须出现在最后一位,否则属于无效配置项
if(index != -1 && index != url.length() - 1) {
// 为什么不允许 * 字符出现在中间位置呢,因为这有可能导致 redirect 参数绕过 allow-url 列表的校验
//
// 举个例子 SaClientModel 配置:
// allow-url=http://*.sa-oauth-client.com/
//
// 开发者原意是为了允许 sa-oauth-client.com 下的所有子域名都可以下放 ticket
// 例如http://shop.sa-oauth-client.com/
//
// 但是如果攻击者精心构建一个url
// http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-token.cc/a.sa-oauth-client.com/
//
// 那么这个 url 就会绕过 allow-url 的校验ticket 被下发到了第三方服务器地址:
// http://sa-token.cc/a.sa-oauth-client.com/?code=v2KKMUFK7dDsMMzXLQ3aWGsyGUjrA0dBB2jeOWrpCnC8b5ScmXXQSv20mIwPK7Cx
//
// 造成了 ticket 参数劫持
// 所以此处需要禁止 allow-url 配置项的中间位置出现 * 字符(出现在末尾是没有问题的)
//
// 这么一刀切的做法可能会导致正常场景下的子域名url也无法通过校验例如
// http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://shop.sa-oauth2-client.com/
//
// 但是为了安全起见,这么做还是有必要的
throw new SaOAuth2Exception("无效的 allow-url 配置(*通配符只允许出现在最后一位):" + url);
}
}
}
// --------- 授权相关
/**
* 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope
@@ -131,10 +245,9 @@ public class SaOAuth2Template {
* @param scopes 权限
* @return 是否已经授权
*/
public boolean isGrant(Object loginId, String clientId, List<String> scopes) {
SaOAuth2Dao dao = SaOAuth2Manager.getDao();
List<String> grantScopeList = dao.getGrantScope(clientId, loginId);
return scopes.isEmpty() || new HashSet<>(grantScopeList).containsAll(scopes);
public boolean isGrantScope(Object loginId, String clientId, List<String> scopes) {
List<String> grantScopeList = SaOAuth2Manager.getDao().getGrantScope(clientId, loginId);
return SaFoxUtil.list1ContainList2AllElement(grantScopeList, scopes);
}
/**
@@ -166,143 +279,12 @@ public class SaOAuth2Template {
}
// 根据近期授权记录,判断是否需要确认
return !isGrant(loginId, clientId, scopes);
return !isGrantScope(loginId, clientId, scopes);
}
/**
* 校验该Client是否签约了指定的Scope
* @param clientId 应用id
* @param scopes 权限(多个用逗号隔开)
*/
public void checkContract(String clientId, List<String> scopes) {
List<String> clientScopeList = checkClientModel(clientId).contractScopes;
if( ! new HashSet<>(clientScopeList).containsAll(scopes)) {
throw new SaOAuth2Exception("请求的Scope暂未签约").setCode(SaOAuth2ErrorCode.CODE_30112);
}
}
/**
* 校验该Client使用指定url作为回调地址是否合法
* @param clientId 应用id
* @param url 指定url
*/
public void checkRightUrl(String clientId, String url) {
// 1、是否是一个有效的url
if( ! SaFoxUtil.isUrl(url)) {
throw new SaOAuth2Exception("无效redirect_url" + url).setCode(SaOAuth2ErrorCode.CODE_30113);
}
// 2、截取掉?后面的部分
int qIndex = url.indexOf("?");
if(qIndex != -1) {
url = url.substring(0, qIndex);
}
// --------- 请求数据校验相关
// 3、不允许出现@字符
if(url.contains("@")) {
// 为什么不允许出现 @ 字符呢,因为这有可能导致 redirect_url 参数绕过 AllowUrl 列表的校验
//
// 举个例子 SaClientModel 配置:
// allow-url=http://sa-oauth-client.com*
//
// 开发者原意是为了允许 sa-oauth-client.com 下的所有地址都可以下放 code
//
// 但是如果攻击者精心构建一个url
// http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com@sa-token.cc
//
// 那么这个url就会绕过 allow-url 的校验code 被下发到了第三方服务器地址:
// http://sa-token.cc/?code=i8vDfbpqBViMe01QoLY1kHROJWYvv9plBtvTZ6kk77KK0e0U4Xj99NPfSZEYjRul
//
// 造成了 code 参数劫持
// 所以此处需要禁止在 url 中出现 @ 字符
//
// 这么一刀切的做法可能会导致一些特殊的正常url也无法通过校验例如
// http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com/@getInfo
//
// 但是为了安全起见,这么做还是有必要的
throw new SaOAuth2Exception("无效 redirect_url不允许出现@字符):" + url);
}
// 4、是否在[允许地址列表]之中
SaClientModel clientModel = checkClientModel(clientId);
checkAllowUrlList(clientModel.allowRedirectUris);
if( ! SaStrategy.instance.hasElement.apply(clientModel.allowRedirectUris, url)) {
throw new SaOAuth2Exception("非法 redirect_url: " + url).setCode(SaOAuth2ErrorCode.CODE_30114);
}
}
/**
* 校验配置的 AllowUrl 是否合规,如果不合规则抛出异常
* @param allowUrlList 待校验的 allow-url 地址列表
*/
public void checkAllowUrlList(List<String> allowUrlList){
checkAllowUrlListStaticMethod(allowUrlList);
}
/**
* 校验配置的 AllowUrl 是否合规,如果不合规则抛出异常
* @param allowUrlList 待校验的 allow-url 地址列表
*/
public static void checkAllowUrlListStaticMethod(List<String> allowUrlList){
for (String url : allowUrlList) {
int index = url.indexOf("*");
// 如果配置了 * 字符,则必须出现在最后一位,否则属于无效配置项
if(index != -1 && index != url.length() - 1) {
// 为什么不允许 * 字符出现在中间位置呢,因为这有可能导致 redirect 参数绕过 allow-url 列表的校验
//
// 举个例子 SaClientModel 配置:
// allow-url=http://*.sa-oauth-client.com/
//
// 开发者原意是为了允许 sa-oauth-client.com 下的所有子域名都可以下放 ticket
// 例如http://shop.sa-oauth-client.com/
//
// 但是如果攻击者精心构建一个url
// http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-token.cc/a.sa-oauth-client.com/
//
// 那么这个 url 就会绕过 allow-url 的校验ticket 被下发到了第三方服务器地址:
// http://sa-token.cc/a.sa-oauth-client.com/?code=v2KKMUFK7dDsMMzXLQ3aWGsyGUjrA0dBB2jeOWrpCnC8b5ScmXXQSv20mIwPK7Cx
//
// 造成了 ticket 参数劫持
// 所以此处需要禁止 allow-url 配置项的中间位置出现 * 字符(出现在末尾是没有问题的)
//
// 这么一刀切的做法可能会导致正常场景下的子域名url也无法通过校验例如
// http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://shop.sa-oauth2-client.com/
//
// 但是为了安全起见,这么做还是有必要的
throw new SaOAuth2Exception("无效的 allow-url 配置(*通配符只允许出现在最后一位):" + url);
}
}
}
/**
* 校验clientId 与 clientSecret 是否正确
* @param clientId 应用id
* @param clientSecret 秘钥
* @return SaClientModel对象
*/
public SaClientModel checkClientSecret(String clientId, String clientSecret) {
SaClientModel cm = checkClientModel(clientId);
SaOAuth2Exception.throwBy(cm.clientSecret == null || ! cm.clientSecret.equals(clientSecret),
"无效client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30115);
return cm;
}
/**
* 校验clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes
* @param clientId 应用id
* @param clientSecret 秘钥
* @param scopes 权限
* @return SaClientModel对象
*/
public SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List<String> scopes) {
// 先校验 clientSecret
SaClientModel cm = checkClientSecret(clientId, clientSecret);
// 再校验 是否签约
List<String> clientScopeList = cm.contractScopes;
if( ! new HashSet<>(clientScopeList).containsAll(scopes)) {
throw new SaOAuth2Exception("请求的Scope暂未签约").setCode(SaOAuth2ErrorCode.CODE_30116);
}
// 返回数据
return cm;
}
/**
* 校验:使用 code 获取 token 时提供的参数校验
* @param code 授权码
@@ -317,23 +299,24 @@ public class SaOAuth2Template {
// 校验Code是否存在
CodeModel cm = dao.getCode(code);
SaOAuth2Exception.throwBy(cm == null, "无效code: " + code, SaOAuth2ErrorCode.CODE_30117);
SaOAuth2Exception.throwBy(cm == null, "无效 code: " + code, SaOAuth2ErrorCode.CODE_30117);
// 校验ClientId是否一致
SaOAuth2Exception.throwBy( ! cm.clientId.equals(clientId), "无效client_id: " + clientId, SaOAuth2ErrorCode.CODE_30118);
SaOAuth2Exception.throwBy( ! cm.clientId.equals(clientId), "无效 client_id: " + clientId, SaOAuth2ErrorCode.CODE_30118);
// 校验Secret是否正确
String dbSecret = checkClientModel(clientId).clientSecret;
SaOAuth2Exception.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30119);
SaOAuth2Exception.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效 client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30119);
// 如果提供了redirectUri则校验其是否与请求Code时提供的一致
if( ! SaFoxUtil.isEmpty(redirectUri)) {
SaOAuth2Exception.throwBy( ! redirectUri.equals(cm.redirectUri), "无效redirect_uri: " + redirectUri, SaOAuth2ErrorCode.CODE_30120);
SaOAuth2Exception.throwBy( ! redirectUri.equals(cm.redirectUri), "无效 redirect_uri: " + redirectUri, SaOAuth2ErrorCode.CODE_30120);
}
// 返回CodeModel
return cm;
}
/**
* 校验:使用 Refresh-Token 刷新 Access-Token 时提供的参数校验
* @param clientId 应用id
@@ -347,18 +330,20 @@ public class SaOAuth2Template {
// 校验Refresh-Token是否存在
RefreshTokenModel rt = dao.getRefreshToken(refreshToken);
SaOAuth2Exception.throwBy(rt == null, "无效refresh_token: " + refreshToken, SaOAuth2ErrorCode.CODE_30121);
SaOAuth2RefreshTokenException.throwBy(rt == null, "无效 refresh_token: " + refreshToken, refreshToken, SaOAuth2ErrorCode.CODE_30121);
// 校验ClientId是否一致
SaOAuth2Exception.throwBy( ! rt.clientId.equals(clientId), "无效client_id: " + clientId, SaOAuth2ErrorCode.CODE_30122);
SaOAuth2ClientModelException.throwBy( ! rt.clientId.equals(clientId), "无效 client_id: " + clientId, clientId, SaOAuth2ErrorCode.CODE_30122);
// 校验Secret是否正确
String dbSecret = checkClientModel(clientId).clientSecret;
SaOAuth2Exception.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30123);
SaOAuth2ClientModelException.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret,
clientId, SaOAuth2ErrorCode.CODE_30123);
// 返回Refresh-Token
// 返回 Refresh-Token
return rt;
}
/**
* 校验Access-Token、clientId、clientSecret 三者是否匹配成功
* @param clientId 应用id
@@ -368,30 +353,271 @@ public class SaOAuth2Template {
*/
public AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) {
AccessTokenModel at = checkAccessToken(accessToken);
SaOAuth2Exception.throwBy( ! at.clientId.equals(clientId), "无效client_id" + clientId, SaOAuth2ErrorCode.CODE_30124);
SaOAuth2ClientModelException.throwBy( ! at.clientId.equals(clientId), "无效 client_id" + clientId, clientId, SaOAuth2ErrorCode.CODE_30124);
checkClientSecret(clientId, clientSecret);
return at;
}
// ----------------- Access-Token 相关 -----------------
/**
* 回收指定的 ClientToken
* 获取 AccessTokenModel无效的 AccessToken 会返回 null
* @param accessToken /
* @return /
*/
public AccessTokenModel getAccessToken(String accessToken) {
return SaOAuth2Manager.getDao().getAccessToken(accessToken);
}
/**
* 校验 Access-Token成功返回 AccessTokenModel失败则抛出异常
* @param accessToken /
* @return /
*/
public AccessTokenModel checkAccessToken(String accessToken) {
AccessTokenModel at = SaOAuth2Manager.getDao().getAccessToken(accessToken);
if(at == null) {
throw new SaOAuth2AccessTokenException("无效 access_token: " + accessToken)
.setAccessToken(accessToken)
.setCode(SaOAuth2ErrorCode.CODE_30106);
}
return at;
}
/**
* 获取 Access-Token根据索引 clientId、loginId
* @param clientId /
* @param loginId /
* @return /
*/
public String getAccessTokenValue(String clientId, Object loginId) {
return SaOAuth2Manager.getDao().getAccessTokenValue(clientId, loginId);
}
/**
* 判断:指定 Access-Token 是否具有指定 Scope 列表,返回 true 或 false
* @param accessToken Access-Token
* @param scopes 需要校验的权限列表
*/
public boolean hasAccessTokenScope(String accessToken, String... scopes) {
try {
checkAccessTokenScope(accessToken, scopes);
return true;
} catch (SaOAuth2AccessTokenException e) {
return false;
}
}
/**
* 校验:指定 Access-Token 是否具有指定 Scope 列表,如果不具备则抛出异常
* @param accessToken Access-Token
* @param scopes 需要校验的权限列表
*/
public void checkAccessTokenScope(String accessToken, String... scopes) {
if(SaFoxUtil.isEmptyArray(scopes)) {
return;
}
ClientTokenModel ct = checkClientToken(accessToken);
for (String scope : scopes) {
if(! ct.scopes.contains(scope)) {
throw new SaOAuth2AccessTokenScopeException("该 access_token 不具备 scope" + scope)
.setAccessToken(accessToken)
.setScope(scope)
.setCode(SaOAuth2ErrorCode.CODE_30108);
}
}
}
/**
* 获取 Access-Token 所代表的LoginId
* @param accessToken Access-Token
* @return LoginId
*/
public Object getLoginIdByAccessToken(String accessToken) {
return checkAccessToken(accessToken).loginId;
}
/**
* 获取 Access-Token 所代表的 clientId
* @param accessToken Access-Token
* @return LoginId
*/
public Object getClientIdByAccessToken(String accessToken) {
return checkAccessToken(accessToken).clientId;
}
/**
* 回收 Access-Token
* @param accessToken Access-Token值
*/
public void revokeAccessToken(String accessToken) {
AccessTokenModel at = getAccessToken(accessToken);
if(at == null) {
return;
}
// 删 at、索引
SaOAuth2Dao dao = SaOAuth2Manager.getDao();
dao.deleteAccessToken(accessToken);
dao.deleteAccessTokenIndex(at.clientId, at.loginId);
// 删对应的 rt、索引
// String rtValue = dao.getRefreshTokenValue(at.clientId, at.loginId);
// dao.deleteRefreshToken(rtValue);
// dao.deleteRefreshTokenIndex(at.clientId, at.loginId);
}
/**
* 回收 Access-Token根据索引 clientId、loginId
* @param clientId /
* @param loginId /
*/
public void revokeAccessTokenByIndex(String clientId, Object loginId) {
SaOAuth2Dao dao = SaOAuth2Manager.getDao();
// 删 at、删索引
String accessToken = getAccessTokenValue(clientId, loginId);
if(accessToken != null) {
dao.deleteAccessToken(accessToken);
dao.deleteAccessTokenIndex(clientId, loginId);
}
}
// ----------------- Refresh-Token 相关 -----------------
/**
* 获取 RefreshTokenModel无效的 RefreshToken 会返回 null
* @param refreshToken /
* @return /
*/
public RefreshTokenModel getRefreshToken(String refreshToken) {
return SaOAuth2Manager.getDao().getRefreshToken(refreshToken);
}
/**
* 校验 Refresh-Token成功返回 RefreshTokenModel失败则抛出异常
* @param refreshToken /
* @return /
*/
public RefreshTokenModel checkRefreshToken(String refreshToken) {
RefreshTokenModel rt = SaOAuth2Manager.getDao().getRefreshToken(refreshToken);
if(rt == null) {
throw new SaOAuth2RefreshTokenException("无效 refresh_token: " + refreshToken)
.setRefreshToken(refreshToken)
.setCode(SaOAuth2ErrorCode.CODE_30111);
}
return rt;
}
/**
* 获取 Refresh-Token根据索引 clientId、loginId
* @param clientId /
* @param loginId /
* @return /
*/
public String getRefreshTokenValue(String clientId, Object loginId) {
return SaOAuth2Manager.getDao().getRefreshTokenValue(clientId, loginId);
}
/**
* 根据 RefreshToken 刷新出一个 AccessToken
* @param refreshToken /
* @return /
*/
public AccessTokenModel refreshAccessToken(String refreshToken) {
return SaOAuth2Manager.getDataGenerate().refreshAccessToken(refreshToken);
}
// ----------------- Client-Token 相关 -----------------
/**
* 获取 ClientTokenModel无效的 ClientToken 会返回 null
* @param clientToken /
* @return /
*/
public ClientTokenModel getClientToken(String clientToken) {
return SaOAuth2Manager.getDao().getClientToken(clientToken);
}
/**
* 校验 Client-Token成功返回 ClientTokenModel失败则抛出异常
* @param clientToken /
* @return /
*/
public ClientTokenModel checkClientToken(String clientToken) {
ClientTokenModel ct = getClientToken(clientToken);
if(ct == null) {
throw new SaOAuth2ClientTokenException("无效 client_token: " + clientToken)
.setClientToken(clientToken)
.setCode(SaOAuth2ErrorCode.CODE_30107);
}
return ct;
}
/**
* 获取 ClientToken根据索引 clientId
* @param clientId /
* @return /
*/
public String getClientTokenValue(String clientId) {
return SaOAuth2Manager.getDao().getClientTokenValue(clientId);
}
/**
* 判断:指定 Client-Token 是否具有指定 Scope 列表,返回 true 或 false
* @param clientToken Client-Token
* @param scopes 需要校验的权限列表
*/
public boolean hasClientTokenScope(String clientToken, String... scopes) {
try {
checkClientTokenScope(clientToken, scopes);
return true;
} catch (SaOAuth2ClientTokenException e) {
return false;
}
}
/**
* 校验:指定 Client-Token 是否具有指定 Scope 列表,如果不具备则抛出异常
* @param clientToken Client-Token
* @param scopes 需要校验的权限列表
*/
public void checkClientTokenScope(String clientToken, String... scopes) {
if(SaFoxUtil.isEmptyArray(scopes)) {
return;
}
ClientTokenModel ct = checkClientToken(clientToken);
for (String scope : scopes) {
if(! ct.scopes.contains(scope)) {
throw new SaOAuth2ClientTokenScopeException("该 client_token 不具备 scope" + scope)
.setClientToken(clientToken)
.setScope(scope)
.setCode(SaOAuth2ErrorCode.CODE_30109);
}
}
}
/**
* 回收 ClientToken
*
* @param clientToken /
*/
public void revokeClientToken(String clientToken) {
SaOAuth2Dao dao = SaOAuth2Manager.getDao();
ClientTokenModel ctModel = dao.getClientToken(clientToken);
if(ctModel == null) {
ClientTokenModel ct = getClientToken(clientToken);
if(ct == null) {
return;
}
// 删 ct、索引
// 删 ct、索引
SaOAuth2Dao dao = SaOAuth2Manager.getDao();
dao.deleteClientToken(clientToken);
dao.deleteClientTokenIndex(ctModel.clientId);
dao.deleteClientTokenIndex(ct.clientId);
}
/**
* 回收指定的 ClientToken根据索引 clientId
* 回收 ClientToken根据索引 clientId
*
* @param clientId /
*/
@@ -399,33 +625,30 @@ public class SaOAuth2Template {
SaOAuth2Dao dao = SaOAuth2Manager.getDao();
// 删 clientToken
String clientToken = dao.getClientTokenValue(clientId);
String clientToken = getClientTokenValue(clientId);
if(clientToken != null) {
dao.deleteClientToken(clientToken);
dao.deleteClientTokenIndex(clientId);
}
}
/**
* 回收 PastToken根据索引 clientId
*
* @param clientId /
*/
public void revokePastTokenByIndex(String clientId) {
SaOAuth2Dao dao = SaOAuth2Manager.getDao();
// 删 pastToken
String pastToken = dao.getPastTokenValue(clientId);
if(pastToken != null) {
dao.deletePastToken(pastToken);
dao.deletePastTokenIndex(clientId);
}
}
// ------------------- 包装其它 bean 的方法
/**
* 获取Access-Token Model
* @param accessToken /
* @return /
*/
public AccessTokenModel getAccessToken(String accessToken) {
return SaOAuth2Manager.getDao().getAccessToken(accessToken);
}
// ----------------- 包装其它 bean 的方法 -----------------
/**
* 持久化:用户授权记录
@@ -455,8 +678,4 @@ public class SaOAuth2Template {
return SaOAuth2Manager.getDataConverter().convertScopeStringToList(lowerScope);
}
}

View File

@@ -18,120 +18,52 @@ package cn.dev33.satoken.oauth2.template;
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.CodeModel;
import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel;
import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel;
import java.util.List;
/**
* Sa-Token-OAuth2 模块 工具类
* Sa-Token OAuth2 模块 工具类
*
* @author click33
* @since 1.23.0
*/
public class SaOAuth2Util {
// ------------------- 资源校验API
// ----------------- ClientModel 相关 -----------------
/**
* 根据id获取Client信息, 如果Client为空则抛出异常
* @param clientId 应用id
* @return ClientModel
* 获取 ClientModel根据 clientId
*
* @param clientId /
* @return /
*/
public static SaClientModel getClientModel(String clientId) {
return SaOAuth2Manager.getTemplate().getClientModel(clientId);
}
/**
* 校验 clientId 信息并返回 ClientModel如果找不到对应 Client 信息则抛出异常
* @param clientId /
* @return /
*/
public static SaClientModel checkClientModel(String clientId) {
return SaOAuth2Manager.getTemplate().checkClientModel(clientId);
}
/**
* 获取 Access-Token如果AccessToken为空则抛出异常
* @param accessToken .
* @return .
*/
public static AccessTokenModel checkAccessToken(String accessToken) {
return SaOAuth2Manager.getTemplate().checkAccessToken(accessToken);
}
/**
* 获取 Client-Token如果ClientToken为空则抛出异常
* @param clientToken .
* @return .
*/
public static ClientTokenModel checkClientToken(String clientToken) {
return SaOAuth2Manager.getTemplate().checkClientToken(clientToken);
}
/**
* 获取 Access-Token 所代表的LoginId
* @param accessToken Access-Token
* @return LoginId
*/
public static Object getLoginIdByAccessToken(String accessToken) {
return SaOAuth2Manager.getTemplate().getLoginIdByAccessToken(accessToken);
}
/**
* 校验:指定 Access-Token 是否具有指定 Scope
* @param accessToken Access-Token
* @param scopes 需要校验的权限列表
*/
public static void checkScope(String accessToken, String... scopes) {
SaOAuth2Manager.getTemplate().checkScope(accessToken, scopes);
}
/**
* 校验:指定 Client-Token 是否具有指定 Scope
* @param clientToken Client-Token
* @param scopes 需要校验的权限列表
*/
public static void checkClientTokenScope(String clientToken, String... scopes) {
SaOAuth2Manager.getTemplate().checkClientTokenScope(clientToken, scopes);
}
// ------------------- 数据校验
/**
* 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope
* @param loginId 账号id
* @param clientId 应用id
* @param scopes 权限
* @return 是否已经授权
*/
public static boolean isGrant(Object loginId, String clientId, List<String> scopes) {
return SaOAuth2Manager.getTemplate().isGrant(loginId, clientId, scopes);
}
/**
* 校验该Client是否签约了指定的Scope
* @param clientId 应用id
* @param scopes 权限(多个用逗号隔开)
*/
public static void checkContract(String clientId, List<String> scopes) {
SaOAuth2Manager.getTemplate().checkContract(clientId, scopes);
}
/**
* 校验该Client使用指定url作为回调地址是否合法
* @param clientId 应用id
* @param url 指定url
*/
public static void checkRightUrl(String clientId, String url) {
SaOAuth2Manager.getTemplate().checkRightUrl(clientId, url);
}
/**
* 校验clientId 与 clientSecret 是否正确
* @param clientId 应用id
* @param clientSecret 秘钥
* @return SaClientModel对象
* @param clientId 应用id
* @param clientSecret 秘钥
* @return SaClientModel对象
*/
public static SaClientModel checkClientSecret(String clientId, String clientSecret) {
return SaOAuth2Manager.getTemplate().checkClientSecret(clientId, clientSecret);
}
/**
* 校验clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes
* 校验clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes
* @param clientId 应用id
* @param clientSecret 秘钥
* @param scopes 权限
@@ -140,43 +72,235 @@ public class SaOAuth2Util {
public static SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List<String> scopes) {
return SaOAuth2Manager.getTemplate().checkClientSecretAndScope(clientId, clientSecret, scopes);
}
/**
* 校验:使用 code 获取 token 时提供的参数校验
* @param code 授权码
* @param clientId 应用id
* @param clientSecret 秘钥
* @param redirectUri 重定向地址
* @return CodeModel对象
* 判断:该 Client 是否签约了指定的 Scope返回 true 或 false
* @param clientId 应用id
* @param scopes 权限
* @return /
*/
public static CodeModel checkGainTokenParam(String code, String clientId, String clientSecret, String redirectUri) {
return SaOAuth2Manager.getTemplate().checkGainTokenParam(code, clientId, clientSecret, redirectUri);
public static boolean isContractScope(String clientId, List<String> scopes) {
return SaOAuth2Manager.getTemplate().isContractScope(clientId, scopes);
}
/**
* 校验:使用 Refresh-Token 刷新 Access-Token 时提供的参数校验
* @param clientId 应用id
* @param clientSecret 秘钥
* @param refreshToken Refresh-Token
* @return CodeModel对象
* 校验:该 Client 是否签约了指定的 Scope如果没有则抛出异常
* @param clientId 应用id
* @param scopes 权限列表
* @return /
*/
public static RefreshTokenModel checkRefreshTokenParam(String clientId, String clientSecret, String refreshToken) {
return SaOAuth2Manager.getTemplate().checkRefreshTokenParam(clientId, clientSecret, refreshToken);
}
/**
* 校验Access-Token、clientId、clientSecret 三者是否匹配成功
* @param clientId 应用id
* @param clientSecret 秘钥
* @param accessToken Access-Token
* @return SaClientModel对象
*/
public static AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) {
return SaOAuth2Manager.getTemplate().checkAccessTokenParam(clientId, clientSecret, accessToken);
public static SaClientModel checkContractScope(String clientId, List<String> scopes) {
return SaOAuth2Manager.getTemplate().checkContractScope(clientId, scopes);
}
/**
* 回收指定的 ClientToken
* 校验:该 Client 是否签约了指定的 Scope如果没有则抛出异常
* @param cm 应用
* @param scopes 权限列表
* @return /
*/
public static SaClientModel checkContractScope(SaClientModel cm, List<String> scopes) {
return SaOAuth2Manager.getTemplate().checkContractScope(cm, scopes);
}
// --------- redirect_uri 相关
/**
* 校验:该 Client 使用指定 url 作为回调地址,是否合法
* @param clientId 应用id
* @param url 指定url
*/
public static void checkRedirectUri(String clientId, String url) {
SaOAuth2Manager.getTemplate().checkRedirectUri(clientId, url);
}
// --------- 授权相关
/**
* 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope
* @param loginId 账号id
* @param clientId 应用id
* @param scopes 权限
* @return 是否已经授权
*/
public static boolean isGrantScope(Object loginId, String clientId, List<String> scopes) {
return SaOAuth2Manager.getTemplate().isGrantScope(loginId, clientId, scopes);
}
// ----------------- Access-Token 相关 -----------------
/**
* 获取 AccessTokenModel无效的 AccessToken 会返回 null
* @param accessToken /
* @return /
*/
public static AccessTokenModel getAccessToken(String accessToken) {
return SaOAuth2Manager.getTemplate().getAccessToken(accessToken);
}
/**
* 校验 Access-Token成功返回 AccessTokenModel失败则抛出异常
* @param accessToken /
* @return /
*/
public static AccessTokenModel checkAccessToken(String accessToken) {
return SaOAuth2Manager.getTemplate().checkAccessToken(accessToken);
}
/**
* 获取 Access-Token根据索引 clientId、loginId
* @param clientId /
* @param loginId /
* @return /
*/
public static String getAccessTokenValue(String clientId, Object loginId) {
return SaOAuth2Manager.getTemplate().getAccessTokenValue(clientId, loginId);
}
/**
* 判断:指定 Access-Token 是否具有指定 Scope 列表,返回 true 或 false
* @param accessToken Access-Token
* @param scopes 需要校验的权限列表
*/
public static boolean hasAccessTokenScope(String accessToken, String... scopes) {
return SaOAuth2Manager.getTemplate().hasAccessTokenScope(accessToken, scopes);
}
/**
* 校验:指定 Access-Token 是否具有指定 Scope 列表,如果不具备则抛出异常
* @param accessToken Access-Token
* @param scopes 需要校验的权限列表
*/
public static void checkAccessTokenScope(String accessToken, String... scopes) {
SaOAuth2Manager.getTemplate().checkAccessTokenScope(accessToken, scopes);
}
/**
* 获取 Access-Token 所代表的LoginId
* @param accessToken Access-Token
* @return LoginId
*/
public static Object getLoginIdByAccessToken(String accessToken) {
return SaOAuth2Manager.getTemplate().getLoginIdByAccessToken(accessToken);
}
/**
* 获取 Access-Token 所代表的 clientId
* @param accessToken Access-Token
* @return LoginId
*/
public static Object getClientIdByAccessToken(String accessToken) {
return SaOAuth2Manager.getTemplate().getClientIdByAccessToken(accessToken);
}
/**
* 回收 Access-Token
* @param accessToken Access-Token值
*/
public static void revokeAccessToken(String accessToken) {
SaOAuth2Manager.getTemplate().revokeAccessToken(accessToken);
}
/**
* 回收 Access-Token根据索引 clientId、loginId
* @param clientId /
* @param loginId /
*/
public static void revokeAccessTokenByIndex(String clientId, Object loginId) {
SaOAuth2Manager.getTemplate().revokeAccessTokenByIndex(clientId, loginId);
}
// ----------------- Refresh-Token 相关 -----------------
/**
* 获取 RefreshTokenModel无效的 RefreshToken 会返回 null
* @param refreshToken /
* @return /
*/
public static RefreshTokenModel getRefreshToken(String refreshToken) {
return SaOAuth2Manager.getTemplate().getRefreshToken(refreshToken);
}
/**
* 校验 Refresh-Token成功返回 RefreshTokenModel失败则抛出异常
* @param refreshToken /
* @return /
*/
public static RefreshTokenModel checkRefreshToken(String refreshToken) {
return SaOAuth2Manager.getTemplate().checkRefreshToken(refreshToken);
}
/**
* 获取 Refresh-Token根据索引 clientId、loginId
* @param clientId /
* @param loginId /
* @return /
*/
public static String getRefreshTokenValue(String clientId, Object loginId) {
return SaOAuth2Manager.getTemplate().getRefreshTokenValue(clientId, loginId);
}
/**
* 根据 RefreshToken 刷新出一个 AccessToken
* @param refreshToken /
* @return /
*/
public static AccessTokenModel refreshAccessToken(String refreshToken) {
return SaOAuth2Manager.getTemplate().refreshAccessToken(refreshToken);
}
// ----------------- Client-Token 相关 -----------------
/**
* 获取 ClientTokenModel无效的 ClientToken 会返回 null
* @param clientToken /
* @return /
*/
public static ClientTokenModel getClientToken(String clientToken) {
return SaOAuth2Manager.getTemplate().getClientToken(clientToken);
}
/**
* 校验 Client-Token成功返回 ClientTokenModel失败则抛出异常
* @param clientToken /
* @return /
*/
public static ClientTokenModel checkClientToken(String clientToken) {
return SaOAuth2Manager.getTemplate().checkClientToken(clientToken);
}
/**
* 获取 ClientToken根据索引 clientId
* @param clientId /
* @return /
*/
public static String getClientTokenValue(String clientId) {
return SaOAuth2Manager.getTemplate().getClientTokenValue(clientId);
}
/**
* 判断:指定 Client-Token 是否具有指定 Scope 列表,返回 true 或 false
* @param clientToken Client-Token
* @param scopes 需要校验的权限列表
*/
public static boolean hasClientTokenScope(String clientToken, String... scopes) {
return SaOAuth2Manager.getTemplate().hasClientTokenScope(clientToken, scopes);
}
/**
* 校验:指定 Client-Token 是否具有指定 Scope 列表,如果不具备则抛出异常
* @param clientToken Client-Token
* @param scopes 需要校验的权限列表
*/
public static void checkClientTokenScope(String clientToken, String... scopes) {
SaOAuth2Manager.getTemplate().checkClientTokenScope(clientToken, scopes);
}
/**
* 回收 ClientToken
*
* @param clientToken /
*/
@@ -185,7 +309,7 @@ public class SaOAuth2Util {
}
/**
* 回收指定的 ClientToken根据索引clientId
* 回收 ClientToken根据索引 clientId
*
* @param clientId /
*/
@@ -193,65 +317,13 @@ public class SaOAuth2Util {
SaOAuth2Manager.getTemplate().revokeClientTokenByIndex(clientId);
}
// ------------------- save 数据
/**
* 持久化:用户授权记录
* @param clientId 应用id
* @param loginId 账号id
* @param scopes 权限列表
* 回收 PastToken根据索引 clientId
*
* @param clientId /
*/
public static void saveGrantScope(String clientId, Object loginId, List<String> scopes) {
SaOAuth2Manager.getTemplate().saveGrantScope(clientId, loginId, scopes);
}
// ------------------- get 数据
/**
* 获取Code Model
* @param code .
* @return .
*/
public static CodeModel getCode(String code) {
return SaOAuth2Manager.getDao().getCode(code);
}
/**
* 获取Access-Token Model
* @param accessToken .
* @return .
*/
public static AccessTokenModel getAccessToken(String accessToken) {
return SaOAuth2Manager.getDao().getAccessToken(accessToken);
}
/**
* 获取Refresh-Token Model
* @param refreshToken .
* @return .
*/
public static RefreshTokenModel getRefreshToken(String refreshToken) {
return SaOAuth2Manager.getDao().getRefreshToken(refreshToken);
}
/**
* 获取Client-Token Model
* @param clientToken .
* @return .
*/
public static ClientTokenModel getClientToken(String clientToken) {
return SaOAuth2Manager.getDao().getClientToken(clientToken);
}
/**
* 获取:用户授权记录
* @param clientId 应用id
* @param loginId 账号id
* @return 权限
*/
public static List<String> getGrantScope(String clientId, Object loginId) {
return SaOAuth2Manager.getDao().getGrantScope(clientId, loginId);
public static void revokePastTokenByIndex(String clientId) {
SaOAuth2Manager.getTemplate().revokePastTokenByIndex(clientId);
}
}