新增自定义 scope 处理器支持

This commit is contained in:
click33
2024-08-18 18:08:46 +08:00
parent 2b46d27b87
commit 281985bfdb
11 changed files with 244 additions and 30 deletions
@@ -3,6 +3,8 @@ package com.pj.oauth2;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult; import cn.dev33.satoken.util.SaResult;
import com.ejlchina.okhttps.OkHttps; import com.ejlchina.okhttps.OkHttps;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pj.utils.SoMap; import com.pj.utils.SoMap;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -32,7 +34,7 @@ public class SaOAuthClientController {
// 根据Code码进行登录,获取 Access-Token 和 openid // 根据Code码进行登录,获取 Access-Token 和 openid
@RequestMapping("/codeLogin") @RequestMapping("/codeLogin")
public SaResult codeLogin(String code) { public SaResult codeLogin(String code) throws JsonProcessingException {
// 调用Server端接口,获取 Access-Token 以及其他信息 // 调用Server端接口,获取 Access-Token 以及其他信息
String str = OkHttps.sync(serverUrl + "/oauth2/token") String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "authorization_code") .addBodyPara("grant_type", "authorization_code")
@@ -43,7 +45,7 @@ public class SaOAuthClientController {
.getBody() .getBody()
.toString(); .toString();
SoMap so = SoMap.getSoMap().setJsonString(str); SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so); System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败 // code不等于200 代表请求失败
if(so.getInt("code") != 200) { if(so.getInt("code") != 200) {
@@ -61,7 +63,7 @@ public class SaOAuthClientController {
// 根据 Refresh-Token 去刷新 Access-Token // 根据 Refresh-Token 去刷新 Access-Token
@RequestMapping("/refresh") @RequestMapping("/refresh")
public SaResult refresh(String refreshToken) { public SaResult refresh(String refreshToken) throws JsonProcessingException {
// 调用Server端接口,通过 Refresh-Token 刷新出一个新的 Access-Token // 调用Server端接口,通过 Refresh-Token 刷新出一个新的 Access-Token
String str = OkHttps.sync(serverUrl + "/oauth2/refresh") String str = OkHttps.sync(serverUrl + "/oauth2/refresh")
.addBodyPara("grant_type", "refresh_token") .addBodyPara("grant_type", "refresh_token")
@@ -72,7 +74,7 @@ public class SaOAuthClientController {
.getBody() .getBody()
.toString(); .toString();
SoMap so = SoMap.getSoMap().setJsonString(str); SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so); System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败 // code不等于200 代表请求失败
if(so.getInt("code") != 200) { if(so.getInt("code") != 200) {
@@ -85,7 +87,7 @@ public class SaOAuthClientController {
// 模式三:密码式-授权登录 // 模式三:密码式-授权登录
@RequestMapping("/passwordLogin") @RequestMapping("/passwordLogin")
public SaResult passwordLogin(String username, String password) { public SaResult passwordLogin(String username, String password) throws JsonProcessingException {
// 模式三:密码式-授权登录 // 模式三:密码式-授权登录
String str = OkHttps.sync(serverUrl + "/oauth2/token") String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "password") .addBodyPara("grant_type", "password")
@@ -97,7 +99,7 @@ public class SaOAuthClientController {
.getBody() .getBody()
.toString(); .toString();
SoMap so = SoMap.getSoMap().setJsonString(str); SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so); System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败 // code不等于200 代表请求失败
if(so.getInt("code") != 200) { if(so.getInt("code") != 200) {
@@ -115,7 +117,7 @@ public class SaOAuthClientController {
// 模式四:获取应用的 Client-Token // 模式四:获取应用的 Client-Token
@RequestMapping("/clientToken") @RequestMapping("/clientToken")
public SaResult clientToken() { public SaResult clientToken() throws JsonProcessingException {
// 调用Server端接口 // 调用Server端接口
String str = OkHttps.sync(serverUrl + "/oauth2/client_token") String str = OkHttps.sync(serverUrl + "/oauth2/client_token")
.addBodyPara("grant_type", "client_credentials") .addBodyPara("grant_type", "client_credentials")
@@ -125,7 +127,7 @@ public class SaOAuthClientController {
.getBody() .getBody()
.toString(); .toString();
SoMap so = SoMap.getSoMap().setJsonString(str); SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so); System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败 // code不等于200 代表请求失败
if(so.getInt("code") != 200) { if(so.getInt("code") != 200) {
@@ -145,7 +147,7 @@ public class SaOAuthClientController {
// 根据 Access-Token 置换相关的资源: 获取账号昵称、头像、性别等信息 // 根据 Access-Token 置换相关的资源: 获取账号昵称、头像、性别等信息
@RequestMapping("/getUserinfo") @RequestMapping("/getUserinfo")
public SaResult getUserinfo(String accessToken) { public SaResult getUserinfo(String accessToken) throws JsonProcessingException {
// 调用Server端接口,查询开放的资源 // 调用Server端接口,查询开放的资源
String str = OkHttps.sync(serverUrl + "/oauth2/userinfo") String str = OkHttps.sync(serverUrl + "/oauth2/userinfo")
.addBodyPara("access_token", accessToken) .addBodyPara("access_token", accessToken)
@@ -153,7 +155,7 @@ public class SaOAuthClientController {
.getBody() .getBody()
.toString(); .toString();
SoMap so = SoMap.getSoMap().setJsonString(str); SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so); System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败 // code不等于200 代表请求失败
if(so.getInt("code") != 200) { if(so.getInt("code") != 200) {
@@ -24,7 +24,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
.setClientId("1001") .setClientId("1001")
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee") .setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
.setAllowUrl("*") .setAllowUrl("*")
.setContractScopes(Arrays.asList("userinfo")) .setContractScopes(Arrays.asList("userinfo", "openid"))
.setIsAutoMode(true); .setIsAutoMode(true);
} }
return null; return null;
@@ -73,14 +73,11 @@ public class SaOAuth2DataConverterDefaultImpl implements SaOAuth2DataConverter {
public AccessTokenModel convertCodeToAccessToken(CodeModel cm) { public AccessTokenModel convertCodeToAccessToken(CodeModel cm) {
AccessTokenModel at = new AccessTokenModel(); AccessTokenModel at = new AccessTokenModel();
at.accessToken = SaOAuth2Manager.getDataLoader().randomAccessToken(cm.clientId, cm.loginId, cm.scopes); at.accessToken = SaOAuth2Manager.getDataLoader().randomAccessToken(cm.clientId, cm.loginId, cm.scopes);
// at.refreshToken = randomRefreshToken(cm.clientId, cm.loginId, cm.scope);
at.clientId = cm.clientId; at.clientId = cm.clientId;
at.loginId = cm.loginId; at.loginId = cm.loginId;
at.scopes = cm.scopes; at.scopes = cm.scopes;
at.openid = SaOAuth2Manager.getDataLoader().getOpenid(cm.clientId, cm.loginId);
SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(cm.clientId); SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(cm.clientId);
at.expiresTime = System.currentTimeMillis() + (clientModel.getAccessTokenTimeout() * 1000); at.expiresTime = System.currentTimeMillis() + (clientModel.getAccessTokenTimeout() * 1000);
// at.refreshExpiresTime = System.currentTimeMillis() + (checkClientModel(cm.clientId).getRefreshTokenTimeout() * 1000);
return at; return at;
} }
@@ -18,9 +18,11 @@ package cn.dev33.satoken.oauth2.data.generate;
import cn.dev33.satoken.oauth2.SaOAuth2Manager; import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts;
import cn.dev33.satoken.oauth2.dao.SaOAuth2Dao; import cn.dev33.satoken.oauth2.dao.SaOAuth2Dao;
import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverter;
import cn.dev33.satoken.oauth2.data.model.*; import cn.dev33.satoken.oauth2.data.model.*;
import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy;
import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaFoxUtil;
import java.util.List; import java.util.List;
@@ -68,6 +70,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate {
public AccessTokenModel generateAccessToken(String code) { public AccessTokenModel generateAccessToken(String code) {
SaOAuth2Dao dao = SaOAuth2Manager.getDao(); SaOAuth2Dao dao = SaOAuth2Manager.getDao();
SaOAuth2DataConverter dataConverter = SaOAuth2Manager.getDataConverter();
// 1、先校验 // 1、先校验
CodeModel cm = dao.getCode(code); CodeModel cm = dao.getCode(code);
@@ -78,8 +81,9 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate {
dao.deleteRefreshToken(dao.getRefreshTokenValue(cm.clientId, cm.loginId)); dao.deleteRefreshToken(dao.getRefreshTokenValue(cm.clientId, cm.loginId));
// 3、生成token // 3、生成token
AccessTokenModel at = SaOAuth2Manager.getDataConverter().convertCodeToAccessToken(cm); AccessTokenModel at = dataConverter.convertCodeToAccessToken(cm);
RefreshTokenModel rt = SaOAuth2Manager.getDataConverter().convertAccessTokenToRefreshToken(at); SaOAuth2Strategy.instance.workAccessTokenByScope.accept(at);
RefreshTokenModel rt = dataConverter.convertAccessTokenToRefreshToken(at);
at.refreshToken = rt.refreshToken; at.refreshToken = rt.refreshToken;
at.refreshExpiresTime = rt.expiresTime; at.refreshExpiresTime = rt.expiresTime;
@@ -16,6 +16,7 @@
package cn.dev33.satoken.oauth2.data.model; package cn.dev33.satoken.oauth2.data.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -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.strategy;
import cn.dev33.satoken.oauth2.data.model.AccessTokenModel;
import java.util.function.Consumer;
/**
* 函数式接口:AccessTokenModel 加工
*
* <p> 参数:AccessTokenModel </p>
* <p> 返回:无 </p>
*
* @author click33
* @since 1.35.0
*/
@FunctionalInterface
public interface SaScopeWorkFunction extends Consumer<AccessTokenModel> {
}
@@ -25,6 +25,7 @@ import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.Api;
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.GrantType; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.GrantType;
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.Param; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.Param;
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.ResponseType; import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts.ResponseType;
import cn.dev33.satoken.oauth2.data.generate.SaOAuth2DataGenerate;
import cn.dev33.satoken.oauth2.data.model.*; import cn.dev33.satoken.oauth2.data.model.*;
import cn.dev33.satoken.oauth2.data.model.other.ClientIdAndSecretModel; import cn.dev33.satoken.oauth2.data.model.other.ClientIdAndSecretModel;
import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode; import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode;
@@ -140,6 +141,7 @@ public class SaOAuth2ServerProcessor {
SaRequest req = SaHolder.getRequest(); SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse(); SaResponse res = SaHolder.getResponse();
SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); SaOAuth2Config cfg = SaOAuth2Manager.getConfig();
SaOAuth2DataGenerate dataGenerate = SaOAuth2Manager.getDataGenerate();
// 1、如果尚未登录, 则先去登录 // 1、如果尚未登录, 则先去登录
if( ! getStpLogic().isLogin()) { if( ! getStpLogic().isLogin()) {
@@ -161,18 +163,19 @@ public class SaOAuth2ServerProcessor {
return cfg.confirmView.apply(ra.clientId, ra.scopes); return cfg.confirmView.apply(ra.clientId, ra.scopes);
} }
// 6、判断授权类型 // 6、判断授权类型
// 如果是 授权码式,则:开始重定向授权,下放code // 如果是 授权码式,则:开始重定向授权,下放code
if(ResponseType.code.equals(ra.responseType)) { if(ResponseType.code.equals(ra.responseType)) {
CodeModel codeModel = SaOAuth2Manager.getDataGenerate().generateCode(ra); CodeModel codeModel = dataGenerate.generateCode(ra);
String redirectUri = SaOAuth2Manager.getDataGenerate().buildRedirectUri(ra.redirectUri, codeModel.code, ra.state); String redirectUri = dataGenerate.buildRedirectUri(ra.redirectUri, codeModel.code, ra.state);
return res.redirect(redirectUri); return res.redirect(redirectUri);
} }
// 如果是 隐藏式,则:开始重定向授权,下放 token // 如果是 隐藏式,则:开始重定向授权,下放 token
if(ResponseType.token.equals(ra.responseType)) { if(ResponseType.token.equals(ra.responseType)) {
AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, false); AccessTokenModel at = dataGenerate.generateAccessToken(ra, false);
String redirectUri = SaOAuth2Manager.getDataGenerate().buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state); String redirectUri = dataGenerate.buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state);
return res.redirect(redirectUri); return res.redirect(redirectUri);
} }
@@ -0,0 +1,45 @@
/*
* 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.scope.handler;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.data.model.AccessTokenModel;
import cn.dev33.satoken.oauth2.scope.CommonScope;
/**
* 所有OAuth2 权限处理器的父接口
*
* @author click33
* @since 1.39.0
*/
public class OpenIdScopeHandler implements SaOAuth2ScopeAbstractHandler {
/**
* 获取所要处理的权限
*/
public String getHandlerScope() {
return CommonScope.OPENID;
}
/**
* 所需要执行的方法
*/
public void work(AccessTokenModel at) {
System.out.println("追加 openid " + at.accessToken);
at.openid = SaOAuth2Manager.getDataLoader().getOpenid(at.clientId, at.loginId);
}
}
@@ -0,0 +1,44 @@
/*
* 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.scope.handler;
import cn.dev33.satoken.oauth2.data.model.AccessTokenModel;
/**
* 所有OAuth2 权限处理器的父接口
*
* @author click33
* @since 1.39.0
*/
public interface SaOAuth2ScopeAbstractHandler {
/**
* 获取所要处理的权限
*
* @return /
*/
String getHandlerScope();
/**
* 所需要执行的方法
*
* @param at /
*/
default void work(AccessTokenModel at) {
}
}
@@ -0,0 +1,94 @@
/*
* 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.strategy;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.oauth2.function.strategy.SaScopeWorkFunction;
import cn.dev33.satoken.oauth2.scope.CommonScope;
import cn.dev33.satoken.oauth2.scope.handler.OpenIdScopeHandler;
import cn.dev33.satoken.oauth2.scope.handler.SaOAuth2ScopeAbstractHandler;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Sa-Token OAuth2 相关策略
*
* @author click33
* @since 1.39.0
*/
public final class SaOAuth2Strategy {
private SaOAuth2Strategy() {
registerDefaultScopeHandler();
}
/**
* 全局单例引用
*/
public static final SaOAuth2Strategy instance = new SaOAuth2Strategy();
// ----------------------- 所有策略
/**
* 权限处理器集合
*/
public Map<String, SaOAuth2ScopeAbstractHandler> scopeHandlerMap = new LinkedHashMap<>();
/**
* 注册所有默认的权限处理器
*/
public void registerDefaultScopeHandler() {
scopeHandlerMap.put(CommonScope.OPENID, new OpenIdScopeHandler());
}
/**
* 注册一个权限处理器
*/
public void registerScopeHandler(SaOAuth2ScopeAbstractHandler handler) {
scopeHandlerMap.put(handler.getHandlerScope(), handler);
// TODO 优化日志输出
SaManager.getLog().info("新增权限处理器:" + handler.getHandlerScope());
// SaTokenEventCenter.doRegisterAnnotationHandler(handler);
}
/**
* 移除一个权限处理器
*/
public void removeScopeHandler(String scope) {
scopeHandlerMap.remove(scope);
}
/**
* 根据 scope 信息对一个 AccessTokenModel 进行加工处理
*/
public SaScopeWorkFunction workAccessTokenByScope = (at) -> {
System.out.println("增强:" + at.accessToken);
System.out.println("权限:" + at.scopes);
// 遍历所有的权限处理器,如果此 AccessToken 具有这些权限,则开始加工
if(at.scopes != null && !at.scopes.isEmpty()) {
for (Map.Entry<String, SaOAuth2ScopeAbstractHandler> entry: scopeHandlerMap.entrySet()) {
if(at.scopes.contains(entry.getKey())) {
entry.getValue().work(at);
}
}
}
};
}
@@ -47,16 +47,6 @@ public class SaOAuth2Template {
return SaOAuth2Manager.getDataLoader().getClientModel(clientId); return SaOAuth2Manager.getDataLoader().getClientModel(clientId);
} }
/**
* 根据ClientId 和 LoginId 获取openid
* @param clientId 应用id
* @param loginId 账号id
* @return 此账号在此Client下的openid
*/
public String getOpenid(String clientId, Object loginId) {
return SaOAuth2Manager.getDataLoader().getOpenid(clientId, loginId);
}
// ------------------- 资源校验API // ------------------- 资源校验API
/** /**
* 根据id获取Client信息, 如果Client为空,则抛出异常 * 根据id获取Client信息, 如果Client为空,则抛出异常