新增自定义 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

View File

@ -3,6 +3,8 @@ package com.pj.oauth2;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.ejlchina.okhttps.OkHttps;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pj.utils.SoMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
@ -32,7 +34,7 @@ public class SaOAuthClientController {
// 根据Code码进行登录获取 Access-Token openid
@RequestMapping("/codeLogin")
public SaResult codeLogin(String code) {
public SaResult codeLogin(String code) throws JsonProcessingException {
// 调用Server端接口获取 Access-Token 以及其他信息
String str = OkHttps.sync(serverUrl + "/oauth2/token")
.addBodyPara("grant_type", "authorization_code")
@ -43,7 +45,7 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
@ -61,7 +63,7 @@ public class SaOAuthClientController {
// 根据 Refresh-Token 去刷新 Access-Token
@RequestMapping("/refresh")
public SaResult refresh(String refreshToken) {
public SaResult refresh(String refreshToken) throws JsonProcessingException {
// 调用Server端接口通过 Refresh-Token 刷新出一个新的 Access-Token
String str = OkHttps.sync(serverUrl + "/oauth2/refresh")
.addBodyPara("grant_type", "refresh_token")
@ -72,7 +74,7 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
@ -85,7 +87,7 @@ public class SaOAuthClientController {
// 模式三密码式-授权登录
@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")
.addBodyPara("grant_type", "password")
@ -97,7 +99,7 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
@ -115,7 +117,7 @@ public class SaOAuthClientController {
// 模式四获取应用的 Client-Token
@RequestMapping("/clientToken")
public SaResult clientToken() {
public SaResult clientToken() throws JsonProcessingException {
// 调用Server端接口
String str = OkHttps.sync(serverUrl + "/oauth2/client_token")
.addBodyPara("grant_type", "client_credentials")
@ -125,7 +127,7 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {
@ -145,7 +147,7 @@ public class SaOAuthClientController {
// 根据 Access-Token 置换相关的资源: 获取账号昵称头像性别等信息
@RequestMapping("/getUserinfo")
public SaResult getUserinfo(String accessToken) {
public SaResult getUserinfo(String accessToken) throws JsonProcessingException {
// 调用Server端接口查询开放的资源
String str = OkHttps.sync(serverUrl + "/oauth2/userinfo")
.addBodyPara("access_token", accessToken)
@ -153,7 +155,7 @@ public class SaOAuthClientController {
.getBody()
.toString();
SoMap so = SoMap.getSoMap().setJsonString(str);
System.out.println("返回结果: " + so);
System.out.println("返回结果: " + new ObjectMapper().writeValueAsString(so));
// code不等于200 代表请求失败
if(so.getInt("code") != 200) {

View File

@ -24,7 +24,7 @@ public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
.setClientId("1001")
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
.setAllowUrl("*")
.setContractScopes(Arrays.asList("userinfo"))
.setContractScopes(Arrays.asList("userinfo", "openid"))
.setIsAutoMode(true);
}
return null;

View File

@ -73,14 +73,11 @@ public class SaOAuth2DataConverterDefaultImpl implements SaOAuth2DataConverter {
public AccessTokenModel convertCodeToAccessToken(CodeModel cm) {
AccessTokenModel at = new AccessTokenModel();
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.loginId = cm.loginId;
at.scopes = cm.scopes;
at.openid = SaOAuth2Manager.getDataLoader().getOpenid(cm.clientId, cm.loginId);
SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(cm.clientId);
at.expiresTime = System.currentTimeMillis() + (clientModel.getAccessTokenTimeout() * 1000);
// at.refreshExpiresTime = System.currentTimeMillis() + (checkClientModel(cm.clientId).getRefreshTokenTimeout() * 1000);
return at;
}

View File

@ -18,9 +18,11 @@ package cn.dev33.satoken.oauth2.data.generate;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts;
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.error.SaOAuth2ErrorCode;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.List;
@ -68,6 +70,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate {
public AccessTokenModel generateAccessToken(String code) {
SaOAuth2Dao dao = SaOAuth2Manager.getDao();
SaOAuth2DataConverter dataConverter = SaOAuth2Manager.getDataConverter();
// 1先校验
CodeModel cm = dao.getCode(code);
@ -78,8 +81,9 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate {
dao.deleteRefreshToken(dao.getRefreshTokenValue(cm.clientId, cm.loginId));
// 3生成token
AccessTokenModel at = SaOAuth2Manager.getDataConverter().convertCodeToAccessToken(cm);
RefreshTokenModel rt = SaOAuth2Manager.getDataConverter().convertAccessTokenToRefreshToken(at);
AccessTokenModel at = dataConverter.convertCodeToAccessToken(cm);
SaOAuth2Strategy.instance.workAccessTokenByScope.accept(at);
RefreshTokenModel rt = dataConverter.convertAccessTokenToRefreshToken(at);
at.refreshToken = rt.refreshToken;
at.refreshExpiresTime = rt.expiresTime;

View File

@ -16,6 +16,7 @@
package cn.dev33.satoken.oauth2.data.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.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.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> {
}

View File

@ -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.Param;
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.other.ClientIdAndSecretModel;
import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode;
@ -140,6 +141,7 @@ public class SaOAuth2ServerProcessor {
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaOAuth2Config cfg = SaOAuth2Manager.getConfig();
SaOAuth2DataGenerate dataGenerate = SaOAuth2Manager.getDataGenerate();
// 1如果尚未登录, 则先去登录
if( ! getStpLogic().isLogin()) {
@ -161,18 +163,19 @@ public class SaOAuth2ServerProcessor {
return cfg.confirmView.apply(ra.clientId, ra.scopes);
}
// 6判断授权类型
// 如果是 授权码式开始重定向授权下放code
if(ResponseType.code.equals(ra.responseType)) {
CodeModel codeModel = SaOAuth2Manager.getDataGenerate().generateCode(ra);
String redirectUri = SaOAuth2Manager.getDataGenerate().buildRedirectUri(ra.redirectUri, codeModel.code, ra.state);
CodeModel codeModel = dataGenerate.generateCode(ra);
String redirectUri = dataGenerate.buildRedirectUri(ra.redirectUri, codeModel.code, ra.state);
return res.redirect(redirectUri);
}
// 如果是 隐藏式开始重定向授权下放 token
if(ResponseType.token.equals(ra.responseType)) {
AccessTokenModel at = SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, false);
String redirectUri = SaOAuth2Manager.getDataGenerate().buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state);
AccessTokenModel at = dataGenerate.generateAccessToken(ra, false);
String redirectUri = dataGenerate.buildImplicitRedirectUri(ra.redirectUri, at.accessToken, ra.state);
return res.redirect(redirectUri);
}

View File

@ -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);
}
}

View File

@ -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) {
}
}

View File

@ -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);
}
}
}
};
}

View File

@ -47,16 +47,6 @@ public class SaOAuth2Template {
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
/**
* 根据id获取Client信息, 如果Client为空则抛出异常