将 scope 字段改为 List 类型

This commit is contained in:
click33 2024-08-14 18:11:20 +08:00
parent 47cf8939cb
commit 15e2d9f668
20 changed files with 544 additions and 237 deletions

View File

@ -29,7 +29,6 @@ import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
/**
* Sa-Token 内部工具类
@ -563,7 +562,7 @@ public class SaFoxUtil {
* @return 字符串
*/
public static String convertListToString(List<?> list) {
if(list == null || list.size() == 0) {
if(list == null || list.isEmpty()) {
return "";
}
StringBuilder str = new StringBuilder();

View File

@ -5,6 +5,8 @@ import cn.dev33.satoken.oauth2.data.model.SaClientModel;
import cn.dev33.satoken.secure.SaSecureUtil;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* Sa-Token OAuth2自定义数据加载器
*
@ -22,7 +24,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
.setClientId("1001")
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
.setAllowUrl("*")
.setContractScope("userinfo")
.setContractScopes(Arrays.asList("userinfo"))
.setIsAutoMode(true);
}
return null;

View File

@ -34,27 +34,25 @@ public class SaOAuth2ServerController {
// Sa-OAuth2 定制化配置
@Autowired
public void setSaOAuth2Config(SaOAuth2Config cfg) {
cfg.
// 未登录的视图
setNotLoginView(()->{
return new ModelAndView("login.html");
}).
// 未登录的视图
cfg.notLoginView = ()->{
return new ModelAndView("login.html");
};
// 登录处理函数
setDoLoginHandle((name, pwd) -> {
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok();
}
return SaResult.error("账号名或密码错误");
}).
cfg.doLoginHandle = (name, pwd) -> {
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok();
}
return SaResult.error("账号名或密码错误");
};
// 授权确认视图
setConfirmView((clientId, scope)->{
Map<String, Object> map = new HashMap<>();
map.put("clientId", clientId);
map.put("scope", scope);
return new ModelAndView("confirm.html", map);
})
;
cfg.confirmView = (clientId, scope)->{
Map<String, Object> map = new HashMap<>();
map.put("clientId", clientId);
map.put("scope", scope);
return new ModelAndView("confirm.html", map);
};
}
// 全局异常拦截

View File

@ -16,6 +16,8 @@
package cn.dev33.satoken.oauth2;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverter;
import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverterDefaultImpl;
import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoader;
import cn.dev33.satoken.oauth2.data.loader.SaOAuth2DataLoaderDefaultImpl;
import cn.dev33.satoken.oauth2.data.resolver.SaOAuth2DataResolver;
@ -84,4 +86,22 @@ public class SaOAuth2Manager {
SaOAuth2Manager.dataResolver = dataResolver;
}
/**
* OAuth2 数据格式转换器
*/
private static volatile SaOAuth2DataConverter dataConverter;
public static SaOAuth2DataConverter getDataConverter() {
if (dataConverter == null) {
synchronized (SaOAuth2Manager.class) {
if (dataConverter == null) {
setDataConverter(new SaOAuth2DataConverterDefaultImpl());
}
}
}
return dataConverter;
}
public static void setDataConverter(SaOAuth2DataConverter dataConverter) {
SaOAuth2Manager.dataConverter = dataConverter;
}
}

View File

@ -15,11 +15,12 @@
*/
package cn.dev33.satoken.oauth2.config;
import cn.dev33.satoken.oauth2.function.SaOAuth2ConfirmViewFunction;
import cn.dev33.satoken.oauth2.function.SaOAuth2DoLoginHandleFunction;
import cn.dev33.satoken.oauth2.function.SaOAuth2NotLoginViewFunction;
import cn.dev33.satoken.util.SaResult;
import java.io.Serializable;
import java.util.function.BiFunction;
import java.util.function.Supplier;
/**
* Sa-Token-OAuth2 配置类 Model
@ -218,65 +219,17 @@ public class SaOAuth2Config implements Serializable {
/**
* OAuth-Server端未登录时返回的View
*/
public Supplier<Object> notLoginView = () -> "当前会话在OAuth-Server认证中心尚未登录";
public SaOAuth2NotLoginViewFunction notLoginView = () -> "当前会话在OAuth-Server认证中心尚未登录";
/**
* OAuth-Server端确认授权时返回的View
*/
public BiFunction<String, String, Object> confirmView = (clientId, scope) -> "本次操作需要用户授权";
public SaOAuth2ConfirmViewFunction confirmView = (clientId, scopes) -> "本次操作需要用户授权";
/**
* OAuth-Server端登录函数
*/
public BiFunction<String, String, Object> doLoginHandle = (name, pwd) -> SaResult.error();
/**
* @param notLoginView OAuth-Server端未登录时返回的View
* @return 对象自身
*/
public SaOAuth2Config setNotLoginView(Supplier<Object> notLoginView) {
this.notLoginView = notLoginView;
return this;
}
/**
* @return 函数 OAuth-Server端未登录时返回的View
*/
public Supplier<Object> getNotLoginView() {
return notLoginView;
}
/**
* @param confirmView OAuth-Server端确认授权时返回的View
* @return 对象自身
*/
public SaOAuth2Config setConfirmView(BiFunction<String, String, Object> confirmView) {
this.confirmView = confirmView;
return this;
}
/**
* @return 函数 OAuth-Server端确认授权时返回的View
*/
public BiFunction<String, String, Object> getConfirmView() {
return confirmView;
}
/**
* @param doLoginHandle OAuth-Server端登录函数
* @return 对象自身
*/
public SaOAuth2Config setDoLoginHandle(BiFunction<String, String, Object> doLoginHandle) {
this.doLoginHandle = doLoginHandle;
return this;
}
/**
* @return 函数 OAuth-Server端登录函数
*/
public BiFunction<String, String, Object> getDoLoginHandle() {
return doLoginHandle;
}
public SaOAuth2DoLoginHandleFunction doLoginHandle = (name, pwd) -> SaResult.error();
@Override
public String toString() {

View File

@ -0,0 +1,49 @@
/*
* 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.convert;
import java.util.List;
/**
* Sa-Token OAuth2 数据格式转换器
*
* @author click33
* @since 1.39.0
*/
public interface SaOAuth2DataConverter {
/**
* 转换 scope 数据格式String -> List
* @param scopeString /
* @return /
*/
List<String> convertScopeStringToList(String scopeString);
/**
* 转换 scope 数据格式List -> String
* @param scopeList /
* @return /
*/
String convertScopeListToString(List<String> scopeList);
/**
* 转换 AllowUrl 数据格式String -> List
* @param allowUrl /
* @return /
*/
List<String> convertAllowUrlStringToList(String allowUrl);
}

View File

@ -0,0 +1,64 @@
/*
* 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.convert;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.Collections;
import java.util.List;
/**
* Sa-Token OAuth2 数据格式转换器默认实现类
*
* @author click33
* @since 1.39.0
*/
public class SaOAuth2DataConverterDefaultImpl implements SaOAuth2DataConverter {
/**
* 转换 scope 数据格式String -> List
*/
@Override
public List<String> convertScopeStringToList(String scopeString) {
if(SaFoxUtil.isEmpty(scopeString)) {
return Collections.emptyList();
}
// 兼容以下三种分隔符空格逗号%20
scopeString = scopeString.replaceAll(" ", ",");
scopeString = scopeString.replaceAll("%20", ",");
return SaFoxUtil.convertStringToList(scopeString);
}
/**
* 转换 scope 数据格式List -> String
*/
@Override
public String convertScopeListToString(List<String> scopeList) {
return SaFoxUtil.convertListToString(scopeList);
}
/**
* 转换 AllowUrl 数据格式String -> List
*/
@Override
public List<String> convertAllowUrlStringToList(String allowUrl) {
if(SaFoxUtil.isEmpty(allowUrl)) {
return Collections.emptyList();
}
return SaFoxUtil.convertStringToList(allowUrl);
}
}

View File

@ -17,6 +17,7 @@ package cn.dev33.satoken.oauth2.data.model;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
@ -67,31 +68,106 @@ public class AccessTokenModel implements Serializable {
/**
* 授权范围
*/
public String scope;
public List<String> scopes;
public AccessTokenModel() {}
/**
* 构建一个
* @param accessToken accessToken
* @param clientId 应用id
* @param scope 请求授权范围
* @param scopes 请求授权范围
* @param loginId 对应的账号id
*/
public AccessTokenModel(String accessToken, String clientId, Object loginId, String scope) {
public AccessTokenModel(String accessToken, String clientId, Object loginId, List<String> scopes) {
super();
this.accessToken = accessToken;
this.clientId = clientId;
this.loginId = loginId;
this.scope = scope;
this.scopes = scopes;
}
public String getAccessToken() {
return accessToken;
}
public AccessTokenModel setAccessToken(String accessToken) {
this.accessToken = accessToken;
return this;
}
public String getRefreshToken() {
return refreshToken;
}
public AccessTokenModel setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
return this;
}
public long getExpiresTime() {
return expiresTime;
}
public AccessTokenModel setExpiresTime(long expiresTime) {
this.expiresTime = expiresTime;
return this;
}
public long getRefreshExpiresTime() {
return refreshExpiresTime;
}
public AccessTokenModel setRefreshExpiresTime(long refreshExpiresTime) {
this.refreshExpiresTime = refreshExpiresTime;
return this;
}
public String getClientId() {
return clientId;
}
public AccessTokenModel setClientId(String clientId) {
this.clientId = clientId;
return this;
}
public Object getLoginId() {
return loginId;
}
public AccessTokenModel setLoginId(Object loginId) {
this.loginId = loginId;
return this;
}
public String getOpenid() {
return openid;
}
public AccessTokenModel setOpenid(String openid) {
this.openid = openid;
return this;
}
public List<String> getScopes() {
return scopes;
}
public AccessTokenModel setScopes(List<String> scopes) {
this.scopes = scopes;
return this;
}
@Override
public String toString() {
return "AccessTokenModel [accessToken=" + accessToken + ", refreshToken=" + refreshToken
+ ", accessTokenTimeout=" + expiresTime + ", refreshTokenTimeout=" + refreshExpiresTime
+ ", clientId=" + clientId + ", scope=" + scope + ", openid=" + openid + "]";
+ ", clientId=" + clientId + ", scopes=" + scopes + ", openid=" + openid + "]";
}
// 追加只读属性
/**
* 获取 Access-Token 的剩余有效期
@ -115,6 +191,7 @@ public class AccessTokenModel implements Serializable {
* 将所有属性转换为下划线形式的Map
* @return 属性转Map
*/
@Deprecated
public Map<String, Object> toLineMap() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("access_token", accessToken);
@ -122,7 +199,7 @@ public class AccessTokenModel implements Serializable {
map.put("expires_in", getExpiresIn());
map.put("refresh_expires_in", getRefreshExpiresIn());
map.put("client_id", clientId);
map.put("scope", scope);
map.put("scopes", scopes);
map.put("openid", openid);
return map;
}

View File

@ -17,6 +17,7 @@ package cn.dev33.satoken.oauth2.data.model;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
@ -47,7 +48,7 @@ public class ClientTokenModel implements Serializable {
/**
* 授权范围
*/
public String scope;
public List<String> scopes;
public ClientTokenModel() {}
@ -55,19 +56,19 @@ public class ClientTokenModel implements Serializable {
* 构建一个
* @param accessToken accessToken
* @param clientId 应用id
* @param scope 请求授权范围
* @param scopes 请求授权范围
*/
public ClientTokenModel(String accessToken, String clientId, String scope) {
public ClientTokenModel(String accessToken, String clientId, List<String> scopes) {
super();
this.clientToken = accessToken;
this.clientId = clientId;
this.scope = scope;
this.scopes = scopes;
}
@Override
public String toString() {
return "ClientTokenModel [clientToken=" + clientToken + ", expiresTime=" + expiresTime + ", clientId="
+ clientId + ", scope=" + scope + "]";
+ clientId + ", scopes=" + scopes + "]";
}
/**
@ -80,15 +81,16 @@ public class ClientTokenModel implements Serializable {
}
/**
* 将所有属性转换为下划线形式的Map
* @return 属性转Map
* 将所有属性转换为下划线形式的Map
* @return 属性转Map
*/
@Deprecated
public Map<String, Object> toLineMap() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("client_token", clientToken);
map.put("expires_in", getExpiresIn());
map.put("client_id", clientId);
map.put("scope", scope);
map.put("scopes", scopes);
return map;
}

View File

@ -16,6 +16,7 @@
package cn.dev33.satoken.oauth2.data.model;
import java.io.Serializable;
import java.util.List;
/**
* Model: 授权码
@ -40,7 +41,7 @@ public class CodeModel implements Serializable {
/**
* 授权范围
*/
public String scope;
public List<String> scopes;
/**
* 对应账号id
@ -62,93 +63,67 @@ public class CodeModel implements Serializable {
* 构建一个
* @param code 授权码
* @param clientId 应用id
* @param scope 请求授权范围
* @param scopes 请求授权范围
* @param loginId 对应的账号id
* @param redirectUri 重定向地址
*/
public CodeModel(String code, String clientId, String scope, Object loginId, String redirectUri) {
public CodeModel(String code, String clientId, List<String> scopes, Object loginId, String redirectUri) {
super();
this.code = code;
this.clientId = clientId;
this.scope = scope;
this.scopes = scopes;
this.loginId = loginId;
this.redirectUri = redirectUri;
}
/**
* @return code
*/
public String getCode() {
return code;
}
/**
* @param code 要设置的 code
*/
public void setCode(String code) {
public CodeModel setCode(String code) {
this.code = code;
return this;
}
/**
* @return clientId
*/
public String getClientId() {
return clientId;
}
/**
* @param clientId 要设置的 clientId
*/
public void setClientId(String clientId) {
public CodeModel setClientId(String clientId) {
this.clientId = clientId;
return this;
}
/**
* @return scope
*/
public String getScope() {
return scope;
public List<String> getScopes() {
return scopes;
}
/**
* @param scope 要设置的 scope
*/
public void setScope(String scope) {
this.scope = scope;
public CodeModel setScopes(List<String> scopes) {
this.scopes = scopes;
return this;
}
/**
* @return loginId
*/
public Object getLoginId() {
return loginId;
}
/**
* @param loginId 要设置的 loginId
*/
public void setLoginId(Object loginId) {
public CodeModel setLoginId(Object loginId) {
this.loginId = loginId;
return this;
}
/**
* @return redirectUri
*/
public String getRedirectUri() {
return redirectUri;
}
/**
* @param redirectUri 要设置的 redirectUri
*/
public void setRedirectUri(String redirectUri) {
public CodeModel setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
return this;
}
@Override
public String toString() {
return "CodeModel [code=" + code + ", clientId=" + clientId + ", scope=" + scope + ", loginId=" + loginId
return "CodeModel [code=" + code + ", clientId=" + clientId + ", scopes=" + scopes + ", loginId=" + loginId
+ ", redirectUri=" + redirectUri + "]";
}

View File

@ -16,6 +16,7 @@
package cn.dev33.satoken.oauth2.data.model;
import java.io.Serializable;
import java.util.List;
/**
* Model: Refresh-Token
@ -45,7 +46,7 @@ public class RefreshTokenModel implements Serializable {
/**
* 授权范围
*/
public String scope;
public List<String> scopes;
/**
* 对应账号id
@ -57,10 +58,64 @@ public class RefreshTokenModel implements Serializable {
*/
public String openid;
public String getRefreshToken() {
return refreshToken;
}
public RefreshTokenModel setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
return this;
}
public long getExpiresTime() {
return expiresTime;
}
public RefreshTokenModel setExpiresTime(long expiresTime) {
this.expiresTime = expiresTime;
return this;
}
public String getClientId() {
return clientId;
}
public RefreshTokenModel setClientId(String clientId) {
this.clientId = clientId;
return this;
}
public List<String> getScopes() {
return scopes;
}
public RefreshTokenModel setScopes(List<String> scopes) {
this.scopes = scopes;
return this;
}
public Object getLoginId() {
return loginId;
}
public RefreshTokenModel setLoginId(Object loginId) {
this.loginId = loginId;
return this;
}
public String getOpenid() {
return openid;
}
public RefreshTokenModel setOpenid(String openid) {
this.openid = openid;
return this;
}
@Override
public String toString() {
return "RefreshTokenModel [refreshToken=" + refreshToken + ", expiresTime=" + expiresTime
+ ", clientId=" + clientId + ", scope=" + scope + ", loginId=" + loginId + ", openid=" + openid + "]";
+ ", clientId=" + clientId + ", scopes=" + scopes + ", loginId=" + loginId + ", openid=" + openid + "]";
}
/**

View File

@ -16,6 +16,7 @@
package cn.dev33.satoken.oauth2.data.model;
import java.io.Serializable;
import java.util.List;
import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
@ -39,7 +40,7 @@ public class RequestAuthModel implements Serializable {
/**
* 授权范围
*/
public String scope;
public List<String> scopes;
/**
* 对应的账号id
@ -79,18 +80,18 @@ public class RequestAuthModel implements Serializable {
}
/**
* @return scope
* @return scopes
*/
public String getScope() {
return scope;
public List<String> getScopes() {
return scopes;
}
/**
* @param scope 要设置的 scope
* @param scopes 要设置的 scopes
* @return 对象自身
*/
public RequestAuthModel setScope(String scope) {
this.scope = scope;
public RequestAuthModel setScopes(List<String> scopes) {
this.scopes = scopes;
return this;
}
@ -166,7 +167,7 @@ public class RequestAuthModel implements Serializable {
if(SaFoxUtil.isEmpty(clientId)) {
throw new SaOAuth2Exception("client_id 不可为空").setCode(SaOAuth2ErrorCode.CODE_30101);
}
if(SaFoxUtil.isEmpty(scope)) {
if(SaFoxUtil.isEmpty(scopes)) {
throw new SaOAuth2Exception("scope 不可为空").setCode(SaOAuth2ErrorCode.CODE_30102);
}
if(SaFoxUtil.isEmpty(redirectUri)) {

View File

@ -15,11 +15,12 @@
*/
package cn.dev33.satoken.oauth2.data.model;
import java.io.Serializable;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.config.SaOAuth2Config;
import java.io.Serializable;
import java.util.List;
/**
* Client应用信息 Model
*
@ -43,7 +44,7 @@ public class SaClientModel implements Serializable {
/**
* 应用签约的所有权限, 多个用逗号隔开
*/
public String contractScope;
public List<String> contractScopes;
/**
* 应用允许授权的所有URL, 多个用逗号隔开
@ -93,11 +94,11 @@ public class SaClientModel implements Serializable {
this.clientTokenTimeout = config.getClientTokenTimeout();
this.pastClientTokenTimeout = config.getPastClientTokenTimeout();
}
public SaClientModel(String clientId, String clientSecret, String contractScope, String allowUrl) {
public SaClientModel(String clientId, String clientSecret, List<String> contractScopes, String allowUrl) {
super();
this.clientId = clientId;
this.clientSecret = clientSecret;
this.contractScope = contractScope;
this.contractScopes = contractScopes;
this.allowUrl = allowUrl;
}
@ -134,18 +135,18 @@ public class SaClientModel implements Serializable {
}
/**
* @return 应用签约的所有权限, 多个用逗号隔开
* @return 应用签约的所有权限
*/
public String getContractScope() {
return contractScope;
public List<String> getContractScopes() {
return contractScopes;
}
/**
* @param contractScope 应用签约的所有权限, 多个用逗号隔开
* @param contractScopes 应用签约的所有权限, 多个用逗号隔开
* @return 对象自身
*/
public SaClientModel setContractScope(String contractScope) {
this.contractScope = contractScope;
public SaClientModel setContractScopes(List<String> contractScopes) {
this.contractScopes = contractScopes;
return this;
}
@ -329,8 +330,8 @@ public class SaClientModel implements Serializable {
//
@Override
public String toString() {
return "SaClientModel [clientId=" + clientId + ", clientSecret=" + clientSecret + ", contractScope="
+ contractScope + ", allowUrl=" + allowUrl + ", isCode=" + isCode + ", isImplicit=" + isImplicit
return "SaClientModel [clientId=" + clientId + ", clientSecret=" + clientSecret + ", contractScopes="
+ contractScopes + ", allowUrl=" + allowUrl + ", isCode=" + isCode + ", isImplicit=" + isImplicit
+ ", isPassword=" + isPassword + ", isClient=" + isClient + ", isAutoMode=" + isAutoMode
+ ", isNewRefresh=" + isNewRefresh + ", accessTokenTimeout=" + accessTokenTimeout
+ ", refreshTokenTimeout=" + refreshTokenTimeout + ", clientTokenTimeout=" + clientTokenTimeout

View File

@ -15,6 +15,7 @@
*/
package cn.dev33.satoken.oauth2.data.resolver;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.TokenType;
import cn.dev33.satoken.oauth2.data.model.AccessTokenModel;
import cn.dev33.satoken.oauth2.data.model.ClientTokenModel;
@ -44,7 +45,7 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver {
map.put("expires_in", at.getExpiresIn());
map.put("refresh_expires_in", at.getRefreshExpiresIn());
map.put("client_id", at.clientId);
map.put("scope", at.scope);
map.put("scope", SaOAuth2Manager.getDataConverter().convertScopeListToString(at.scopes));
map.put("openid", at.openid);
return SaResult.ok().setMap(map);
}
@ -59,7 +60,7 @@ public class SaOAuth2DataResolverDefaultImpl implements SaOAuth2DataResolver {
map.put("access_token", ct.clientToken); // 兼容 OAuth2 协议
map.put("expires_in", ct.getExpiresIn());
map.put("client_id", ct.clientId);
map.put("scope", ct.scope);
map.put("scope", SaOAuth2Manager.getDataConverter().convertScopeListToString(ct.scopes));
return SaResult.ok().setMap(map);
}

View File

@ -0,0 +1,34 @@
/*
* 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.function;
import java.util.List;
import java.util.function.BiFunction;
/**
* 函数式接口OAuth-Server端 确认授权时返回的View
*
* <p> 参数 </p>
* <p> 返回view 视图 </p>
*
* @author click33
* @since 1.39.0
*/
@FunctionalInterface
public interface SaOAuth2ConfirmViewFunction extends BiFunction<String, List<String>, Object> {
}

View File

@ -0,0 +1,33 @@
/*
* 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.function;
import java.util.function.BiFunction;
/**
* 函数式接口登录函数
*
* <p> 参数name, pwd </p>
* <p> 返回认证返回结果 </p>
*
* @author click33
* @since 1.39.0
*/
@FunctionalInterface
public interface SaOAuth2DoLoginHandleFunction extends BiFunction<String, String, Object> {
}

View File

@ -0,0 +1,33 @@
/*
* 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.function;
import java.util.function.Supplier;
/**
* 函数式接口OAuth-Server端 未登录时返回的View
*
* <p> 参数clientId, scope </p>
* <p> 返回view 视图 </p>
*
* @author click33
* @since 1.39.0
*/
@FunctionalInterface
public interface SaOAuth2NotLoginViewFunction extends Supplier<Object> {
}

View File

@ -35,6 +35,8 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
import java.util.List;
/**
* Sa-Token OAuth2 请求处理器
*
@ -142,7 +144,7 @@ public class SaOAuth2ServerProcessor {
// 1如果尚未登录, 则先去登录
if( ! getStpLogic().isLogin()) {
return cfg.getNotLoginView().get();
return cfg.notLoginView.get();
}
// 2构建请求Model
@ -152,12 +154,12 @@ public class SaOAuth2ServerProcessor {
oauth2Template.checkRightUrl(ra.clientId, ra.redirectUri);
// 4校验此次申请的Scope该Client是否已经签约
oauth2Template.checkContract(ra.clientId, ra.scope);
oauth2Template.checkContract(ra.clientId, ra.scopes);
// 5判断如果此次申请的Scope该用户尚未授权则转到授权页面
boolean isGrant = oauth2Template.isGrant(ra.loginId, ra.clientId, ra.scope);
boolean isGrant = oauth2Template.isGrant(ra.loginId, ra.clientId, ra.scopes);
if( ! isGrant) {
return cfg.getConfirmView().apply(ra.clientId, ra.scope);
return cfg.confirmView.apply(ra.clientId, ra.scopes);
}
// 6判断授权类型
@ -275,7 +277,7 @@ public class SaOAuth2ServerProcessor {
SaRequest req = SaHolder.getRequest();
SaOAuth2Config cfg = SaOAuth2Manager.getConfig();
return cfg.getDoLoginHandle().apply(req.getParamNotNull(Param.name), req.getParamNotNull(Param.pwd));
return cfg.doLoginHandle.apply(req.getParamNotNull(Param.name), req.getParamNotNull(Param.pwd));
}
/**
@ -288,8 +290,9 @@ public class SaOAuth2ServerProcessor {
String clientId = req.getParamNotNull(Param.client_id);
String scope = req.getParamNotNull(Param.scope);
List<String> scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(scope);
Object loginId = getStpLogic().getLoginId();
oauth2Template.saveGrantScope(clientId, loginId, scope);
oauth2Template.saveGrantScope(clientId, loginId, scopes);
return SaResult.ok();
}
@ -308,15 +311,16 @@ public class SaOAuth2ServerProcessor {
String clientId = req.getParamNotNull(Param.client_id);
String clientSecret = req.getParamNotNull(Param.client_secret);
String scope = req.getParam(Param.scope, "");
List<String> scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(scope);
// 2校验 ClientScope scope
oauth2Template.checkClientSecretAndScope(clientId, clientSecret, scope);
oauth2Template.checkClientSecretAndScope(clientId, clientSecret, scopes);
// 3防止因前端误传token造成逻辑干扰
// SaHolder.getStorage().set(getStpLogic().stpLogic.splicingKeyJustCreatedSave(), "no-token");
// 3调用API 开始登录如果没能成功登录则直接退出
Object retObj = cfg.getDoLoginHandle().apply(username, password);
Object retObj = cfg.doLoginHandle.apply(username, password);
if( ! getStpLogic().isLogin()) {
return retObj;
}
@ -325,7 +329,7 @@ public class SaOAuth2ServerProcessor {
RequestAuthModel ra = new RequestAuthModel();
ra.clientId = clientId;
ra.loginId = getStpLogic().getLoginId();
ra.scope = scope;
ra.scopes = scopes;
// 5生成 Access-Token
AccessTokenModel at = oauth2Template.generateAccessToken(ra, true);
@ -345,16 +349,17 @@ public class SaOAuth2ServerProcessor {
// 获取参数
String clientId = req.getParamNotNull(Param.client_id);
String clientSecret = req.getParamNotNull(Param.client_secret);
String scope = req.getParam(Param.scope);
String scope = req.getParam(Param.scope, "");
List<String> scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(scope);
//校验 ClientScope
oauth2Template.checkContract(clientId, scope);
oauth2Template.checkContract(clientId, scopes);
// 校验 ClientSecret
oauth2Template.checkClientSecret(clientId, clientSecret);
// 生成
ClientTokenModel ct = oauth2Template.generateClientToken(clientId, scope);
ClientTokenModel ct = oauth2Template.generateClientToken(clientId, scopes);
// 返回
return SaOAuth2Manager.getDataResolver().buildClientTokenReturnValue(ct);

View File

@ -25,6 +25,7 @@ import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.HashSet;
import java.util.List;
/**
@ -107,7 +108,7 @@ public class SaOAuth2Template {
return;
}
AccessTokenModel at = checkAccessToken(accessToken);
List<String> scopeList = SaFoxUtil.convertStringToList(at.scope);
List<String> scopeList = at.scopes;
for (String scope : scopes) {
SaOAuth2Exception.throwBy( ! scopeList.contains(scope), "该 Access-Token 不具备 Scope" + scope, SaOAuth2ErrorCode.CODE_30108);
}
@ -122,7 +123,7 @@ public class SaOAuth2Template {
return;
}
ClientTokenModel ct = checkClientToken(clientToken);
List<String> scopeList = SaFoxUtil.convertStringToList(ct.scope);
List<String> scopeList = ct.scopes;
for (String scope : scopes) {
SaOAuth2Exception.throwBy( ! scopeList.contains(scope), "该 Client-Token 不具备 Scope" + scope, SaOAuth2ErrorCode.CODE_30109);
}
@ -141,7 +142,9 @@ public class SaOAuth2Template {
ra.responseType = req.getParamNotNull(Param.response_type);
ra.redirectUri = req.getParamNotNull(Param.redirect_uri);
ra.state = req.getParam(Param.state);
ra.scope = req.getParam(Param.scope, "");
// 数据解析
String scope = req.getParam(Param.scope, "");
ra.scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(scope);
ra.loginId = loginId;
return ra;
}
@ -156,8 +159,8 @@ public class SaOAuth2Template {
deleteCode(getCodeValue(ra.clientId, ra.loginId));
// 生成新Code
String code = randomCode(ra.clientId, ra.loginId, ra.scope);
CodeModel cm = new CodeModel(code, ra.clientId, ra.scope, ra.loginId, ra.redirectUri);
String code = randomCode(ra.clientId, ra.loginId, ra.scopes);
CodeModel cm = new CodeModel(code, ra.clientId, ra.scopes, ra.loginId, ra.redirectUri);
// 保存新Code
saveCode(cm);
@ -250,8 +253,8 @@ public class SaOAuth2Template {
}
// 2生成 新Access-Token
String newAtValue = randomAccessToken(ra.clientId, ra.loginId, ra.scope);
AccessTokenModel at = new AccessTokenModel(newAtValue, ra.clientId, ra.loginId, ra.scope);
String newAtValue = randomAccessToken(ra.clientId, ra.loginId, ra.scopes);
AccessTokenModel at = new AccessTokenModel(newAtValue, ra.clientId, ra.loginId, ra.scopes);
at.openid = getOpenid(ra.clientId, ra.loginId);
at.expiresTime = System.currentTimeMillis() + (checkClientModel(ra.clientId).getAccessTokenTimeout() * 1000);
@ -272,10 +275,10 @@ public class SaOAuth2Template {
/**
* 构建ModelClient-Token
* @param clientId 应用id
* @param scope 授权范围
* @param scopes 授权范围
* @return Client-Token Model
*/
public ClientTokenModel generateClientToken(String clientId, String scope) {
public ClientTokenModel generateClientToken(String clientId, List<String> scopes) {
// 1删掉旧 Past-Token
deleteClientToken(getPastTokenValue(clientId));
@ -291,7 +294,7 @@ public class SaOAuth2Template {
}
// 3生成新Client-Token
ClientTokenModel ct = new ClientTokenModel(randomClientToken(clientId, scope), clientId, scope);
ClientTokenModel ct = new ClientTokenModel(randomClientToken(clientId, scopes), clientId, scopes);
ct.expiresTime = System.currentTimeMillis() + (cm.getClientTokenTimeout() * 1000);
// 3保存新Client-Token
@ -356,23 +359,21 @@ public class SaOAuth2Template {
* 判断指定 loginId 是否对一个 Client 授权给了指定 Scope
* @param loginId 账号id
* @param clientId 应用id
* @param scope 权限
* @param scopes 权限
* @return 是否已经授权
*/
public boolean isGrant(Object loginId, String clientId, String scope) {
List<String> grantScopeList = SaFoxUtil.convertStringToList(getGrantScope(clientId, loginId));
List<String> scopeList = SaFoxUtil.convertStringToList(scope);
return scopeList.size() == 0 || grantScopeList.containsAll(scopeList);
public boolean isGrant(Object loginId, String clientId, List<String> scopes) {
List<String> grantScopeList = getGrantScope(clientId, loginId);
return scopes.isEmpty() || new HashSet<>(grantScopeList).containsAll(scopes);
}
/**
* 校验该Client是否签约了指定的Scope
* @param clientId 应用id
* @param scope 权限(多个用逗号隔开)
* @param scopes 权限(多个用逗号隔开)
*/
public void checkContract(String clientId, String scope) {
List<String> clientScopeList = SaFoxUtil.convertStringToList(checkClientModel(clientId).contractScope);
List<String> scopelist = SaFoxUtil.convertStringToList(scope);
if( ! clientScopeList.containsAll(scopelist)) {
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);
}
}
@ -394,7 +395,8 @@ public class SaOAuth2Template {
}
// 3是否在[允许地址列表]之中
List<String> allowList = SaFoxUtil.convertStringToList(checkClientModel(clientId).allowUrl);
SaClientModel clientModel = checkClientModel(clientId);
List<String> allowList = SaOAuth2Manager.getDataConverter().convertAllowUrlStringToList(clientModel.allowUrl);
if( ! SaStrategy.instance.hasElement.apply(allowList, url)) {
throw new SaOAuth2Exception("非法redirect_url" + url).setCode(SaOAuth2ErrorCode.CODE_30114);
}
@ -415,16 +417,15 @@ public class SaOAuth2Template {
* 校验clientId clientSecret 是否正确并且是否签约了指定 scopes
* @param clientId 应用id
* @param clientSecret 秘钥
* @param scopes 权限多个用逗号隔开
* @param scopes 权限
* @return SaClientModel对象
*/
public SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, String scopes) {
public SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List<String> scopes) {
// 先校验 clientSecret
SaClientModel cm = checkClientSecret(clientId, clientSecret);
// 再校验 是否签约
List<String> clientScopeList = SaFoxUtil.convertStringToList(cm.contractScope);
List<String> scopelist = SaFoxUtil.convertStringToList(scopes);
if( ! clientScopeList.containsAll(scopelist)) {
List<String> clientScopeList = cm.contractScopes;
if( ! new HashSet<>(clientScopeList).containsAll(scopes)) {
throw new SaOAuth2Exception("请求的Scope暂未签约").setCode(SaOAuth2ErrorCode.CODE_30116);
}
// 返回数据
@ -504,11 +505,11 @@ public class SaOAuth2Template {
*/
public AccessTokenModel convertCodeToAccessToken(CodeModel cm) {
AccessTokenModel at = new AccessTokenModel();
at.accessToken = randomAccessToken(cm.clientId, cm.loginId, cm.scope);
at.accessToken = randomAccessToken(cm.clientId, cm.loginId, cm.scopes);
// at.refreshToken = randomRefreshToken(cm.clientId, cm.loginId, cm.scope);
at.clientId = cm.clientId;
at.loginId = cm.loginId;
at.scope = cm.scope;
at.scopes = cm.scopes;
at.openid = getOpenid(cm.clientId, cm.loginId);
at.expiresTime = System.currentTimeMillis() + (checkClientModel(cm.clientId).getAccessTokenTimeout() * 1000);
// at.refreshExpiresTime = System.currentTimeMillis() + (checkClientModel(cm.clientId).getRefreshTokenTimeout() * 1000);
@ -521,10 +522,10 @@ public class SaOAuth2Template {
*/
public RefreshTokenModel convertAccessTokenToRefreshToken(AccessTokenModel at) {
RefreshTokenModel rt = new RefreshTokenModel();
rt.refreshToken = randomRefreshToken(at.clientId, at.loginId, at.scope);
rt.refreshToken = randomRefreshToken(at.clientId, at.loginId, at.scopes);
rt.clientId = at.clientId;
rt.loginId = at.loginId;
rt.scope = at.scope;
rt.scopes = at.scopes;
rt.openid = at.openid;
rt.expiresTime = System.currentTimeMillis() + (checkClientModel(at.clientId).getRefreshTokenTimeout() * 1000);
// 改变at属性
@ -539,11 +540,11 @@ public class SaOAuth2Template {
*/
public AccessTokenModel convertRefreshTokenToAccessToken(RefreshTokenModel rt) {
AccessTokenModel at = new AccessTokenModel();
at.accessToken = randomAccessToken(rt.clientId, rt.loginId, rt.scope);
at.accessToken = randomAccessToken(rt.clientId, rt.loginId, rt.scopes);
at.refreshToken = rt.refreshToken;
at.clientId = rt.clientId;
at.loginId = rt.loginId;
at.scope = rt.scope;
at.scopes = rt.scopes;
at.openid = rt.openid;
at.expiresTime = System.currentTimeMillis() + (checkClientModel(rt.clientId).getAccessTokenTimeout() * 1000);
at.refreshExpiresTime = rt.expiresTime;
@ -556,10 +557,10 @@ public class SaOAuth2Template {
*/
public RefreshTokenModel convertRefreshTokenToRefreshToken(RefreshTokenModel rt) {
RefreshTokenModel newRt = new RefreshTokenModel();
newRt.refreshToken = randomRefreshToken(rt.clientId, rt.loginId, rt.scope);
newRt.refreshToken = randomRefreshToken(rt.clientId, rt.loginId, rt.scopes);
newRt.expiresTime = System.currentTimeMillis() + (checkClientModel(rt.clientId).getRefreshTokenTimeout() * 1000);
newRt.clientId = rt.clientId;
newRt.scope = rt.scope;
newRt.scopes = rt.scopes;
newRt.loginId = rt.loginId;
newRt.openid = rt.openid;
return newRt;
@ -665,12 +666,13 @@ public class SaOAuth2Template {
* 持久化用户授权记录
* @param clientId 应用id
* @param loginId 账号id
* @param scope 权限列表(多个逗号隔开)
* @param scopes 权限列表
*/
public void saveGrantScope(String clientId, Object loginId, String scope) {
if( ! SaFoxUtil.isEmpty(scope)) {
public void saveGrantScope(String clientId, Object loginId, List<String> scopes) {
if( ! SaFoxUtil.isEmpty(scopes)) {
long ttl = checkClientModel(clientId).getAccessTokenTimeout();
SaManager.getSaTokenDao().set(splicingGrantScopeKey(clientId, loginId), scope, ttl);
String value = SaOAuth2Manager.getDataConverter().convertScopeListToString(scopes);
SaManager.getSaTokenDao().set(splicingGrantScopeKey(clientId, loginId), value, ttl);
}
}
@ -768,8 +770,9 @@ public class SaOAuth2Template {
* @param loginId 账号id
* @return 权限
*/
public String getGrantScope(String clientId, Object loginId) {
return SaManager.getSaTokenDao().get(splicingGrantScopeKey(clientId, loginId));
public List<String> getGrantScope(String clientId, Object loginId) {
String value = SaManager.getSaTokenDao().get(splicingGrantScopeKey(clientId, loginId));
return SaOAuth2Manager.getDataConverter().convertScopeStringToList(value);
}
// ------------------- delete数据
@ -861,39 +864,39 @@ public class SaOAuth2Template {
* 随机一个 Code
* @param clientId 应用id
* @param loginId 账号id
* @param scope 权限
* @param scopes 权限
* @return Code
*/
public String randomCode(String clientId, Object loginId, String scope) {
public String randomCode(String clientId, Object loginId, List<String> scopes) {
return SaFoxUtil.getRandomString(60);
}
/**
* 随机一个 Access-Token
* @param clientId 应用id
* @param loginId 账号id
* @param scope 权限
* @param scopes 权限
* @return Access-Token
*/
public String randomAccessToken(String clientId, Object loginId, String scope) {
public String randomAccessToken(String clientId, Object loginId, List<String> scopes) {
return SaFoxUtil.getRandomString(60);
}
/**
* 随机一个 Refresh-Token
* @param clientId 应用id
* @param loginId 账号id
* @param scope 权限
* @param scopes 权限
* @return Refresh-Token
*/
public String randomRefreshToken(String clientId, Object loginId, String scope) {
public String randomRefreshToken(String clientId, Object loginId, List<String> scopes) {
return SaFoxUtil.getRandomString(60);
}
/**
* 随机一个 Client-Token
* @param clientId 应用id
* @param scope 权限
* @param scopes 权限
* @return Client-Token
*/
public String randomClientToken(String clientId, String scope) {
public String randomClientToken(String clientId, List<String> scopes) {
return SaFoxUtil.getRandomString(60);
}

View File

@ -19,6 +19,8 @@ import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.oauth2.data.model.*;
import cn.dev33.satoken.oauth2.processor.SaOAuth2ServerProcessor;
import java.util.List;
/**
* Sa-Token-OAuth2 模块 工具类
*
@ -135,11 +137,11 @@ public class SaOAuth2Util {
/**
* 构建ModelClient-Token
* @param clientId 应用id
* @param scope 授权范围
* @param scopes 授权范围
* @return Client-Token Model
*/
public static ClientTokenModel generateClientToken(String clientId, String scope) {
return SaOAuth2ServerProcessor.instance.oauth2Template.generateClientToken(clientId, scope);
public static ClientTokenModel generateClientToken(String clientId, List<String> scopes) {
return SaOAuth2ServerProcessor.instance.oauth2Template.generateClientToken(clientId, scopes);
}
/**
@ -178,20 +180,20 @@ public class SaOAuth2Util {
* 判断指定 loginId 是否对一个 Client 授权给了指定 Scope
* @param loginId 账号id
* @param clientId 应用id
* @param scope 权限
* @param scopes 权限
* @return 是否已经授权
*/
public static boolean isGrant(Object loginId, String clientId, String scope) {
return SaOAuth2ServerProcessor.instance.oauth2Template.isGrant(loginId, clientId, scope);
public static boolean isGrant(Object loginId, String clientId, List<String> scopes) {
return SaOAuth2ServerProcessor.instance.oauth2Template.isGrant(loginId, clientId, scopes);
}
/**
* 校验该Client是否签约了指定的Scope
* @param clientId 应用id
* @param scope 权限(多个用逗号隔开)
* @param scopes 权限(多个用逗号隔开)
*/
public static void checkContract(String clientId, String scope) {
SaOAuth2ServerProcessor.instance.oauth2Template.checkContract(clientId, scope);
public static void checkContract(String clientId, List<String> scopes) {
SaOAuth2ServerProcessor.instance.oauth2Template.checkContract(clientId, scopes);
}
/**
@ -217,10 +219,10 @@ public class SaOAuth2Util {
* 校验clientId clientSecret 是否正确并且是否签约了指定 scopes
* @param clientId 应用id
* @param clientSecret 秘钥
* @param scopes 权限多个用逗号隔开
* @param scopes 权限
* @return SaClientModel对象
*/
public static SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, String scopes) {
public static SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List<String> scopes) {
return SaOAuth2ServerProcessor.instance.oauth2Template.checkClientSecretAndScope(clientId, clientSecret, scopes);
}
@ -264,10 +266,10 @@ public class SaOAuth2Util {
* 持久化用户授权记录
* @param clientId 应用id
* @param loginId 账号id
* @param scope 权限列表(多个逗号隔开)
* @param scopes 权限列表
*/
public static void saveGrantScope(String clientId, Object loginId, String scope) {
SaOAuth2ServerProcessor.instance.oauth2Template.saveGrantScope(clientId, loginId, scope);
public static void saveGrantScope(String clientId, Object loginId, List<String> scopes) {
SaOAuth2ServerProcessor.instance.oauth2Template.saveGrantScope(clientId, loginId, scopes);
}
@ -315,7 +317,7 @@ public class SaOAuth2Util {
* @param loginId 账号id
* @return 权限
*/
public static String getGrantScope(String clientId, Object loginId) {
public static List<String> getGrantScope(String clientId, Object loginId) {
return SaOAuth2ServerProcessor.instance.oauth2Template.getGrantScope(clientId, loginId);
}