1
0
mirror of synced 2026-05-22 22:53:16 +00:00

整体重构 sa-token-sso 模块,将 server 端和 client 端代码拆分

This commit is contained in:
click33
2024-04-30 08:34:30 +08:00
parent 32157cc389
commit 08659f1fa8
49 changed files with 2441 additions and 1974 deletions
@@ -1,8 +1,8 @@
package com.pj.h5;
import cn.dev33.satoken.sso.SaSsoConsts;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.sso.template.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
@@ -1,8 +1,8 @@
package com.pj.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoOfRedis;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.dtflys.forest.Forest;
@@ -27,15 +27,15 @@ public class SsoConfig {
// 配置SSO相关参数
@Bean
public void configSso(SaSsoConfig sso) { //SaSsoConfig 已自动构建
public void configSso(SaSsoServerConfig ssoServer) { //SaSsoConfig 已自动构建
// 配置:未登录时返回的View
sso.notLoginView = () -> {
ssoServer.notLoginView = () -> {
return new ModelAndView("sa-login.html");
};
// 配置:登录处理函数
sso.doLoginHandle = (name, pwd) -> {
ssoServer.doLoginHandle = (name, pwd) -> {
// 此处仅做模拟登录,真实环境应该查询数据进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
@@ -45,7 +45,7 @@ public class SsoConfig {
};
// 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉)
sso.sendHttp = url -> {
ssoServer.sendHttp = url -> {
try {
// 发起 http 请求
System.out.println("------ 发起请求:" + url);
@@ -1,7 +1,7 @@
package com.pj.sso;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Mapping;
@@ -22,6 +22,6 @@ public class SsoServerController {
*/
@Mapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.serverDister();
return SaSsoServerProcessor.instance.dister();
}
}
@@ -10,7 +10,7 @@ sa-token:
# domain: stp.com
# ------- SSO-模式二相关配置
sso:
sso-server:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址
@@ -22,7 +22,8 @@ sa-token:
# 是否打开模式三
isHttp: true
# 接口调用秘钥(用于SSO模式三的单点注销功能)
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
sign:
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明)
sa-token: #名字可以随意取
@@ -21,8 +21,8 @@ public class SsoClientController implements Render {
@Produces(MimeType.TEXT_HTML_VALUE)
@Mapping("/")
public String index() {
String authUrl = SaSsoManager.getConfig().splicingAuthUrl();
String solUrl = SaSsoManager.getConfig().splicingSloUrl();
String authUrl = SaSsoManager.getClientConfig().splicingAuthUrl();
String solUrl = SaSsoManager.getClientConfig().splicingSloUrl();
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='" + authUrl + "?mode=simple&redirect=' + encodeURIComponent(location.href);\">登录</a> " +
@@ -5,7 +5,7 @@ server:
# Sa-Token 配置
sa-token:
# SSO-相关配置
sso:
sso-client:
# SSO-Server端-单点登录授权地址
auth-url: http://sso.stp.com:9000/sso/auth
# SSO-Server端-单点注销地址
@@ -1,7 +1,7 @@
package com.pj.h5;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.noear.solon.annotation.Controller;
@@ -34,7 +34,7 @@ public class H5Controller implements Render {
// 根据ticket进行登录
@Mapping("/sso/doLoginByTicket")
public SaResult doLoginByTicket(String ticket) {
Object loginId = SaSsoProcessor.instance.checkTicket(ticket, "/sso/doLoginByTicket");
Object loginId = SaSsoClientProcessor.instance.checkTicketByMode2Or3(ticket, "/sso/doLoginByTicket");
if(loginId != null) {
StpUtil.login(loginId);
return SaResult.data(StpUtil.getTokenValue());
@@ -1,7 +1,7 @@
package com.pj.sso;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.noear.solon.annotation.Controller;
@@ -37,7 +37,7 @@ public class SsoClientController implements Render {
*/
@Mapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.clientDister();
return SaSsoClientProcessor.instance.dister();
}
// 全局异常拦截并转换
@@ -5,7 +5,7 @@ server:
# sa-token配置
sa-token:
# SSO-相关配置
sso:
sso-client:
# SSO-Server端 统一认证地址
auth-url: http://sa-sso-server.com:9000/sso/auth
# auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
@@ -1,10 +1,9 @@
package com.pj.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.sso.SaSsoTemplate;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoClientTemplate;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Condition;
import org.noear.solon.annotation.Configuration;
@@ -25,7 +24,7 @@ public class SaSsoAutoConfigure {
* 获取 SSO 配置Bean
* */
@Bean
public SaSsoConfig getConfig(@Inject(value = "${sa-token.sso}",required = false) SaSsoConfig ssoConfig) {
public SaSsoClientConfig getConfig(@Inject(value = "${sa-token.sso-client}",required = false) SaSsoClientConfig ssoConfig) {
return ssoConfig;
}
@@ -35,18 +34,17 @@ public class SaSsoAutoConfigure {
* @param saSsoConfig 配置对象
*/
@Bean
public void setSaSsoConfig(@Inject(required = false) SaSsoConfig saSsoConfig) {
SaSsoManager.setConfig(saSsoConfig);
public void setSaSsoConfig(@Inject(required = false) SaSsoClientConfig saSsoConfig) {
SaSsoManager.setClientConfig(saSsoConfig);
}
/**
* 注入 Sa-Token-SSO 单点登录模块 Bean
*
* @param ssoTemplate saSsoTemplate对象
* @param ssoClientTemplate ssoClientTemplate对象
*/
@Bean
public void setSaSsoTemplate(@Inject(required = false) SaSsoTemplate ssoTemplate) {
SaSsoUtil.ssoTemplate = ssoTemplate;
SaSsoProcessor.instance.ssoTemplate = ssoTemplate;
public void setSaSsoClientTemplate(@Inject(required = false) SaSsoClientTemplate ssoClientTemplate) {
SaSsoClientProcessor.instance.ssoClientTemplate = ssoClientTemplate;
}
}
@@ -1,7 +1,7 @@
package com.pj.sso;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.noear.solon.annotation.Controller;
@@ -11,6 +11,9 @@ import org.noear.solon.boot.web.MimeType;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Render;
import java.util.HashMap;
import java.util.Map;
/**
* Sa-Token-SSO Client端 Controller
* @author click33
@@ -37,15 +40,21 @@ public class SsoClientController implements Render {
*/
@Mapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.clientDister();
return SaSsoClientProcessor.instance.dister();
}
// 查询我的账号信息
@Mapping("/sso/myinfo")
public Object myinfo() {
Object userinfo = SaSsoUtil.getUserinfo(StpUtil.getLoginId());
System.out.println("--------info" + userinfo);
return userinfo;
@Mapping("/sso/myInfo")
public Object myInfo() {
// 组织请求参数
Map<String, Object> map = new HashMap<>();
map.put("apiType", "userinfo");
map.put("loginId", StpUtil.getLoginId());
// 发起请求
Object resData = SaSsoUtil.getData(map);
System.out.println("sso-server 返回的信息:" + resData);
return resData;
}
// 全局异常拦截并转换
@@ -1,6 +1,6 @@
package com.pj.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
import com.dtflys.forest.Forest;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Configuration;
@@ -12,9 +12,9 @@ import org.noear.solon.annotation.Configuration;
public class SsoConfig {
// 配置SSO相关参数
@Bean
private void configSso(SaSsoConfig sso) {
private void configSso(SaSsoClientConfig ssoClient) {
// 配置Http请求处理器
sso.sendHttp = url -> {
ssoClient.sendHttp = url -> {
System.out.println("------ 发起请求:" + url);
return Forest.get(url).executeAsString();
};
@@ -5,7 +5,7 @@ server:
# sa-token配置
sa-token:
# SSO-相关配置
sso:
sso-client:
# SSO-Server端 统一认证地址
auth-url: http://sa-sso-server.com:9000/sso/auth
# 使用Http请求校验ticket
@@ -4,8 +4,8 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.sso.SaSsoConsts;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.sso.template.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
@@ -1,9 +1,9 @@
package com.pj.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.sign.SaSignUtil;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.dtflys.forest.Forest;
@@ -20,7 +20,7 @@ import org.springframework.web.servlet.ModelAndView;
@RestController
public class SsoServerController {
/*
/**
* SSO-Server端:处理所有SSO相关请求
* http://{host}:{port}/sso/auth -- 单点登录授权地址,接受参数:redirect=授权重定向地址
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口,接受参数:name、pwd
@@ -29,20 +29,20 @@ public class SsoServerController {
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.serverDister();
return SaSsoServerProcessor.instance.dister();
}
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoConfig sso) {
private void configSso(SaSsoServerConfig ssoServer) {
// 配置:未登录时返回的View
sso.notLoginView = () -> {
ssoServer.notLoginView = () -> {
return new ModelAndView("sa-login.html");
};
// 配置:登录处理函数
sso.doLoginHandle = (name, pwd) -> {
ssoServer.doLoginHandle = (name, pwd) -> {
// 此处仅做模拟登录,真实环境应该查询数据进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
@@ -52,11 +52,12 @@ public class SsoServerController {
};
// 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉)
sso.sendHttp = url -> {
ssoServer.sendHttp = url -> {
try {
// 发起 http 请求
System.out.println("------ 发起请求:" + url);
return Forest.get(url).executeAsString();
String resStr = Forest.get(url).executeAsString();
System.out.println("------ 请求结果:" + resStr);
return resStr;
} catch (Exception e) {
e.printStackTrace();
return null;
@@ -10,7 +10,7 @@ sa-token:
# domain: stp.com
# ------- SSO-模式二相关配置
sso:
sso-server:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址
@@ -1,5 +1,6 @@
package com.pj;
import cn.dev33.satoken.sso.SaSsoManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -13,7 +14,15 @@ public class SaSso1ClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaSso1ClientApplication.class, args);
System.out.println("\nSa-Token SSO模式一 Client端启动成功");
System.out.println();
System.out.println("---------------------- Sa-Token SSO 模式一 Client 端启动成功 ----------------------");
System.out.println("配置信息:" + SaSsoManager.getClientConfig());
System.out.println("测试访问应用端一: http://s1.stp.com:9001");
System.out.println("测试访问应用端二: http://s2.stp.com:9001");
System.out.println("测试访问应用端三: http://s3.stp.com:9001");
System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456");
System.out.println();
}
}
@@ -1,12 +1,11 @@
package com.pj.sso;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Sa-Token-SSO Client端 Controller
@@ -18,8 +17,8 @@ public class SsoClientController {
// SSO-Client端:首页
@RequestMapping("/")
public String index() {
String authUrl = SaSsoManager.getConfig().splicingAuthUrl();
String solUrl = SaSsoManager.getConfig().splicingSloUrl();
String authUrl = SaSsoManager.getClientConfig().splicingAuthUrl();
String solUrl = SaSsoManager.getClientConfig().splicingSloUrl();
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='" + authUrl + "?mode=simple&redirect=' + encodeURIComponent(location.href);\">登录</a> " +
@@ -5,11 +5,9 @@ server:
# Sa-Token 配置
sa-token:
# SSO-相关配置
sso:
# SSO-Server端-单点登录授权地址
auth-url: http://sso.stp.com:9000/sso/auth
# SSO-Server端-单点注销地址
slo-url: http://sso.stp.com:9000/sso/signout
sso-client:
# SSO-Server端主机地址
server-url: http://sso.stp.com:9000
# 配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis
alone-redis:
@@ -1,5 +1,6 @@
package com.pj;
import cn.dev33.satoken.sso.SaSsoManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -8,7 +9,15 @@ public class SaSso2ClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaSso2ClientApplication.class, args);
System.out.println("\nSa-Token SSO模式二 Client端启动成功");
System.out.println();
System.out.println("---------------------- Sa-Token SSO 模式二 Client 端启动成功 ----------------------");
System.out.println("配置信息:" + SaSsoManager.getClientConfig());
System.out.println("测试访问应用端一: http://sa-sso-client1.com:9001");
System.out.println("测试访问应用端二: http://sa-sso-client2.com:9001");
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9001");
System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456");
System.out.println();
}
}
@@ -1,14 +1,13 @@
package com.pj.h5;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 前后台分离架构下集成SSO所需的代码 (SSO-Client端)
* <p>(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)</p>
@@ -34,7 +33,7 @@ public class H5Controller {
// 根据ticket进行登录
@RequestMapping("/sso/doLoginByTicket")
public SaResult doLoginByTicket(String ticket) {
Object loginId = SaSsoProcessor.instance.checkTicket(ticket, "/sso/doLoginByTicket");
Object loginId = SaSsoClientProcessor.instance.checkTicketByMode2Or3(ticket, "/sso/doLoginByTicket");
if(loginId != null) {
StpUtil.login(loginId);
return SaResult.data(StpUtil.getTokenValue());
@@ -1,13 +1,12 @@
package com.pj.sso;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token-SSO Client端 Controller
* @author click33
@@ -33,7 +32,7 @@ public class SsoClientController {
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.clientDister();
return SaSsoClientProcessor.instance.dister();
}
// 全局异常拦截
@@ -5,12 +5,11 @@ server:
# sa-token配置
sa-token:
# SSO-相关配置
sso:
sso-client:
# SSO-Server端 统一认证地址
auth-url: http://sa-sso-server.com:9000/sso/auth
server-url: http://sa-sso-server.com:9000
# 前后端分离时用这个
# auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
# 是否打开单点注销接口
is-slo: true
# 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis
alone-redis:
@@ -1,5 +1,6 @@
package com.pj;
import cn.dev33.satoken.sso.SaSsoManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -8,7 +9,15 @@ public class SaSso3ClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaSso3ClientApplication.class, args);
System.out.println("\nSa-Token SSO模式三 Client端启动成功");
System.out.println();
System.out.println("---------------------- Sa-Token SSO 模式三 Client 端启动成功 ----------------------");
System.out.println("配置信息:" + SaSsoManager.getClientConfig());
System.out.println("测试访问应用端一: http://sa-sso-client1.com:9001");
System.out.println("测试访问应用端二: http://sa-sso-client2.com:9001");
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9001");
System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456");
System.out.println();
}
}
@@ -1,8 +1,8 @@
package com.pj.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.dtflys.forest.Forest;
@@ -39,16 +39,18 @@ public class SsoClientController {
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.clientDister();
return SaSsoClientProcessor.instance.dister();
}
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoConfig sso) {
private void configSso(SaSsoClientConfig ssoClient) {
// 配置Http请求处理器
sso.sendHttp = url -> {
ssoClient.sendHttp = url -> {
System.out.println("------ 发起请求:" + url);
return Forest.get(url).executeAsString();
String resStr = Forest.get(url).executeAsString();
System.out.println("------ 请求结果:" + resStr);
return resStr;
};
}
@@ -4,18 +4,13 @@ server:
# sa-token配置
sa-token:
# SSO-相关配置
sso:
# SSO-Server端 统一认证地址
auth-url: http://sa-sso-server.com:9000/sso/auth
# sso-client 相关配置
sso-client:
# sso-server 端主机地址
server-url: http://sa-sso-server.com:9000
# 使用 Http 请求校验ticket (模式三)
is-http: true
# SSO-Server端 ticket校验地址
check-ticket-url: http://sa-sso-server.com:9000/sso/checkTicket
# 单点注销地址
slo-url: http://sa-sso-server.com:9000/sso/signout
# 查询数据地址
get-data-url: http://sa-sso-server.com:9000/sso/getData
sign:
# API 接口调用秘钥
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
@@ -1,585 +0,0 @@
/*
* 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.config;
import java.io.Serializable;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token SSO 单点登录模块 配置类 Model
*
* @author click33
* @since 1.30.0
*/
public class SaSsoConfig implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
// ----------------- Server端相关配置
/**
* 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响)
*/
public String mode = "";
/**
* Ticket有效期 (单位: 秒)
*/
public long ticketTimeout = 60 * 5;
/**
* 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
*/
public String allowUrl = "*";
/**
* 是否打开单点注销功能
*/
public Boolean isSlo = true;
/**
* 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
*/
public Boolean isHttp = false;
// ----------------- Client端相关配置
// /**
// * 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响)
// */
// public String mode = ""; // 同Server端,不再重复声明
/**
* 当前 Client 名称标识,用于和 ticket 码的互相锁定
*/
public String client;
/**
* 配置 Server 端单点登录授权地址
*/
public String authUrl = "/sso/auth";
// /**
// * 是否打开单点注销功能
// */
// public Boolean isSlo = true; // 同Server端,不再重复声明
// /**
// * 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
// */
// public Boolean isHttp = false; // 同Server端,不再重复声明
// /**
// * 接口调用秘钥 (用于SSO模式三单点注销的接口通信身份校验)
// */
// public String secretkey; // 同Server端,不再重复声明
/**
* 配置 Server 端的 ticket 校验地址
*/
public String checkTicketUrl = "/sso/checkTicket";
/**
* 配置 Server 端查询数据 getData 地址
*/
public String getDataUrl = "/sso/getData";
/**
* 配置 Server 端查询 userinfo 地址
*/
public String userinfoUrl = "/sso/userinfo";
/**
* 配置 Server 端单点注销地址
*/
public String sloUrl = "/sso/signout";
/**
* 配置当前 Client 端的登录地址(为空时自动获取)
*/
public String currSsoLogin;
/**
* 配置当前 Client 端的单点注销回调URL (为空时自动获取)
*/
public String currSsoLogoutCall;
/**
* 配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置
*/
public String serverUrl;
/**
* 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*/
public Boolean isCheckSign = true;
/**
* 获取 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响)
*
* @return /
*/
public String getMode() {
return this.mode;
}
/**
* 设置 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响)
*
* @param mode /
*/
public void setMode(String mode) {
this.mode = mode;
}
/**
* @return Ticket有效期 (单位: 秒)
*/
public long getTicketTimeout() {
return ticketTimeout;
}
/**
* @param ticketTimeout Ticket有效期 (单位: 秒)
* @return 对象自身
*/
public SaSsoConfig setTicketTimeout(long ticketTimeout) {
this.ticketTimeout = ticketTimeout;
return this;
}
/**
* @return 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
*/
public String getAllowUrl() {
return allowUrl;
}
/**
* @param allowUrl 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
* @return 对象自身
*/
public SaSsoConfig setAllowUrl(String allowUrl) {
this.allowUrl = allowUrl;
return this;
}
/**
* @return 是否打开单点注销功能
*/
public Boolean getIsSlo() {
return isSlo;
}
/**
* @param isSlo 是否打开单点注销功能
* @return 对象自身
*/
public SaSsoConfig setIsSlo(Boolean isSlo) {
this.isSlo = isSlo;
return this;
}
/**
* @return isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
*/
public Boolean getIsHttp() {
return isHttp;
}
/**
* @param isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
* @return 对象自身
*/
public SaSsoConfig setIsHttp(Boolean isHttp) {
this.isHttp = isHttp;
return this;
}
/**
* @return 当前 Client 名称标识,用于和 ticket 码的互相锁定
*/
public String getClient() {
return client;
}
/**
* @param client 当前 Client 名称标识,用于和 ticket 码的互相锁定
*/
public SaSsoConfig setClient(String client) {
this.client = client;
return this;
}
/**
* @return 配置的 Server 端单点登录授权地址
*/
public String getAuthUrl() {
return authUrl;
}
/**
* @param authUrl 配置 Server 端单点登录授权地址
* @return 对象自身
*/
public SaSsoConfig setAuthUrl(String authUrl) {
this.authUrl = authUrl;
return this;
}
/**
* @return 配置的 Server 端的 ticket 校验地址
*/
public String getCheckTicketUrl() {
return checkTicketUrl;
}
/**
* @param checkTicketUrl 配置 Server 端的 ticket 校验地址
* @return 对象自身
*/
public SaSsoConfig setCheckTicketUrl(String checkTicketUrl) {
this.checkTicketUrl = checkTicketUrl;
return this;
}
/**
* @return Server 端查询数据 getData 地址
*/
public String getGetDataUrl() {
return getDataUrl;
}
/**
* @param getDataUrl 配置 Server 端查询数据 getData 地址
* @return 对象自身
*/
public SaSsoConfig setGetDataUrl(String getDataUrl) {
this.getDataUrl = getDataUrl;
return this;
}
/**
* @return 配置的 Server 端查询 userinfo 地址
*/
public String getUserinfoUrl() {
return userinfoUrl;
}
/**
* @param userinfoUrl 配置 Server 端查询 userinfo 地址
* @return 对象自身
*/
public SaSsoConfig setUserinfoUrl(String userinfoUrl) {
this.userinfoUrl = userinfoUrl;
return this;
}
/**
* @return 配置 Server 端单点注销地址
*/
public String getSloUrl() {
return sloUrl;
}
/**
* @param sloUrl 配置 Server 端单点注销地址
* @return 对象自身
*/
public SaSsoConfig setSloUrl(String sloUrl) {
this.sloUrl = sloUrl;
return this;
}
/**
* @return 配置当前 Client 端的登录地址(为空时自动获取)
*/
public String getCurrSsoLogin() {
return currSsoLogin;
}
/**
* @param currSsoLogin 配置当前 Client 端的登录地址(为空时自动获取)
* @return 对象自身
*/
public SaSsoConfig setCurrSsoLogin(String currSsoLogin) {
this.currSsoLogin = currSsoLogin;
return this;
}
/**
* @return 配置当前 Client 端的单点注销回调URL (为空时自动获取)
*/
public String getCurrSsoLogoutCall() {
return currSsoLogoutCall;
}
/**
* @param currSsoLogoutCall 配置当前 Client 端的单点注销回调URL (为空时自动获取)
* @return 对象自身
*/
public SaSsoConfig setCurrSsoLogoutCall(String currSsoLogoutCall) {
this.currSsoLogoutCall = currSsoLogoutCall;
return this;
}
/**
* @return 配置的 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置
*/
public String getServerUrl() {
return serverUrl;
}
/**
* @param serverUrl 配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置
* @return 对象自身
*/
public SaSsoConfig setServerUrl(String serverUrl) {
this.serverUrl = serverUrl;
return this;
}
/**
* 获取 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*
* @return isCheckSign 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*/
public Boolean getIsCheckSign() {
return this.isCheckSign;
}
/**
* 设置 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*
* @param isCheckSign 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*/
public SaSsoConfig setIsCheckSign(Boolean isCheckSign) {
this.isCheckSign = isCheckSign;
return this;
}
@Override
public String toString() {
return "SaSsoConfig ["
+ "mode=" + mode
+ ", ticketTimeout=" + ticketTimeout
+ ", allowUrl=" + allowUrl
+ ", isSlo=" + isSlo
+ ", isHttp=" + isHttp
+ ", client=" + client
+ ", authUrl=" + authUrl
+ ", checkTicketUrl=" + checkTicketUrl
+ ", getDataUrl=" + getDataUrl
+ ", userinfoUrl=" + userinfoUrl
+ ", sloUrl=" + sloUrl
+ ", currSsoLogin=" + currSsoLogin
+ ", currSsoLogoutCall=" + currSsoLogoutCall
+ ", serverUrl=" + serverUrl
+ ", isCheckSign=" + isCheckSign
+ "]";
}
// 额外添加的一些函数
/**
* @return 获取拼接urlServer 端单点登录授权地址
*/
public String splicingAuthUrl() {
return SaFoxUtil.spliceTwoUrl(getServerUrl(), getAuthUrl());
}
/**
* @return 获取拼接urlServer 端的 ticket 校验地址
*/
public String splicingCheckTicketUrl() {
return SaFoxUtil.spliceTwoUrl(getServerUrl(), getCheckTicketUrl());
}
/**
* @return 获取拼接urlServer 端查询数据 getData 地址
*/
public String splicingGetDataUrl() {
return SaFoxUtil.spliceTwoUrl(getServerUrl(), getGetDataUrl());
}
/**
* @return 获取拼接urlServer 端查询 userinfo 地址
*/
public String splicingUserinfoUrl() {
return SaFoxUtil.spliceTwoUrl(getServerUrl(), getUserinfoUrl());
}
/**
* @return 获取拼接urlServer 端单点注销地址
*/
public String splicingSloUrl() {
return SaFoxUtil.spliceTwoUrl(getServerUrl(), getSloUrl());
}
/**
* 以数组形式写入允许的授权回调地址
* @param url 所有集合
* @return 对象自身
*/
public SaSsoConfig setAllow(String ...url) {
this.allowUrl = SaFoxUtil.arrayJoin(url);
return this;
}
// -------------------- SaSsoHandle 所有回调函数 --------------------
/**
* SSO-Server端:未登录时返回的View
*/
public Supplier<Object> notLoginView = () -> {
return "当前会话在SSO-Server认证中心尚未登录(当前未配置登录视图)";
};
/**
* SSO-Server端:登录函数
*/
public BiFunction<String, String, Object> doLoginHandle = (name, pwd) -> {
return SaResult.error();
};
/**
* SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用)
* <p> 参数:loginId, back
* <p> 返回值:返回给前端的值
*/
public BiFunction<Object, String, Object> ticketResultHandle = null;
/**
* SSO-Client端:发送Http请求的处理函数
*/
public Function<String, String> sendHttp = url -> {
throw new SaSsoException("请配置 Http 请求处理器").setCode(SaSsoErrorCode.CODE_30010);
};
// -------------------- 废弃方法 --------------------
/**
* <h2> 属性为 public,请直接访问 </h2>
* @param notLoginView SSO-Server端:未登录时返回的View
* @return 对象自身
*/
@Deprecated
public SaSsoConfig setNotLoginView(Supplier<Object> notLoginView) {
this.notLoginView = notLoginView;
return this;
}
/**
* <h2> 属性为 public,请直接访问 </h2>
* @return 函数 SSO-Server端:未登录时返回的View
*/
@Deprecated
public Supplier<Object> getNotLoginView() {
return notLoginView;
}
/**
* <h2> 属性为 public,请直接访问 </h2>
* @param doLoginHandle SSO-Server端:登录函数
* @return 对象自身
*/
@Deprecated
public SaSsoConfig setDoLoginHandle(BiFunction<String, String, Object> doLoginHandle) {
this.doLoginHandle = doLoginHandle;
return this;
}
/**
* <h2> 属性为 public,请直接访问 </h2>
* @return 函数 SSO-Server端:登录函数
*/
@Deprecated
public BiFunction<String, String, Object> getDoLoginHandle() {
return doLoginHandle;
}
/**
* <h2> 属性为 public,请直接访问 </h2>
* @param ticketResultHandle SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用)
* @return 对象自身
*/
@Deprecated
public SaSsoConfig setTicketResultHandle(BiFunction<Object, String, Object> ticketResultHandle) {
this.ticketResultHandle = ticketResultHandle;
return this;
}
/**
* <h2> 属性为 public,请直接访问 </h2>
* @return 函数 SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用)
*/
@Deprecated
public BiFunction<Object, String, Object> getTicketResultHandle() {
return ticketResultHandle;
}
/**
* <h2> 属性为 public,请直接访问 </h2>
* @param sendHttp SSO-Client端:发送Http请求的处理函数
* @return 对象自身
*/
@Deprecated
public SaSsoConfig setSendHttp(Function<String, String> sendHttp) {
this.sendHttp = sendHttp;
return this;
}
/**
* <h2> 属性为 public,请直接访问 </h2>
* @return 函数 SSO-Client端:发送Http请求的处理函数
*/
@Deprecated
public Function<String, String> getSendHttp() {
return sendHttp;
}
/**
* @return 配置当前 Client 端的单点注销回调URL (为空时自动获取)
*/
public String getSsoLogoutCall() {
return currSsoLogoutCall;
}
/**
* @param ssoLogoutCall 配置当前 Client 端的单点注销回调URL (为空时自动获取)
* @return 对象自身
*/
public SaSsoConfig setSsoLogoutCall(String ssoLogoutCall) {
this.currSsoLogoutCall = ssoLogoutCall;
return this;
}
}
@@ -15,7 +15,8 @@
*/
package cn.dev33.satoken.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
/**
* Sa-Token-SSO 模块 总控类
@@ -26,29 +27,62 @@ import cn.dev33.satoken.config.SaSsoConfig;
public class SaSsoManager {
/**
* Sso 配置 Bean
* Sso Server 端 配置 Bean
*/
private volatile static SaSsoConfig config;
public static SaSsoConfig getConfig() {
if (config == null) {
private volatile static SaSsoServerConfig serverConfig;
public static SaSsoServerConfig getServerConfig() {
if (serverConfig == null) {
synchronized (SaSsoManager.class) {
if (config == null) {
setConfig(new SaSsoConfig());
if (serverConfig == null) {
setServerConfig(new SaSsoServerConfig());
}
}
}
return config;
return serverConfig;
}
public static void setConfig(SaSsoConfig config) {
SaSsoManager.config = config;
public static void setServerConfig(SaSsoServerConfig serverConfig) {
SaSsoManager.serverConfig = serverConfig;
// 如果配置了 is-check-sign=false,则打印一条警告日志
if ( ! config.getIsCheckSign()) {
System.err.println("-----------------------------------------------------------------------------");
System.err.println("警告信息:");
System.err.println("当前配置项 sa-token.sso.is-check-sign=false 代表跳过 SSO 参数签名校验");
System.err.println("此模式仅为方便本地调试使用,生产环境下请务必配置为 true (配置项默认为true)");
System.err.println("-----------------------------------------------------------------------------");
if ( ! serverConfig.getIsCheckSign()) {
printNoCheckSignWarningByStartup();
}
}
/**
* Sso Client 端 配置 Bean
*/
private volatile static SaSsoClientConfig clientConfig;
public static SaSsoClientConfig getClientConfig() {
if (clientConfig == null) {
synchronized (SaSsoManager.class) {
if (clientConfig == null) {
setClientConfig(new SaSsoClientConfig());
}
}
}
return clientConfig;
}
public static void setClientConfig(SaSsoClientConfig clientConfig) {
SaSsoManager.clientConfig = clientConfig;
// 如果配置了 is-check-sign=false,则打印一条警告日志
if ( ! clientConfig.getIsCheckSign()) {
printNoCheckSignWarningByStartup();
}
}
// 在启动时检测到 sa-token.sso.is-check-sign=false 时,输出警告信息
public static void printNoCheckSignWarningByStartup() {
System.err.println("-----------------------------------------------------------------------------");
System.err.println("警告信息:");
System.err.println("当前配置项 sa-token.sso.is-check-sign=false 代表跳过 SSO 参数签名校验");
System.err.println("此模式仅为方便本地调试使用,生产环境下请务必配置为 true (配置项默认为true)");
System.err.println("-----------------------------------------------------------------------------");
}
// 在运行时检测到 sa-token.sso.is-check-sign=false 时,输出警告信息
public static void printNoCheckSignWarningByRuntime() {
System.err.println("警告信息:当前配置项 sa-token.sso.is-check-sign=false 已跳过参数签名校验," +
"此模式仅为方便本地调试使用,生产环境下请务必配置为 true (配置项默认为true)");
}
}
@@ -1,511 +0,0 @@
/*
* 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.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.name.ApiName;
import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* SSO 请求处理器
*
* @author click33
* @since 1.32.0
*/
public class SaSsoProcessor {
/**
* 底层 SaSsoTemplate 对象
*/
public SaSsoTemplate ssoTemplate = SaSsoUtil.ssoTemplate;
// ----------- SSO-Server 端路由分发 -----------
/**
* 分发 Server 端所有请求
* @return 处理结果
*/
public Object serverDister() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoConfig cfg = SaSsoManager.getConfig();
ApiName apiName = ssoTemplate.apiName;
// ------------------ 路由分发 ------------------
// ---------- SSO-Server端:授权地址
if(req.isPath(apiName.ssoAuth)) {
return ssoAuth();
}
// ---------- SSO-Server端:RestAPI 登录接口
if(req.isPath(apiName.ssoDoLogin)) {
return ssoDoLogin();
}
// ---------- SSO-Server端:校验ticket 获取账号id
if(req.isPath(apiName.ssoCheckTicket) && cfg.getIsHttp()) {
return ssoCheckTicket();
}
// ---------- SSO-Server端:单点注销
if(req.isPath(apiName.ssoSignout)) {
return ssoSignout();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Server端:授权地址
* @return 处理结果
*/
public Object ssoAuth() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaSsoConfig cfg = SaSsoManager.getConfig();
StpLogic stpLogic = ssoTemplate.getStpLogic();
ParamName paramName = ssoTemplate.paramName;
// ---------- 此处有两种情况分开处理:
// ---- 情况1:在SSO认证中心尚未登录,需要先去登录
if( ! stpLogic.isLogin()) {
return cfg.notLoginView.get();
}
// ---- 情况2:在SSO认证中心已经登录,需要重定向回 Client 端,而这又分为两种方式:
String mode = req.getParam(paramName.mode, "");
// 方式1:直接重定向回Client端 (mode=simple)
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
String redirect = req.getParam(paramName.redirect);
ssoTemplate.checkRedirectUrl(redirect);
return res.redirect(redirect);
} else {
// 方式2:带着ticket参数重定向回Client端 (mode=ticket)
String redirectUrl = ssoTemplate.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(paramName.client), req.getParam(paramName.redirect));
return res.redirect(redirectUrl);
}
}
/**
* SSO-Server端:RestAPI 登录接口
* @return 处理结果
*/
public Object ssoDoLogin() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoConfig cfg = SaSsoManager.getConfig();
ParamName paramName = ssoTemplate.paramName;
// 处理
return cfg.doLoginHandle.apply(req.getParam(paramName.name), req.getParam(paramName.pwd));
}
/**
* SSO-Server端:校验ticket 获取账号id [模式三]
* @return 处理结果
*/
public Object ssoCheckTicket() {
ParamName paramName = ssoTemplate.paramName;
// 1、获取参数
SaRequest req = SaHolder.getRequest();
String client = req.getParam(paramName.client);
String ticket = req.getParamNotNull(paramName.ticket);
String sloCallback = req.getParam(paramName.ssoLogoutCall);
// 2、校验签名
if(ssoTemplate.getSsoConfig().getIsCheckSign()) {
ssoTemplate.getSignTemplate().checkRequest(req,
paramName.client, paramName.ticket, paramName.ssoLogoutCall);
} else {
ssoTemplate.printNoCheckSignWarning();
}
// 3、校验ticket,获取 loginId
Object loginId = ssoTemplate.checkTicket(ticket, client);
if(SaFoxUtil.isEmpty(loginId)) {
return SaResult.error("无效ticket" + ticket);
}
// 4、注册此客户端的单点注销回调URL
ssoTemplate.registerSloCallbackUrl(loginId, sloCallback);
// 5、给 client 端响应结果
return SaResult.data(loginId);
}
/**
* SSO-Server端:单点注销
* @return 处理结果
*/
public Object ssoSignout() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoConfig cfg = SaSsoManager.getConfig();
ParamName paramName = ssoTemplate.paramName;
// SSO-Server端:单点注销 [用户访问式] (不带loginId参数)
if(cfg.getIsSlo() && ! req.hasParam(paramName.loginId)) {
return ssoSignoutByUserVisit();
}
// SSO-Server端:单点注销 [Client调用式] (带loginId参数 & isHttp=true)
if(cfg.getIsHttp() && cfg.getIsSlo() && req.hasParam(paramName.loginId)) {
return ssoSignoutByClientHttp();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Server端:单点注销 [用户访问式]
* @return 处理结果
*/
public Object ssoSignoutByUserVisit() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
Object loginId = ssoTemplate.getStpLogic().getLoginIdDefaultNull();
// 单点注销
if(SaFoxUtil.isNotEmpty(loginId)) {
ssoTemplate.ssoLogout(loginId);
}
// 完成
return ssoLogoutBack(req, res);
}
/**
* SSO-Server端:单点注销 [Client调用式]
* @return 处理结果
*/
public Object ssoSignoutByClientHttp() {
ParamName paramName = ssoTemplate.paramName;
// 获取参数
SaRequest req = SaHolder.getRequest();
String loginId = req.getParam(paramName.loginId);
// step.1 校验签名
if(ssoTemplate.getSsoConfig().getIsCheckSign()) {
ssoTemplate.getSignTemplate().checkRequest(req, paramName.loginId);
} else {
ssoTemplate.printNoCheckSignWarning();
}
// step.2 单点注销
ssoTemplate.ssoLogout(loginId);
// 响应
return SaResult.ok();
}
// ----------- SSO-Client 端路由分发 -----------
/**
* 分发 Client 端所有请求
* @return 处理结果
*/
public Object clientDister() {
ApiName apiName = ssoTemplate.apiName;
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoConfig cfg = SaSsoManager.getConfig();
// ------------------ 路由分发 ------------------
// ---------- SSO-Client端:登录地址
if(req.isPath(apiName.ssoLogin)) {
return ssoLogin();
}
// ---------- SSO-Client端:单点注销
if(req.isPath(apiName.ssoLogout)) {
return ssoLogout();
}
// ---------- SSO-Client端:单点注销的回调 [模式三]
if(req.isPath(apiName.ssoLogoutCall) && cfg.getIsSlo() && cfg.getIsHttp()) {
return ssoLogoutCall();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Client端:登录地址
* @return 处理结果
*/
public Object ssoLogin() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaSsoConfig cfg = SaSsoManager.getConfig();
StpLogic stpLogic = ssoTemplate.getStpLogic();
ApiName apiName = ssoTemplate.apiName;
ParamName paramName = ssoTemplate.paramName;
// 获取参数
String back = req.getParam(paramName.back, "/");
String ticket = req.getParam(paramName.ticket);
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
if(stpLogic.isLogin()) {
return res.redirect(back);
}
/*
* 此时有两种情况:
* 情况1ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
*/
if(ticket == null) {
// 获取当前项目的 sso 登录地址
// 全局配置了就是用全局的,否则使用当前请求的地址
String currSsoLoginUrl;
if(SaFoxUtil.isNotEmpty(cfg.getCurrSsoLogin())) {
currSsoLoginUrl = cfg.getCurrSsoLogin();
} else {
currSsoLoginUrl = SaHolder.getRequest().getUrl();
}
// 构建url
String serverAuthUrl = ssoTemplate.buildServerAuthUrl(currSsoLoginUrl, back);
return res.redirect(serverAuthUrl);
} else {
// ------- 1、校验ticket,获取 loginId
Object loginId = checkTicket(ticket, apiName.ssoLogin);
// Be: 如果开发者自定义了处理逻辑
if(cfg.ticketResultHandle != null) {
return cfg.ticketResultHandle.apply(loginId, back);
}
// ------- 2、如果 loginId 无值,说明 ticket 无效
if(SaFoxUtil.isEmpty(loginId)) {
throw new SaSsoException("无效ticket" + ticket).setCode(SaSsoErrorCode.CODE_30004);
} else {
// 3、如果 loginId 有值,说明 ticket 有效,此时进行登录并重定向至back地址
stpLogic.login(loginId);
return res.redirect(back);
}
}
}
/**
* SSO-Client端:单点注销
* @return 处理结果
*/
public Object ssoLogout() {
// 获取对象
SaSsoConfig cfg = SaSsoManager.getConfig();
// ---------- SSO-Client端:单点注销 [模式二]
if(cfg.getIsSlo() && ! cfg.getIsHttp()) {
return ssoLogoutType2();
}
// ---------- SSO-Client端:单点注销 [模式三]
if(cfg.getIsSlo() && cfg.getIsHttp()) {
return ssoLogoutType3();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Client端:单点注销 [模式二]
* @return 处理结果
*/
public Object ssoLogoutType2() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = ssoTemplate.getStpLogic();
// 开始处理
if(stpLogic.isLogin()) {
stpLogic.logout(stpLogic.getLoginId());
}
// 返回
return ssoLogoutBack(req, res);
}
/**
* SSO-Client端:单点注销 [模式三]
* @return 处理结果
*/
public Object ssoLogoutType3() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = ssoTemplate.getStpLogic();
// 如果未登录,则无需注销
if( ! stpLogic.isLogin()) {
return ssoLogoutBack(req, res);
}
// 调用 sso-server 认证中心单点注销API
String url = ssoTemplate.buildSloUrl(stpLogic.getLoginId());
SaResult result = ssoTemplate.request(url);
// 校验响应状态码
if(SaResult.CODE_SUCCESS == result.getCode()) {
// 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
if(stpLogic.isLogin()) {
stpLogic.logout();
}
return ssoLogoutBack(req, res);
} else {
// 将 sso-server 回应的消息作为异常抛出
throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30006);
}
}
/**
* SSO-Client端:单点注销的回调 [模式三]
* @return 处理结果
*/
public Object ssoLogoutCall() {
ParamName paramName = ssoTemplate.paramName;
// 获取对象
SaRequest req = SaHolder.getRequest();
StpLogic stpLogic = ssoTemplate.getStpLogic();
// 获取参数
String loginId = req.getParamNotNull(paramName.loginId);
// 校验参数签名
if(ssoTemplate.getSsoConfig().getIsCheckSign()) {
ssoTemplate.getSignTemplate().checkRequest(req, paramName.loginId);
} else {
ssoTemplate.printNoCheckSignWarning();
}
// 注销当前应用端会话
stpLogic.logout(loginId);
// 响应
return SaResult.ok("单点注销回调成功");
}
// ----------- 工具方法
/**
* 封装:单点注销成功后返回结果
* @param req SaRequest对象
* @param res SaResponse对象
* @return 返回结果
*/
public Object ssoLogoutBack(SaRequest req, SaResponse res) {
ParamName paramName = ssoTemplate.paramName;
/*
* 三种情况:
* 1. 有back参数,值为SELF -> 回退一级并刷新
* 2. 有back参数,值为url -> 跳转到此url地址
* 3. 无back参数 -> 返回json数据
*/
String back = req.getParam(paramName.back);
if(SaFoxUtil.isNotEmpty(back)) {
if(back.equals(SaSsoConsts.SELF)) {
return "<script>if(document.referrer != location.href){ location.replace(document.referrer || '/'); }</script>";
}
return res.redirect(back);
} else {
return SaResult.ok("单点注销成功");
}
}
/**
* 封装:校验ticket,取出loginId
* @param ticket ticket码
* @param currUri 当前路由的uri,用于计算单点注销回调地址
* @return loginId
*/
public Object checkTicket(String ticket, String currUri) {
SaSsoConfig cfg = SaSsoManager.getConfig();
ApiName apiName = ssoTemplate.apiName;
// --------- 两种模式
if(cfg.getIsHttp()) {
// q1、使用模式三:使用 http 请求从认证中心校验ticket
// 计算当前 sso-client 的单点注销回调地址
String ssoLogoutCall = null;
if(cfg.getIsSlo()) {
// 如果配置了回调地址,就使用配置的值:
if(SaFoxUtil.isNotEmpty(cfg.getCurrSsoLogoutCall())) {
ssoLogoutCall = cfg.getCurrSsoLogoutCall();
}
// 如果提供了当前 uri,则根据此值来计算:
else if(SaFoxUtil.isNotEmpty(currUri)) {
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, apiName.ssoLogoutCall);
}
// 否则视为不注册单点注销回调地址
else {
}
}
// 构建请求URL
String checkUrl = ssoTemplate.buildCheckTicketUrl(ticket, ssoLogoutCall);
// 发起请求
SaResult result = ssoTemplate.request(checkUrl);
// 校验
if(result.getCode() != null && result.getCode() == SaResult.CODE_SUCCESS) {
return result.getData();
} else {
// 将 sso-server 回应的消息作为异常抛出
throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30005);
}
} else {
// q2、使用模式二:直连Redis校验ticket
return ssoTemplate.checkTicket(ticket);
}
}
// ----------- 全局默认实例 -----------
/**
* 全局默认实例
*/
public static SaSsoProcessor instance = new SaSsoProcessor();
}
@@ -1,647 +0,0 @@
/*
* 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.sso;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.sign.SaSignTemplate;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.name.ApiName;
import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
import java.util.*;
/**
* Sa-Token-SSO 单点登录模块
*
* @author click33
* @since 1.30.0
*/
public class SaSsoTemplate {
// ---------------------- 全局配置 ----------------------
/**
* 所有 API 名称
*/
public ApiName apiName = new ApiName();
/**
* 所有参数名称
*/
public ParamName paramName = new ParamName();
/**
* @param paramName 替换 paramName 对象
* @return 对象自身
*/
public SaSsoTemplate setParamName(ParamName paramName) {
this.paramName = paramName;
return this;
}
/**
* @param apiName 替换 apiName 对象
* @return 对象自身
*/
public SaSsoTemplate setApiName(ApiName apiName) {
this.apiName = apiName;
return this;
}
/**
* 获取底层使用的会话对象
* @return /
*/
public StpLogic getStpLogic() {
return StpUtil.stpLogic;
}
/**
* 获取底层使用的配置对象
* @return /
*/
public SaSsoConfig getSsoConfig() {
return SaSsoManager.getConfig();
}
/**
* 获取底层使用的 API 签名对象
* @return /
*/
public SaSignTemplate getSignTemplate() {
return SaManager.getSaSignTemplate();
}
// ---------------------- Ticket 操作 ----------------------
/**
* 保存 Ticket 关联的 loginId
* @param ticket ticket码
* @param loginId 账号id
*/
public void saveTicket(String ticket, Object loginId) {
// 保存 ticket -> loginId 的关系
long ticketTimeout = SaSsoManager.getConfig().getTicketTimeout();
SaManager.getSaTokenDao().set(splicingTicketSaveKey(ticket), String.valueOf(loginId), ticketTimeout);
}
/**
* 保存 Ticket 索引 id 反查 ticket
* @param ticket ticket码
* @param loginId 账号id
*/
public void saveTicketIndex(String ticket, Object loginId) {
long ticketTimeout = SaSsoManager.getConfig().getTicketTimeout();
SaManager.getSaTokenDao().set(splicingTicketIndexKey(loginId), String.valueOf(ticket), ticketTimeout);
}
/**
* 保存 Ticket 关联的 client
* @param ticket ticket码
* @param client 客户端标识
*/
public void saveTicketToClient(String ticket, String client) {
if(SaFoxUtil.isEmpty(client)) {
return;
}
long ticketTimeout = SaSsoManager.getConfig().getTicketTimeout();
SaManager.getSaTokenDao().set(splicingTicketToClientSaveKey(ticket), client, ticketTimeout);
}
/**
* 删除 Ticket
* @param ticket Ticket码
*/
public void deleteTicket(String ticket) {
if(ticket == null) {
return;
}
SaManager.getSaTokenDao().delete(splicingTicketSaveKey(ticket));
}
/**
* 删除 Ticket索引
* @param loginId 账号id
*/
public void deleteTicketIndex(Object loginId) {
if(loginId == null) {
return;
}
SaManager.getSaTokenDao().delete(splicingTicketIndexKey(loginId));
}
/**
* 删除 Ticket 关联的 client
* @param ticket Ticket码
*/
public void deleteTicketToClient(String ticket) {
if(ticket == null) {
return;
}
SaManager.getSaTokenDao().delete(splicingTicketToClientSaveKey(ticket));
}
/**
* 查询 ticket 指向的 loginId,如果 ticket 码无效则返回 null
* @param ticket Ticket码
* @return 账号id
*/
public Object getLoginId(String ticket) {
if(SaFoxUtil.isEmpty(ticket)) {
return null;
}
return SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket));
}
/**
* 查询 ticket 指向的 loginId,并转换为指定类型
* @param <T> 要转换的类型
* @param ticket Ticket码
* @param cs 要转换的类型
* @return 账号id
*/
public <T> T getLoginId(String ticket, Class<T> cs) {
return SaFoxUtil.getValueByType(getLoginId(ticket), cs);
}
/**
* 查询 指定 loginId 其所属的 ticket 值
* @param loginId 账号id
* @return Ticket值
*/
public String getTicketValue(Object loginId) {
if(loginId == null) {
return null;
}
return SaManager.getSaTokenDao().get(splicingTicketIndexKey(loginId));
}
/**
* 查询 ticket 关联的 client,如果 ticket 码无效则返回 null
* @param ticket Ticket码
* @return 账号id
*/
public String getTicketToClient(String ticket) {
if(SaFoxUtil.isEmpty(ticket)) {
return null;
}
return SaManager.getSaTokenDao().get(splicingTicketToClientSaveKey(ticket));
}
//
/**
* 根据 账号id 创建一个 Ticket码
* @param loginId 账号id
* @param client 客户端标识
* @return Ticket码
*/
public String createTicket(Object loginId, String client) {
// 创建 Ticket
String ticket = randomTicket(loginId);
// 保存 Ticket
saveTicket(ticket, loginId);
saveTicketIndex(ticket, loginId);
saveTicketToClient(ticket, client);
// 返回 Ticket
return ticket;
}
/**
* 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除
* @param ticket Ticket码
* @return 账号id
*/
public Object checkTicket(String ticket) {
return checkTicket(ticket, getSsoConfig().getClient());
}
/**
* 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除
* @param ticket Ticket码
* @param client client 标识
* @return 账号id
*/
public Object checkTicket(String ticket, String client) {
// 读取 loginId
String loginId = SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket));
if(loginId != null) {
// 解析出这个 ticket 关联的 Client
String ticketClient = getTicketToClient(ticket);
// 如果指定了 client 标识,则校验一下 client 标识是否一致
if(SaFoxUtil.isNotEmpty(client) && SaFoxUtil.notEquals(client, ticketClient)) {
throw new SaSsoException("该 ticket 不属于 client=" + client + ", ticket 值: " + ticket)
.setCode(SaSsoErrorCode.CODE_30011);
}
// 删除 ticket 信息,使其只有一次性有效
deleteTicket(ticket);
deleteTicketIndex(loginId);
deleteTicketToClient(ticket);
}
//
return loginId;
}
/**
* 随机一个 Ticket码
* @param loginId 账号id
* @return Ticket码
*/
public String randomTicket(Object loginId) {
return SaFoxUtil.getRandomString(64);
}
/**
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
* @return see note
*/
public String getAllowUrl() {
// 默认从配置文件中返回
return SaSsoManager.getConfig().getAllowUrl();
}
/**
* 校验重定向url合法性
* @param url 下放ticket的url地址
*/
public void checkRedirectUrl(String url) {
// 1、是否是一个有效的url
if( ! SaFoxUtil.isUrl(url) ) {
throw new SaSsoException("无效redirect" + url).setCode(SaSsoErrorCode.CODE_30001);
}
// 2、截取掉?后面的部分
int qIndex = url.indexOf("?");
if(qIndex != -1) {
url = url.substring(0, qIndex);
}
// 3、是否在[允许地址列表]之中
List<String> authUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(","));
if( ! SaStrategy.instance.hasElement.apply(authUrlList, url) ) {
throw new SaSsoException("非法redirect" + url).setCode(SaSsoErrorCode.CODE_30002);
}
// 校验通过 √
}
// ------------------- SSO 模式三相关 -------------------
/**
* 为指定账号id注册单点注销回调URL
* @param loginId 账号id
* @param sloCallbackUrl 单点注销时的回调URL
*/
public void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) {
if(SaFoxUtil.isEmpty(loginId) || SaFoxUtil.isEmpty(sloCallbackUrl)) {
return;
}
SaSession session = getStpLogic().getSessionByLoginId(loginId);
Set<String> urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, HashSet::new);
urlSet.add(sloCallbackUrl);
session.set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet);
}
/**
* 指定账号单点注销
* @param loginId 指定账号
*/
public void ssoLogout(Object loginId) {
// 如果这个账号尚未登录,则无操作
SaSession session = getStpLogic().getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
// step.1 遍历通知 Client 端注销会话
SaSsoConfig cfg = SaSsoManager.getConfig();
Set<String> urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, HashSet::new);
for (String url : urlSet) {
url = joinLoginIdAndSign(url, loginId);
cfg.sendHttp.apply(url);
}
// step.2 Server端注销
getStpLogic().logout(loginId);
}
/**
* 根据配置的 getData 地址,查询数据
* @param paramMap 查询参数
* @return 查询结果
*/
public Object getData(Map<String, Object> paramMap) {
String getDataUrl = SaSsoManager.getConfig().splicingGetDataUrl();
return getData(getDataUrl, paramMap);
}
/**
* 根据自定义 path 地址,查询数据 (此方法需要配置 sa-token.sso.server-url 地址)
* @param path 自定义 path
* @param paramMap 查询参数
* @return 查询结果
*/
public Object getData(String path, Map<String, Object> paramMap) {
String url = buildCustomPathUrl(path, paramMap);
return SaSsoManager.getConfig().sendHttp.apply(url);
}
// ---------------------- 构建URL ----------------------
/**
* 构建URLServer端 单点登录地址
* @param clientLoginUrl Client端登录地址
* @param back 回调路径
* @return [SSO-Server端-认证地址 ]
*/
public String buildServerAuthUrl(String clientLoginUrl, String back) {
// 服务端认证地址
String serverUrl = SaSsoManager.getConfig().splicingAuthUrl();
// 拼接客户端标识
String client = SaSsoManager.getConfig().getClient();
if(SaFoxUtil.isNotEmpty(client)) {
serverUrl = SaFoxUtil.joinParam(serverUrl, paramName.client, client);
}
// 对back地址编码
back = (back == null ? "" : back);
back = SaFoxUtil.encodeUrl(back);
// 开始拼接 sso 统一认证地址,形如:serverAuthUrl = http://xxx.com?redirectUrl=xxx.com?back=xxx.com
/*
* 部分 Servlet 版本 request.getRequestURL() 返回的 url 带有 query 参数,形如:http://domain.com?id=1
* 如果不加判断会造成最终生成的 serverAuthUrl 带有双 back 参数 ,这个 if 判断正是为了解决此问题
*/
if( ! clientLoginUrl.contains(paramName.back + "=" + back) ) {
clientLoginUrl = SaFoxUtil.joinParam(clientLoginUrl, paramName.back, back);
}
// 返回
return SaFoxUtil.joinParam(serverUrl, paramName.redirect, clientLoginUrl);
}
/**
* 构建URLServer端向Client下放ticket的地址
* @param loginId 账号id
* @param client 客户端标识
* @param redirect Client端提供的重定向地址
* @return see note
*/
public String buildRedirectUrl(Object loginId, String client, String redirect) {
// 校验 重定向地址 是否合法
checkRedirectUrl(redirect);
// 删掉 旧Ticket
deleteTicket(getTicketValue(loginId));
// 创建 新Ticket
String ticket = createTicket(loginId, client);
// 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket
return SaFoxUtil.joinParam(encodeBackParam(redirect), paramName.ticket, ticket);
}
/**
* 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug
* @param url url
* @return 编码过后的url
*/
public String encodeBackParam(String url) {
// 获取back参数所在位置
int index = url.indexOf("?" + paramName.back + "=");
if(index == -1) {
index = url.indexOf("&" + paramName.back + "=");
if(index == -1) {
return url;
}
}
// 开始编码
int length = paramName.back.length() + 2;
String back = url.substring(index + length);
back = SaFoxUtil.encodeUrl(back);
// 放回url中
url = url.substring(0, index + length) + back;
return url;
}
/**
* 构建URL:校验ticket的URL
* <p> 在模式三下,Client端拿到Ticket后根据此地址向Server端发送请求,获取账号id
* @param ticket ticket码
* @param ssoLogoutCallUrl 单点注销时的回调URL
* @return 构建完毕的URL
*/
public String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) {
// s1:先收集应该增加的参数:client、ticket、ssoLogoutCall
Map<String, Object> paramMap = new TreeMap<>();
// 拼接 client 参数
String client = getSsoConfig().getClient();
if(SaFoxUtil.isNotEmpty(client)) {
paramMap.put(paramName.client, client);
}
// 拼接 ticket 参数
paramMap.put(paramName.ticket, ticket);
// 拼接单点注销时的回调 URL
if(ssoLogoutCallUrl != null) {
paramMap.put(paramName.ssoLogoutCall, ssoLogoutCallUrl);
}
// s2:构建 url 地址
String url = SaSsoManager.getConfig().splicingCheckTicketUrl();
String paramStr = getSignTemplate().addSignParamsAndJoin(paramMap);
String finalUrl = SaFoxUtil.joinParam(url, paramStr);
// 返回
return finalUrl;
}
/**
* 构建URL:单点注销URL
* @param loginId 要注销的账号id
* @return 单点注销URL
*/
public String buildSloUrl(Object loginId) {
String url = SaSsoManager.getConfig().splicingSloUrl();
return joinLoginIdAndSign(url, loginId);
}
/**
* 构建URLServer端 getData 地址,带签名等参数
* @param paramMap 查询参数
* @return /
*/
public String buildGetDataUrl(Map<String, Object> paramMap) {
String getDataUrl = SaSsoManager.getConfig().getGetDataUrl();
return buildCustomPathUrl(getDataUrl, paramMap);
}
/**
* 构建URLServer 端自定义 path 地址,带签名等参数 (此方法需要配置 sa-token.sso.server-url 地址)
* @param paramMap 请求参数
* @return /
*/
public String buildCustomPathUrl(String path, Map<String, Object> paramMap) {
// 如果path不是以 http 开头,那么就拼接上 serverUrl
String url = path;
if( ! url.startsWith("http") ) {
String serverUrl = SaSsoManager.getConfig().getServerUrl();
SaSsoException.notEmpty(serverUrl, "请先配置 sa-token.sso.server-url 地址", SaSsoErrorCode.CODE_30012);
url = SaFoxUtil.spliceTwoUrl(serverUrl, path);
}
// 添加签名等参数,并序列化
return joinParamMapAndSign(url, paramMap);
}
// ------------------- 发起请求 -------------------
/**
* 发出请求,并返回 SaResult 结果
* @param url 请求地址
* @return 返回的结果
*/
public SaResult request(String url) {
String body = SaSsoManager.getConfig().sendHttp.apply(url);
Map<String, Object> map = SaManager.getSaJsonTemplate().parseJsonToMap(body);
return new SaResult(map);
}
/**
* 给 paramMap 追加 sign 等参数,并序列化为kv字符串,拼接到url后面
* @param url 请求地址
* @param paramMap 请求原始参数列表
* @return 加工后的url
*/
public String joinParamMapAndSign(String url, Map<String, Object> paramMap) {
// 在参数列表中追加:时间戳、随机字符串、参数签名
SaManager.getSaSignTemplate().addSignParams(paramMap);
// 将参数列表序列化为kv字符串
String signParams = SaManager.getSaSignTemplate().joinParams(paramMap);
// 将kv字符串拼接到url后面
return SaFoxUtil.joinParam(url, signParams);
}
/**
* 给 url 拼接 loginId 参数,并拼接 sign 等参数
* @param url 链接
* @param loginId 账号id
* @return 加工后的url
*/
public String joinLoginIdAndSign(String url, Object loginId) {
Map<String, Object> paramMap = new LinkedHashMap<>();
paramMap.put(paramName.loginId, loginId);
return joinParamMapAndSign(url, paramMap);
}
// ------------------- 返回相应key -------------------
/**
* 拼接keyTicket 查 账号Id
* @param ticket ticket值
* @return key
*/
public String splicingTicketSaveKey(String ticket) {
return getStpLogic().getConfigOrGlobal().getTokenName() + ":ticket:" + ticket;
}
/**
* 拼接keyTicket 查 所属的 client
* @param ticket ticket值
* @return key
*/
public String splicingTicketToClientSaveKey(String ticket) {
return getStpLogic().getConfigOrGlobal().getTokenName() + ":ticket-client:" + ticket;
}
/**
* 拼接key:账号Id 反查 Ticket
* @param id 账号id
* @return key
*/
public String splicingTicketIndexKey(Object id) {
return getStpLogic().getConfigOrGlobal().getTokenName() + ":id-ticket:" + id;
}
// 在 sa-token.sso.is-check-sign=false 时,输出警告信息
public void printNoCheckSignWarning() {
System.err.println("警告信息:当前配置项 sa-token.sso.is-check-sign=false 已跳过参数签名校验," +
"此模式仅为方便本地调试使用,生产环境下请务必配置为 true (配置项默认为true)");
}
// -------- 以下方法已废弃,仅为兼容旧版本而保留 --------
/**
* 构建URL:Server端 账号资料查询地址
* @param loginId 账号id
* @return Server端 账号资料查询地址
*/
@Deprecated
public String buildUserinfoUrl(Object loginId) {
String userinfoUrl = SaSsoManager.getConfig().splicingUserinfoUrl();
return joinLoginIdAndSign(userinfoUrl, loginId);
}
/**
* 获取:账号资料
* @param loginId 账号id
* @return 账号资料
*/
@Deprecated
public Object getUserinfo(Object loginId) {
String url = buildUserinfoUrl(loginId);
return request(url);
}
}
@@ -0,0 +1,390 @@
/*
* 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.sso.config;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.function.SendHttpFunction;
import cn.dev33.satoken.sso.function.TicketResultHandleFunction;
import cn.dev33.satoken.util.SaFoxUtil;
import java.io.Serializable;
/**
* Sa-Token SSO 单点登录模块 配置类 Client端)
*
* @author click33
* @since 1.30.0
*/
public class SaSsoClientConfig implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
/**
* 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响)
*/
public String mode = "";
/**
* 当前 Client 名称标识,用于和 ticket 码的互相锁定
*/
public String client;
/**
* 配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置
*/
public String serverUrl;
/**
* 单独配置 Server 端单点登录授权地址
*/
public String authUrl = "/sso/auth";
/**
* 单独配置 Server 端的 ticket 校验地址
*/
public String checkTicketUrl = "/sso/checkTicket";
/**
* 单独配置 Server 端查询数据 getData 地址
*/
public String getDataUrl = "/sso/getData";
/**
* 单独配置 Server 端查询 userinfo 地址
*/
public String userinfoUrl = "/sso/userinfo";
/**
* 单独配置 Server 端单点注销地址
*/
public String sloUrl = "/sso/signout";
/**
* 配置当前 Client 端的登录地址(为空时自动获取)
*/
public String currSsoLogin;
/**
* 配置当前 Client 端的单点注销回调URL (为空时自动获取)
*/
public String currSsoLogoutCall;
/**
* 是否打开单点注销功能
*/
public Boolean isSlo = true;
/**
* 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
*/
public Boolean isHttp = false;
/**
* 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*/
public Boolean isCheckSign = true;
// get set
/**
* 获取 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响)
*
* @return /
*/
public String getMode() {
return this.mode;
}
/**
* 设置 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响)
*
* @param mode /
*/
public void setMode(String mode) {
this.mode = mode;
}
/**
* @return 是否打开单点注销功能
*/
public Boolean getIsSlo() {
return isSlo;
}
/**
* @param isSlo 是否打开单点注销功能
* @return 对象自身
*/
public SaSsoClientConfig setIsSlo(Boolean isSlo) {
this.isSlo = isSlo;
return this;
}
/**
* @return isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
*/
public Boolean getIsHttp() {
return isHttp;
}
/**
* @param isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
* @return 对象自身
*/
public SaSsoClientConfig setIsHttp(Boolean isHttp) {
this.isHttp = isHttp;
return this;
}
/**
* @return 当前 Client 名称标识,用于和 ticket 码的互相锁定
*/
public String getClient() {
return client;
}
/**
* @param client 当前 Client 名称标识,用于和 ticket 码的互相锁定
*/
public SaSsoClientConfig setClient(String client) {
this.client = client;
return this;
}
/**
* @return 配置的 Server 端单点登录授权地址
*/
public String getAuthUrl() {
return authUrl;
}
/**
* @param authUrl 配置 Server 端单点登录授权地址
* @return 对象自身
*/
public SaSsoClientConfig setAuthUrl(String authUrl) {
this.authUrl = authUrl;
return this;
}
/**
* @return 配置的 Server 端的 ticket 校验地址
*/
public String getCheckTicketUrl() {
return checkTicketUrl;
}
/**
* @param checkTicketUrl 配置 Server 端的 ticket 校验地址
* @return 对象自身
*/
public SaSsoClientConfig setCheckTicketUrl(String checkTicketUrl) {
this.checkTicketUrl = checkTicketUrl;
return this;
}
/**
* @return Server 端查询数据 getData 地址
*/
public String getGetDataUrl() {
return getDataUrl;
}
/**
* @param getDataUrl 配置 Server 端查询数据 getData 地址
* @return 对象自身
*/
public SaSsoClientConfig setGetDataUrl(String getDataUrl) {
this.getDataUrl = getDataUrl;
return this;
}
/**
* @return 配置的 Server 端查询 userinfo 地址
*/
public String getUserinfoUrl() {
return userinfoUrl;
}
/**
* @param userinfoUrl 配置 Server 端查询 userinfo 地址
* @return 对象自身
*/
public SaSsoClientConfig setUserinfoUrl(String userinfoUrl) {
this.userinfoUrl = userinfoUrl;
return this;
}
/**
* @return 配置 Server 端单点注销地址
*/
public String getSloUrl() {
return sloUrl;
}
/**
* @param sloUrl 配置 Server 端单点注销地址
* @return 对象自身
*/
public SaSsoClientConfig setSloUrl(String sloUrl) {
this.sloUrl = sloUrl;
return this;
}
/**
* @return 配置当前 Client 端的登录地址(为空时自动获取)
*/
public String getCurrSsoLogin() {
return currSsoLogin;
}
/**
* @param currSsoLogin 配置当前 Client 端的登录地址(为空时自动获取)
* @return 对象自身
*/
public SaSsoClientConfig setCurrSsoLogin(String currSsoLogin) {
this.currSsoLogin = currSsoLogin;
return this;
}
/**
* @return 配置当前 Client 端的单点注销回调URL (为空时自动获取)
*/
public String getCurrSsoLogoutCall() {
return currSsoLogoutCall;
}
/**
* @param currSsoLogoutCall 配置当前 Client 端的单点注销回调URL (为空时自动获取)
* @return 对象自身
*/
public SaSsoClientConfig setCurrSsoLogoutCall(String currSsoLogoutCall) {
this.currSsoLogoutCall = currSsoLogoutCall;
return this;
}
/**
* @return 配置的 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置
*/
public String getServerUrl() {
return serverUrl;
}
/**
* @param serverUrl 配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置
* @return 对象自身
*/
public SaSsoClientConfig setServerUrl(String serverUrl) {
this.serverUrl = serverUrl;
return this;
}
/**
* 获取 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*
* @return isCheckSign 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*/
public Boolean getIsCheckSign() {
return this.isCheckSign;
}
/**
* 设置 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*
* @param isCheckSign 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*/
public SaSsoClientConfig setIsCheckSign(Boolean isCheckSign) {
this.isCheckSign = isCheckSign;
return this;
}
@Override
public String toString() {
return "SaSsoConfig ["
+ "mode=" + mode
+ ", client=" + client
+ ", serverUrl=" + serverUrl
+ ", authUrl=" + authUrl
+ ", checkTicketUrl=" + checkTicketUrl
+ ", getDataUrl=" + getDataUrl
+ ", userinfoUrl=" + userinfoUrl
+ ", sloUrl=" + sloUrl
+ ", currSsoLogin=" + currSsoLogin
+ ", currSsoLogoutCall=" + currSsoLogoutCall
+ ", isSlo=" + isSlo
+ ", isHttp=" + isHttp
+ ", isCheckSign=" + isCheckSign
+ "]";
}
// 额外添加的一些函数
/**
* @return 获取拼接urlServer 端单点登录授权地址
*/
public String splicingAuthUrl() {
return SaFoxUtil.spliceTwoUrl(getServerUrl(), getAuthUrl());
}
/**
* @return 获取拼接urlServer 端的 ticket 校验地址
*/
public String splicingCheckTicketUrl() {
return SaFoxUtil.spliceTwoUrl(getServerUrl(), getCheckTicketUrl());
}
/**
* @return 获取拼接urlServer 端查询数据 getData 地址
*/
public String splicingGetDataUrl() {
return SaFoxUtil.spliceTwoUrl(getServerUrl(), getGetDataUrl());
}
/**
* @return 获取拼接urlServer 端查询 userinfo 地址
*/
public String splicingUserinfoUrl() {
return SaFoxUtil.spliceTwoUrl(getServerUrl(), getUserinfoUrl());
}
/**
* @return 获取拼接urlServer 端单点注销地址
*/
public String splicingSloUrl() {
return SaFoxUtil.spliceTwoUrl(getServerUrl(), getSloUrl());
}
// -------------------- 所有回调函数 --------------------
/**
* SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用)
* <p> 参数:loginId, back
* <p> 返回值:返回给前端的值
*/
public TicketResultHandleFunction ticketResultHandle = null;
/**
* SSO-Client端:发送Http请求的处理函数
*/
public SendHttpFunction sendHttp = url -> {
throw new SaSsoException("请配置 Http 请求处理器").setCode(SaSsoErrorCode.CODE_30010);
};
}
@@ -0,0 +1,223 @@
/*
* 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.sso.config;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.function.DoLoginHandleFunction;
import cn.dev33.satoken.sso.function.NotLoginViewFunction;
import cn.dev33.satoken.sso.function.SendHttpFunction;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
import java.io.Serializable;
/**
* Sa-Token SSO 单点登录模块 配置类 Server端)
*
* @author click33
* @since 1.38.0
*/
public class SaSsoServerConfig implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
// ----------------- Server端相关配置
/**
* 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响)
*/
public String mode = "";
/**
* Ticket有效期 (单位: 秒)
*/
public long ticketTimeout = 60 * 5;
/**
* 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
*/
public String allowUrl = "*";
/**
* 是否打开单点注销功能
*/
public Boolean isSlo = true;
/**
* 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
*/
public Boolean isHttp = false;
/**
* 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*/
public Boolean isCheckSign = true;
// get set
/**
* 获取 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响)
*
* @return /
*/
public String getMode() {
return this.mode;
}
/**
* 设置 指定当前系统集成 SSO 时使用的模式(约定型配置项,不对代码逻辑产生任何影响)
*
* @param mode /
*/
public void setMode(String mode) {
this.mode = mode;
}
/**
* @return Ticket有效期 (单位: 秒)
*/
public long getTicketTimeout() {
return ticketTimeout;
}
/**
* @param ticketTimeout Ticket有效期 (单位: 秒)
* @return 对象自身
*/
public SaSsoServerConfig setTicketTimeout(long ticketTimeout) {
this.ticketTimeout = ticketTimeout;
return this;
}
/**
* @return 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
*/
public String getAllowUrl() {
return allowUrl;
}
/**
* @param allowUrl 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
* @return 对象自身
*/
public SaSsoServerConfig setAllowUrl(String allowUrl) {
this.allowUrl = allowUrl;
return this;
}
/**
* @return 是否打开单点注销功能
*/
public Boolean getIsSlo() {
return isSlo;
}
/**
* @param isSlo 是否打开单点注销功能
* @return 对象自身
*/
public SaSsoServerConfig setIsSlo(Boolean isSlo) {
this.isSlo = isSlo;
return this;
}
/**
* @return isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
*/
public Boolean getIsHttp() {
return isHttp;
}
/**
* @param isHttp 是否打开模式三(此值为 true 时将使用 http 请求:校验ticket值、单点注销、获取userinfo
* @return 对象自身
*/
public SaSsoServerConfig setIsHttp(Boolean isHttp) {
this.isHttp = isHttp;
return this;
}
/**
* 获取 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*
* @return isCheckSign 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*/
public Boolean getIsCheckSign() {
return this.isCheckSign;
}
/**
* 设置 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*
* @param isCheckSign 是否校验参数签名(方便本地调试用的一个配置项,生产环境请务必为true)
*/
public SaSsoServerConfig setIsCheckSign(Boolean isCheckSign) {
this.isCheckSign = isCheckSign;
return this;
}
/**
* 以数组形式写入允许的授权回调地址
* @param url 所有集合
* @return 对象自身
*/
public SaSsoServerConfig setAllow(String ...url) {
this.allowUrl = SaFoxUtil.arrayJoin(url);
return this;
}
@Override
public String toString() {
return "SaSsoServerConfig ["
+ "mode=" + mode
+ ", ticketTimeout=" + ticketTimeout
+ ", allowUrl=" + allowUrl
+ ", isSlo=" + isSlo
+ ", isHttp=" + isHttp
+ ", isCheckSign=" + isCheckSign
+ "]";
}
// -------------------- 所有回调函数 --------------------
/**
* SSO-Server端:未登录时返回的View
*/
public NotLoginViewFunction notLoginView = () -> {
return "当前会话在SSO-Server认证中心尚未登录(当前未配置登录视图)";
};
/**
* SSO-Server端:登录函数
*/
public DoLoginHandleFunction doLoginHandle = (name, pwd) -> {
return SaResult.error();
};
/**
* SSO-Server端:发送Http请求的处理函数
*/
public SendHttpFunction sendHttp = url -> {
throw new SaSsoException("请配置 Http 请求处理器").setCode(SaSsoErrorCode.CODE_30010);
};
}
@@ -0,0 +1,32 @@
/*
* 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.sso.function;
import java.util.function.BiFunction;
/**
* 函数式接口:SSO-Server端:未登录时返回的View
*
* <p> 参数:账号、密码 </p>
* <p> 返回:登录结果 </p>
*
* @author click33
* @since 1.38.0
*/
@FunctionalInterface
public interface DoLoginHandleFunction extends BiFunction<String, String, Object> {
}
@@ -0,0 +1,32 @@
/*
* 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.sso.function;
import java.util.function.Supplier;
/**
* 函数式接口:SSO-Server端:未登录时返回的View
*
* <p> 参数:无 </p>
* <p> 返回:未登录时的 View 视图 </p>
*
* @author click33
* @since 1.38.0
*/
@FunctionalInterface
public interface NotLoginViewFunction extends Supplier<Object> {
}
@@ -0,0 +1,32 @@
/*
* 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.sso.function;
import java.util.function.Function;
/**
* 函数式接口:发送Http请求的处理函数
*
* <p> 参数:要请求的url </p>
* <p> 返回:请求结果 </p>
*
* @author click33
* @since 1.38.0
*/
@FunctionalInterface
public interface SendHttpFunction extends Function<String, String> {
}
@@ -0,0 +1,32 @@
/*
* 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.sso.function;
import java.util.function.BiFunction;
/**
* 函数式接口:SSO-Client端:自定义校验Ticket返回值的处理逻辑 (每次从认证中心获取校验Ticket的结果后调用)
*
* <p> 参数:loginId, back </p>
* <p> 返回:返回给前端的值 </p>
*
* @author click33
* @since 1.38.0
*/
@FunctionalInterface
public interface TicketResultHandleFunction extends BiFunction<Object, String, Object> {
}
@@ -0,0 +1,114 @@
/*
* 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.sso.model;
/**
* Sa-Token SSO Model
*
* @author click33
* @since 1.38.0
*/
public class SaSsoClientModel {
/**
* 客户端标识
*/
public String client;
/**
* 单点注销回调url
*/
public String ssoLogoutCall;
/**
* 此 client 注册信息的时间,13位时间戳
*/
public Long regTime;
public SaSsoClientModel() {
}
public SaSsoClientModel(String client, String ssoLogoutCall) {
this.client = client;
this.ssoLogoutCall = ssoLogoutCall;
this.regTime = System.currentTimeMillis();
}
/**
* 获取 客户端标识
*
* @return client 客户端标识
*/
public String getClient() {
return this.client;
}
/**
* 设置 客户端标识
*
* @param client 客户端标识
*/
public void setClient(String client) {
this.client = client;
}
/**
* 获取 单点注销回调url
*
* @return ssoLogoutCall 单点注销回调url
*/
public String getSsoLogoutCall() {
return this.ssoLogoutCall;
}
/**
* 设置 单点注销回调url
*
* @param ssoLogoutCall 单点注销回调url
*/
public void setSsoLogoutCall(String ssoLogoutCall) {
this.ssoLogoutCall = ssoLogoutCall;
}
/**
* 获取 此 client 注册信息的时间,13位时间戳
*
* @return regTime 此 client 注册信息的时间,13位时间戳
*/
public Long getRegTime() {
return this.regTime;
}
/**
* 设置 此 client 注册信息的时间,13位时间戳
*
* @param regTime 此 client 注册信息的时间,13位时间戳
*/
public void setRegTime(Long regTime) {
this.regTime = regTime;
}
@Override
public String toString() {
return "SaSsoClientModel{" +
"client='" + client + '\'' +
", ssoLogoutCall='" + ssoLogoutCall + '\'' +
", regTime='" + regTime + '\'' +
'}';
}
}
@@ -0,0 +1,307 @@
/*
* 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.sso.processor;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.name.ApiName;
import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.sso.template.SaSsoClientTemplate;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* SSO 请求处理器 Client端)
*
* @author click33
* @since 1.38.0
*/
public class SaSsoClientProcessor {
/**
* 全局默认实例
*/
public static SaSsoClientProcessor instance = new SaSsoClientProcessor();
/**
* 底层 SaSsoClientTemplate 对象
*/
public SaSsoClientTemplate ssoClientTemplate = new SaSsoClientTemplate();
// ----------- SSO-Client 端路由分发 -----------
/**
* 分发 Client 端所有请求
* @return 处理结果
*/
public Object dister() {
ApiName apiName = ssoClientTemplate.apiName;
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoClientConfig cfg = ssoClientTemplate.getClientConfig();
// ------------------ 路由分发 ------------------
// ---------- SSO-Client端:登录地址
if(req.isPath(apiName.ssoLogin)) {
return ssoLogin();
}
// ---------- SSO-Client端:单点注销
if(req.isPath(apiName.ssoLogout)) {
return ssoLogout();
}
// ---------- SSO-Client端:单点注销的回调 [模式三]
if(req.isPath(apiName.ssoLogoutCall) && cfg.getIsSlo() && cfg.getIsHttp()) {
return ssoLogoutCall();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Client端:登录地址
* @return 处理结果
*/
public Object ssoLogin() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaSsoClientConfig cfg = ssoClientTemplate.getClientConfig();
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
ApiName apiName = ssoClientTemplate.apiName;
ParamName paramName = ssoClientTemplate.paramName;
// 获取参数
String back = req.getParam(paramName.back, "/");
String ticket = req.getParam(paramName.ticket);
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
if(stpLogic.isLogin()) {
return res.redirect(back);
}
/*
* 此时有两种情况:
* 情况1ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
*/
if(ticket == null) {
// 获取当前项目的 sso 登录地址
// 全局配置了就是用全局的,否则使用当前请求的地址
String currSsoLoginUrl;
if(SaFoxUtil.isNotEmpty(cfg.getCurrSsoLogin())) {
currSsoLoginUrl = cfg.getCurrSsoLogin();
} else {
currSsoLoginUrl = SaHolder.getRequest().getUrl();
}
// 构建url
String serverAuthUrl = ssoClientTemplate.buildServerAuthUrl(currSsoLoginUrl, back);
return res.redirect(serverAuthUrl);
} else {
// ------- 1、校验ticket,获取 loginId
Object loginId = checkTicketByMode2Or3(ticket, apiName.ssoLogin);
// Be: 如果开发者自定义了处理逻辑
if(cfg.ticketResultHandle != null) {
return cfg.ticketResultHandle.apply(loginId, back);
}
// ------- 2、如果 loginId 无值,说明 ticket 无效
if(SaFoxUtil.isEmpty(loginId)) {
throw new SaSsoException("无效ticket" + ticket).setCode(SaSsoErrorCode.CODE_30004);
} else {
// 3、如果 loginId 有值,说明 ticket 有效,此时进行登录并重定向至back地址
stpLogic.login(loginId);
return res.redirect(back);
}
}
}
/**
* SSO-Client端:单点注销
* @return 处理结果
*/
public Object ssoLogout() {
// 获取对象
SaSsoClientConfig cfg = ssoClientTemplate.getClientConfig();
// ---------- SSO-Client端:单点注销 [模式二]
if(cfg.getIsSlo() && ! cfg.getIsHttp()) {
return ssoLogoutType2();
}
// ---------- SSO-Client端:单点注销 [模式三]
if(cfg.getIsSlo() && cfg.getIsHttp()) {
return ssoLogoutType3();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Client端:单点注销 [模式二]
* @return 处理结果
*/
public Object ssoLogoutType2() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
// 开始处理
if(stpLogic.isLogin()) {
stpLogic.logout(stpLogic.getLoginId());
}
// 返回
return ssoLogoutBack(req, res);
}
/**
* SSO-Client端:单点注销 [模式三]
* @return 处理结果
*/
public Object ssoLogoutType3() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
// 如果未登录,则无需注销
if( ! stpLogic.isLogin()) {
return ssoLogoutBack(req, res);
}
// 调用 sso-server 认证中心单点注销API
String url = ssoClientTemplate.buildSloUrl(stpLogic.getLoginId());
SaResult result = ssoClientTemplate.request(url);
// 校验响应状态码
if(SaResult.CODE_SUCCESS == result.getCode()) {
// 极端场景下,sso-server 中心的单点注销可能并不会通知到此 client 端,所以这里需要再补一刀
if(stpLogic.isLogin()) {
stpLogic.logout();
}
return ssoLogoutBack(req, res);
} else {
// 将 sso-server 回应的消息作为异常抛出
throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30006);
}
}
/**
* SSO-Client端:单点注销的回调 [模式三]
* @return 处理结果
*/
public Object ssoLogoutCall() {
// 获取对象
SaRequest req = SaHolder.getRequest();
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
ParamName paramName = ssoClientTemplate.paramName;
SaSsoClientConfig ssoConfig = ssoClientTemplate.getClientConfig();
// 获取参数
String loginId = req.getParamNotNull(paramName.loginId);
// 校验参数签名
if(ssoConfig.getIsCheckSign()) {
ssoClientTemplate.getSignTemplate(ssoConfig.getClient()).checkRequest(req, paramName.loginId);
} else {
SaSsoManager.printNoCheckSignWarningByRuntime();
}
// 注销当前应用端会话
stpLogic.logout(loginId);
// 响应
return SaResult.ok("单点注销回调成功");
}
// 工具方法
/**
* 封装:校验ticket,取出loginId
* @param ticket ticket码
* @param currUri 当前路由的uri,用于计算单点注销回调地址
* @return loginId
*/
public Object checkTicketByMode2Or3(String ticket, String currUri) {
SaSsoClientConfig cfg = ssoClientTemplate.getClientConfig();
ApiName apiName = ssoClientTemplate.apiName;
// --------- 两种模式
if(cfg.getIsHttp()) {
// q1、使用模式三:使用 http 请求从认证中心校验ticket
// 计算当前 sso-client 的单点注销回调地址
String ssoLogoutCall = null;
if(cfg.getIsSlo()) {
// 如果配置了回调地址,就使用配置的值:
if(SaFoxUtil.isNotEmpty(cfg.getCurrSsoLogoutCall())) {
ssoLogoutCall = cfg.getCurrSsoLogoutCall();
}
// 如果提供了当前 uri,则根据此值来计算:
else if(SaFoxUtil.isNotEmpty(currUri)) {
ssoLogoutCall = SaHolder.getRequest().getUrl().replace(currUri, apiName.ssoLogoutCall);
}
// 否则视为不注册单点注销回调地址
else {
}
}
// 构建请求URL
String checkUrl = ssoClientTemplate.buildCheckTicketUrl(ticket, ssoLogoutCall);
// 发起请求
SaResult result = ssoClientTemplate.request(checkUrl);
// 校验
if(result.getCode() != null && result.getCode() == SaResult.CODE_SUCCESS) {
return result.getData();
} else {
// 将 sso-server 回应的消息作为异常抛出
throw new SaSsoException(result.getMsg()).setCode(SaSsoErrorCode.CODE_30005);
}
} else {
// q2、使用模式二:直连Redis校验ticket
// return ssoClientTemplate.checkTicket(ticket);
return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, cfg.getClient());
}
}
/**
* 封装:单点注销成功后返回结果
* @param req SaRequest对象
* @param res SaResponse对象
* @return 返回结果
*/
public Object ssoLogoutBack(SaRequest req, SaResponse res) {
return SaSsoProcessorHelper.ssoLogoutBack(req, res, ssoClientTemplate.paramName);
}
}
@@ -0,0 +1,58 @@
/*
* 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.sso.processor;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* SSO 请求处理器,辅助方法
*
* @author click33
* @since 1.38.0
*/
public class SaSsoProcessorHelper {
/**
* 封装:单点注销成功后返回结果
* @param req SaRequest对象
* @param res SaResponse对象
* @return 返回结果
*/
public static Object ssoLogoutBack(SaRequest req, SaResponse res, ParamName paramName) {
/*
* 三种情况:
* 1. 有back参数,值为SELF -> 回退一级并刷新
* 2. 有back参数,值为url -> 跳转到此url地址
* 3. 无back参数 -> 返回json数据
*/
String back = req.getParam(paramName.back);
if(SaFoxUtil.isNotEmpty(back)) {
if(back.equals(SaSsoConsts.SELF)) {
return "<script>if(document.referrer != location.href){ location.replace(document.referrer || '/'); }</script>";
}
return res.redirect(back);
} else {
return SaResult.ok("单点注销成功");
}
}
}
@@ -0,0 +1,248 @@
/*
* 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.sso.processor;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.sso.name.ApiName;
import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.sso.template.SaSsoServerTemplate;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* SSO 请求处理器 Server端)
*
* @author click33
* @since 1.38.0
*/
public class SaSsoServerProcessor {
/**
* 全局默认实例
*/
public static SaSsoServerProcessor instance = new SaSsoServerProcessor();
/**
* 底层 SaSsoServerTemplate 对象
*/
public SaSsoServerTemplate ssoServerTemplate = new SaSsoServerTemplate();
// ----------- SSO-Server 端路由分发 -----------
/**
* 分发 Server 端所有请求
* @return 处理结果
*/
public Object dister() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoServerConfig cfg = ssoServerTemplate.getServerConfig();
ApiName apiName = ssoServerTemplate.apiName;
// ------------------ 路由分发 ------------------
// ---------- SSO-Server端:授权地址
if(req.isPath(apiName.ssoAuth)) {
return ssoAuth();
}
// ---------- SSO-Server端:RestAPI 登录接口
if(req.isPath(apiName.ssoDoLogin)) {
return ssoDoLogin();
}
// ---------- SSO-Server端:校验ticket 获取账号id
if(req.isPath(apiName.ssoCheckTicket) && cfg.getIsHttp()) {
return ssoCheckTicket();
}
// ---------- SSO-Server端:单点注销
if(req.isPath(apiName.ssoSignout)) {
return ssoSignout();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Server端:授权地址
* @return 处理结果
*/
public Object ssoAuth() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaSsoServerConfig cfg = ssoServerTemplate.getServerConfig();
StpLogic stpLogic = ssoServerTemplate.getStpLogic();
ParamName paramName = ssoServerTemplate.paramName;
// ---------- 此处有两种情况分开处理:
// ---- 情况1:在SSO认证中心尚未登录,需要先去登录
if( ! stpLogic.isLogin()) {
return cfg.notLoginView.get();
}
// ---- 情况2:在SSO认证中心已经登录,需要重定向回 Client 端,而这又分为两种方式:
String mode = req.getParam(paramName.mode, "");
// 方式1:直接重定向回Client端 (mode=simple)
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
String redirect = req.getParam(paramName.redirect);
ssoServerTemplate.checkRedirectUrl(redirect);
return res.redirect(redirect);
} else {
// 方式2:带着ticket参数重定向回Client端 (mode=ticket)
String redirectUrl = ssoServerTemplate.buildRedirectUrl(
stpLogic.getLoginId(), req.getParam(paramName.client), req.getParam(paramName.redirect));
return res.redirect(redirectUrl);
}
}
/**
* SSO-Server端:RestAPI 登录接口
* @return 处理结果
*/
public Object ssoDoLogin() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoServerConfig cfg = ssoServerTemplate.getServerConfig();
ParamName paramName = ssoServerTemplate.paramName;
// 处理
return cfg.doLoginHandle.apply(req.getParam(paramName.name), req.getParam(paramName.pwd));
}
/**
* SSO-Server端:校验ticket 获取账号id [模式三]
* @return 处理结果
*/
public Object ssoCheckTicket() {
ParamName paramName = ssoServerTemplate.paramName;
// 1、获取参数
SaRequest req = SaHolder.getRequest();
String client = req.getParam(paramName.client);
String ticket = req.getParamNotNull(paramName.ticket);
String sloCallback = req.getParam(paramName.ssoLogoutCall);
// 2、校验签名
if(ssoServerTemplate.getServerConfig().getIsCheckSign()) {
ssoServerTemplate.getSignTemplate(client).checkRequest(req,
paramName.client, paramName.ticket, paramName.ssoLogoutCall);
} else {
SaSsoManager.printNoCheckSignWarningByRuntime();
}
// 3、校验ticket,获取 loginId
Object loginId = ssoServerTemplate.checkTicket(ticket, client);
if(SaFoxUtil.isEmpty(loginId)) {
return SaResult.error("无效ticket" + ticket);
}
// 4、注册此客户端的单点注销回调URL
ssoServerTemplate.registerSloCallbackUrl(loginId, client, sloCallback);
// 5、给 client 端响应结果
return SaResult.data(loginId);
}
/**
* SSO-Server端:单点注销
* @return 处理结果
*/
public Object ssoSignout() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaSsoServerConfig cfg = ssoServerTemplate.getServerConfig();
ParamName paramName = ssoServerTemplate.paramName;
// SSO-Server端:单点注销 [用户访问式] (不带loginId参数)
if(cfg.getIsSlo() && ! req.hasParam(paramName.loginId)) {
return ssoSignoutByUserVisit();
}
// SSO-Server端:单点注销 [Client调用式] (带loginId参数 & isHttp=true)
if(cfg.getIsHttp() && cfg.getIsSlo() && req.hasParam(paramName.loginId)) {
return ssoSignoutByClientHttp();
}
// 默认返回
return SaSsoConsts.NOT_HANDLE;
}
/**
* SSO-Server端:单点注销 [用户访问式]
* @return 处理结果
*/
public Object ssoSignoutByUserVisit() {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
Object loginId = ssoServerTemplate.getStpLogic().getLoginIdDefaultNull();
// 单点注销
if(SaFoxUtil.isNotEmpty(loginId)) {
ssoServerTemplate.ssoLogout(loginId);
}
// 完成
return ssoLogoutBack(req, res);
}
/**
* SSO-Server端:单点注销 [Client调用式]
* @return 处理结果
*/
public Object ssoSignoutByClientHttp() {
ParamName paramName = ssoServerTemplate.paramName;
// 获取参数
SaRequest req = SaHolder.getRequest();
String loginId = req.getParam(paramName.loginId);
String client = req.getParam(paramName.client);
// step.1 校验签名
if(ssoServerTemplate.getServerConfig().getIsCheckSign()) {
ssoServerTemplate.getSignTemplate(client).checkRequest(req, paramName.loginId);
} else {
SaSsoManager.printNoCheckSignWarningByRuntime();
}
// step.2 单点注销
ssoServerTemplate.ssoLogout(loginId);
// 响应
return SaResult.ok();
}
/**
* 封装:单点注销成功后返回结果
* @param req SaRequest对象
* @param res SaResponse对象
* @return 返回结果
*/
public Object ssoLogoutBack(SaRequest req, SaResponse res) {
return SaSsoProcessorHelper.ssoLogoutBack(req, res, ssoServerTemplate.paramName);
}
}
@@ -0,0 +1,206 @@
/*
* 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.sso.template;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
import java.util.Map;
import java.util.TreeMap;
/**
* Sa-Token SSO 模板方法类 Client端)
*
* @author click33
* @since 1.38.0
*/
public class SaSsoClientTemplate extends SaSsoTemplate {
/**
* 获取底层使用的SsoClient配置对象
* @return /
*/
public SaSsoClientConfig getClientConfig() {
return SaSsoManager.getClientConfig();
}
// ------------------- SSO 模式三相关 -------------------
/**
* 根据配置的 getData 地址,查询数据
* @param paramMap 查询参数
* @return 查询结果
*/
public Object getData(Map<String, Object> paramMap) {
String getDataUrl = getClientConfig().splicingGetDataUrl();
return getData(getDataUrl, paramMap);
}
/**
* 根据自定义 path 地址,查询数据 (此方法需要配置 sa-token.sso.server-url 地址)
* @param path 自定义 path
* @param paramMap 查询参数
* @return 查询结果
*/
public Object getData(String path, Map<String, Object> paramMap) {
String url = buildCustomPathUrl(path, paramMap);
return getClientConfig().sendHttp.apply(url);
}
// ---------------------- 构建URL ----------------------
/**
* 构建URLServer端 单点登录地址
* @param clientLoginUrl Client端登录地址
* @param back 回调路径
* @return [SSO-Server端-认证地址 ]
*/
public String buildServerAuthUrl(String clientLoginUrl, String back) {
SaSsoClientConfig ssoConfig = getClientConfig();
// 服务端认证地址
String serverUrl = ssoConfig.splicingAuthUrl();
// 拼接客户端标识
String client = ssoConfig.getClient();
if(SaFoxUtil.isNotEmpty(client)) {
serverUrl = SaFoxUtil.joinParam(serverUrl, paramName.client, client);
}
// 对back地址编码
back = (back == null ? "" : back);
back = SaFoxUtil.encodeUrl(back);
// 开始拼接 sso 统一认证地址,形如:serverAuthUrl = http://xxx.com?redirectUrl=xxx.com?back=xxx.com
/*
* 部分 Servlet 版本 request.getRequestURL() 返回的 url 带有 query 参数,形如:http://domain.com?id=1
* 如果不加判断会造成最终生成的 serverAuthUrl 带有双 back 参数 ,这个 if 判断正是为了解决此问题
*/
if( ! clientLoginUrl.contains(paramName.back + "=" + back) ) {
clientLoginUrl = SaFoxUtil.joinParam(clientLoginUrl, paramName.back, back);
}
// 返回
return SaFoxUtil.joinParam(serverUrl, paramName.redirect, clientLoginUrl);
}
/**
* 构建URL:校验ticket的URL
* <p> 在模式三下,Client端拿到Ticket后根据此地址向Server端发送请求,获取账号id
* @param ticket ticket码
* @param ssoLogoutCallUrl 单点注销时的回调URL
* @return 构建完毕的URL
*/
public String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) {
SaSsoClientConfig ssoConfig = getClientConfig();
// 1、url
String url = ssoConfig.splicingCheckTicketUrl();
// 2、参数:client、ticket、ssoLogoutCall
Map<String, Object> paramMap = new TreeMap<>();
paramMap.put(paramName.ticket, ticket);
paramMap.put(paramName.client, ssoConfig.getClient());
paramMap.put(paramName.ssoLogoutCall, ssoLogoutCallUrl);
// 追加签名参数,并序列化为kv字符串
String signParamStr = getSignTemplate(ssoConfig.getClient()).addSignParamsAndJoin(paramMap);
// 3、拼接
return SaFoxUtil.joinParam(url, signParamStr);
}
/**
* 构建URL:单点注销URL
* @param loginId 要注销的账号id
* @return 单点注销URL
*/
public String buildSloUrl(Object loginId) {
// 获取所需对象
SaSsoClientConfig ssoConfig = getClientConfig();
String url = ssoConfig.splicingSloUrl();
String currClient = ssoConfig.getClient();
// 组织请求参数
Map<String, Object> paramMap = new TreeMap<>();
paramMap.put(paramName.loginId, loginId);
paramMap.put(paramName.client, currClient);
// 追加签名参数,并序列化为kv字符串
String signParamsStr = getSignTemplate(currClient).addSignParamsAndJoin(paramMap);
// 拼接到 url 上
return SaFoxUtil.joinParam(url, signParamsStr);
}
/**
* 构建URLServer端 getData 地址,带签名等参数
* @param paramMap 查询参数
* @return /
*/
public String buildGetDataUrl(Map<String, Object> paramMap) {
String getDataUrl = getClientConfig().getGetDataUrl();
return buildCustomPathUrl(getDataUrl, paramMap);
}
/**
* 构建URLServer 端自定义 path 地址,带签名等参数 (此方法需要配置 sa-token.sso.server-url 地址)
* @param paramMap 请求参数
* @return /
*/
public String buildCustomPathUrl(String path, Map<String, Object> paramMap) {
SaSsoClientConfig ssoConfig = getClientConfig();
// 构建 url
// 如果 path 不是以 http 开头,那么就拼接上 serverUrl
String url = path;
if( ! url.startsWith("http") ) {
String serverUrl = ssoConfig.getServerUrl();
SaSsoException.notEmpty(serverUrl, "请先配置 sa-token.sso.server-url 地址", SaSsoErrorCode.CODE_30012);
url = SaFoxUtil.spliceTwoUrl(serverUrl, path);
}
// 构建参数字符串
paramMap.put(paramName.client, ssoConfig.getClient());
String signParamsStr = getSignTemplate(ssoConfig.getClient()).addSignParamsAndJoin(paramMap);
// 拼接
return SaFoxUtil.joinParam(url, signParamsStr);
}
// ------------------- 发起请求 -------------------
/**
* 发出请求,并返回 SaResult 结果
* @param url 请求地址
* @return 返回的结果
*/
public SaResult request(String url) {
String body = getClientConfig().sendHttp.apply(url);
Map<String, Object> map = SaManager.getSaJsonTemplate().parseJsonToMap(body);
return new SaResult(map);
}
}
@@ -0,0 +1,414 @@
/*
* 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.sso.template;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.sso.util.SaSsoConsts;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.model.SaSsoClientModel;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.*;
/**
* Sa-Token SSO 模板方法类 Server端)
*
* @author click33
* @since 1.38.0
*/
public class SaSsoServerTemplate extends SaSsoTemplate {
/**
* 获取底层使用的SsoServer配置对象
* @return /
*/
public SaSsoServerConfig getServerConfig() {
return SaSsoManager.getServerConfig();
}
// ---------------------- Ticket 操作 ----------------------
/**
* 保存 Ticket 关联的 loginId
* @param ticket ticket码
* @param loginId 账号id
*/
public void saveTicket(String ticket, Object loginId) {
// 保存 ticket -> loginId 的关系
long ticketTimeout = getServerConfig().getTicketTimeout();
SaManager.getSaTokenDao().set(splicingTicketSaveKey(ticket), String.valueOf(loginId), ticketTimeout);
}
/**
* 保存 Ticket 索引 id 反查 ticket
* @param ticket ticket码
* @param loginId 账号id
*/
public void saveTicketIndex(String ticket, Object loginId) {
long ticketTimeout = getServerConfig().getTicketTimeout();
SaManager.getSaTokenDao().set(splicingTicketIndexKey(loginId), String.valueOf(ticket), ticketTimeout);
}
/**
* 保存 Ticket 关联的 client
* @param ticket ticket码
* @param client 客户端标识
*/
public void saveTicketToClient(String ticket, String client) {
if(SaFoxUtil.isEmpty(client)) {
return;
}
long ticketTimeout = getServerConfig().getTicketTimeout();
SaManager.getSaTokenDao().set(splicingTicketToClientSaveKey(ticket), client, ticketTimeout);
}
/**
* 删除 Ticket
* @param ticket Ticket码
*/
public void deleteTicket(String ticket) {
if(ticket == null) {
return;
}
SaManager.getSaTokenDao().delete(splicingTicketSaveKey(ticket));
}
/**
* 删除 Ticket索引
* @param loginId 账号id
*/
public void deleteTicketIndex(Object loginId) {
if(loginId == null) {
return;
}
SaManager.getSaTokenDao().delete(splicingTicketIndexKey(loginId));
}
/**
* 删除 Ticket 关联的 client
* @param ticket Ticket码
*/
public void deleteTicketToClient(String ticket) {
if(ticket == null) {
return;
}
SaManager.getSaTokenDao().delete(splicingTicketToClientSaveKey(ticket));
}
/**
* 查询 ticket 指向的 loginId,如果 ticket 码无效则返回 null
* @param ticket Ticket码
* @return 账号id
*/
public Object getLoginId(String ticket) {
if(SaFoxUtil.isEmpty(ticket)) {
return null;
}
return SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket));
}
/**
* 查询 ticket 指向的 loginId,并转换为指定类型
* @param <T> 要转换的类型
* @param ticket Ticket码
* @param cs 要转换的类型
* @return 账号id
*/
public <T> T getLoginId(String ticket, Class<T> cs) {
return SaFoxUtil.getValueByType(getLoginId(ticket), cs);
}
/**
* 查询 指定 loginId 其所属的 ticket 值
* @param loginId 账号id
* @return Ticket值
*/
public String getTicketValue(Object loginId) {
if(loginId == null) {
return null;
}
return SaManager.getSaTokenDao().get(splicingTicketIndexKey(loginId));
}
/**
* 查询 ticket 关联的 client,如果 ticket 码无效则返回 null
* @param ticket Ticket码
* @return 账号id
*/
public String getTicketToClient(String ticket) {
if(SaFoxUtil.isEmpty(ticket)) {
return null;
}
return SaManager.getSaTokenDao().get(splicingTicketToClientSaveKey(ticket));
}
//
/**
* 根据 账号id 创建一个 Ticket码
* @param loginId 账号id
* @param client 客户端标识
* @return Ticket码
*/
public String createTicket(Object loginId, String client) {
// 创建 Ticket
String ticket = randomTicket(loginId);
// 保存 Ticket
saveTicket(ticket, loginId);
saveTicketIndex(ticket, loginId);
saveTicketToClient(ticket, client);
// 返回 Ticket
return ticket;
}
/**
* 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除
* @param ticket Ticket码
* @return 账号id
*/
public Object checkTicket(String ticket) {
return checkTicket(ticket, null);
}
/**
* 校验 Ticket 码,获取账号id,如果此ticket是有效的,则立即删除
* @param ticket Ticket码
* @param client client 标识
* @return 账号id
*/
public Object checkTicket(String ticket, String client) {
// 读取 loginId
String loginId = SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket));
if(loginId != null) {
// 解析出这个 ticket 关联的 Client
String ticketClient = getTicketToClient(ticket);
// 如果指定了 client 标识,则校验一下 client 标识是否一致
if(SaFoxUtil.isNotEmpty(client) && SaFoxUtil.notEquals(client, ticketClient)) {
throw new SaSsoException("该 ticket 不属于 client=" + client + ", ticket 值: " + ticket)
.setCode(SaSsoErrorCode.CODE_30011);
}
// 删除 ticket 信息,使其只有一次性有效
deleteTicket(ticket);
deleteTicketIndex(loginId);
deleteTicketToClient(ticket);
}
//
return loginId;
}
/**
* 随机一个 Ticket码
* @param loginId 账号id
* @return Ticket码
*/
public String randomTicket(Object loginId) {
return SaFoxUtil.getRandomString(64);
}
/**
* 获取:所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
* @return see note
*/
public String getAllowUrl() {
// 默认从配置文件中返回
return getServerConfig().getAllowUrl();
}
/**
* 校验重定向url合法性
* @param url 下放ticket的url地址
*/
public void checkRedirectUrl(String url) {
// 1、是否是一个有效的url
if( ! SaFoxUtil.isUrl(url) ) {
throw new SaSsoException("无效redirect" + url).setCode(SaSsoErrorCode.CODE_30001);
}
// 2、截取掉?后面的部分
int qIndex = url.indexOf("?");
if(qIndex != -1) {
url = url.substring(0, qIndex);
}
// 3、是否在[允许地址列表]之中
List<String> authUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(","));
if( ! SaStrategy.instance.hasElement.apply(authUrlList, url) ) {
throw new SaSsoException("非法redirect" + url).setCode(SaSsoErrorCode.CODE_30002);
}
// 校验通过 √
}
// ------------------- SSO 模式三相关 -------------------
/**
* 为指定账号id注册单点注销回调URL
* @param loginId 账号id
* @param client 指定客户端标识,可为null
* @param sloCallbackUrl 单点注销时的回调URL
*/
public void registerSloCallbackUrl(Object loginId, String client, String sloCallbackUrl) {
// 如果提供的参数是空值,则直接返回,不进行任何操作
if(SaFoxUtil.isEmpty(loginId) || SaFoxUtil.isEmpty(sloCallbackUrl)) {
return;
}
SaSession session = getStpLogic().getSessionByLoginId(loginId);
// 取
List<SaSsoClientModel> scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new);
// 加
scmList.add(new SaSsoClientModel(client, sloCallbackUrl));
// 存
session.set(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, scmList);
}
/**
* 指定账号单点注销
* @param loginId 指定账号
*/
public void ssoLogout(Object loginId) {
// 如果这个账号尚未登录,则无操作
SaSession session = getStpLogic().getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
SaSsoServerConfig ssoConfig = getServerConfig();
// step.1 遍历通知 Client 端注销会话
List<SaSsoClientModel> scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new);
scmList.forEach(scm -> {
// url
String sloCallUrl = scm.getSsoLogoutCall();
// 参数
Map<String, Object> paramsMap = new TreeMap<>();
paramsMap.put(paramName.client, scm.getClient());
paramsMap.put(paramName.loginId, loginId);
String signParamsStr = getSignTemplate(scm.getClient()).addSignParamsAndJoin(paramsMap);
// 拼接
String finalUrl = SaFoxUtil.joinParam(sloCallUrl, signParamsStr);
// 发起请求
ssoConfig.sendHttp.apply(finalUrl);
});
// step.2 Server端注销
getStpLogic().logout(loginId);
}
// ---------------------- 构建URL ----------------------
/**
* 构建URLServer端向Client下放ticket的地址
* @param loginId 账号id
* @param client 客户端标识
* @param redirect Client端提供的重定向地址
* @return see note
*/
public String buildRedirectUrl(Object loginId, String client, String redirect) {
// 校验 重定向地址 是否合法
checkRedirectUrl(redirect);
// 删掉 旧Ticket
deleteTicket(getTicketValue(loginId));
// 创建 新Ticket
String ticket = createTicket(loginId, client);
// 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket
return SaFoxUtil.joinParam(encodeBackParam(redirect), paramName.ticket, ticket);
}
/**
* 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug
* @param url url
* @return 编码过后的url
*/
public String encodeBackParam(String url) {
// 获取back参数所在位置
int index = url.indexOf("?" + paramName.back + "=");
if(index == -1) {
index = url.indexOf("&" + paramName.back + "=");
if(index == -1) {
return url;
}
}
// 开始编码
int length = paramName.back.length() + 2;
String back = url.substring(index + length);
back = SaFoxUtil.encodeUrl(back);
// 放回url中
url = url.substring(0, index + length) + back;
return url;
}
// ------------------- 返回相应key -------------------
/**
* 拼接keyTicket 查 账号Id
* @param ticket ticket值
* @return key
*/
public String splicingTicketSaveKey(String ticket) {
return getStpLogic().getConfigOrGlobal().getTokenName() + ":ticket:" + ticket;
}
/**
* 拼接keyTicket 查 所属的 client
* @param ticket ticket值
* @return key
*/
public String splicingTicketToClientSaveKey(String ticket) {
return getStpLogic().getConfigOrGlobal().getTokenName() + ":ticket-client:" + ticket;
}
/**
* 拼接key:账号Id 反查 Ticket
* @param id 账号id
* @return key
*/
public String splicingTicketIndexKey(Object id) {
return getStpLogic().getConfigOrGlobal().getTokenName() + ":id-ticket:" + id;
}
}
@@ -0,0 +1,85 @@
/*
* 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.sso.template;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.sign.SaSignTemplate;
import cn.dev33.satoken.sso.name.ApiName;
import cn.dev33.satoken.sso.name.ParamName;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import java.util.Map;
/**
* Sa-Token SSO 模板方法类 (公共端)
*
* @author click33
* @since 1.30.0
*/
public class SaSsoTemplate {
// ---------------------- 全局配置 ----------------------
/**
* 所有 API 名称
*/
public ApiName apiName = new ApiName();
/**
* 所有参数名称
*/
public ParamName paramName = new ParamName();
/**
* @param paramName 替换 paramName 对象
* @return 对象自身
*/
public SaSsoTemplate setParamName(ParamName paramName) {
this.paramName = paramName;
return this;
}
/**
* @param apiName 替换 apiName 对象
* @return 对象自身
*/
public SaSsoTemplate setApiName(ApiName apiName) {
this.apiName = apiName;
return this;
}
/**
* 获取底层使用的会话对象
* @return /
*/
public StpLogic getStpLogic() {
return StpUtil.stpLogic;
}
/**
* 获取底层使用的 API 签名对象
* @param client 指定客户端标识,填 null 代表获取默认的
* @return /
*/
public SaSignTemplate getSignTemplate(String client) {
// 框架默认只返回全局 SaSignTemplateclient 参数留作开发者扩展
return SaManager.getSaSignTemplate();
}
}
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.sso;
package cn.dev33.satoken.sso.template;
import cn.dev33.satoken.util.SaResult;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
import java.util.Map;
@@ -27,12 +28,6 @@ import java.util.Map;
*/
public class SaSsoUtil {
/**
* 底层 SaSsoTemplate 对象
*/
public static SaSsoTemplate ssoTemplate = new SaSsoTemplate();
// ---------------------- Ticket 操作 ----------------------
/**
@@ -42,7 +37,7 @@ public class SaSsoUtil {
* @return Ticket码
*/
public static String createTicket(Object loginId, String client) {
return ssoTemplate.createTicket(loginId, client);
return SaSsoServerProcessor.instance.ssoServerTemplate.createTicket(loginId, client);
}
/**
@@ -50,7 +45,7 @@ public class SaSsoUtil {
* @param ticket Ticket码
*/
public static void deleteTicket(String ticket) {
ssoTemplate.deleteTicket(ticket);
SaSsoServerProcessor.instance.ssoServerTemplate.deleteTicket(ticket);
}
/**
@@ -58,7 +53,7 @@ public class SaSsoUtil {
* @param loginId 账号id
*/
public static void deleteTicketIndex(Object loginId) {
ssoTemplate.deleteTicketIndex(loginId);
SaSsoServerProcessor.instance.ssoServerTemplate.deleteTicketIndex(loginId);
}
/**
@@ -67,7 +62,7 @@ public class SaSsoUtil {
* @return 账号id
*/
public static Object getLoginId(String ticket) {
return ssoTemplate.getLoginId(ticket);
return SaSsoServerProcessor.instance.ssoServerTemplate.getLoginId(ticket);
}
/**
@@ -78,7 +73,7 @@ public class SaSsoUtil {
* @return 账号id
*/
public static <T> T getLoginId(String ticket, Class<T> cs) {
return ssoTemplate.getLoginId(ticket, cs);
return SaSsoServerProcessor.instance.ssoServerTemplate.getLoginId(ticket, cs);
}
/**
@@ -87,7 +82,7 @@ public class SaSsoUtil {
* @return 账号id
*/
public static Object checkTicket(String ticket) {
return ssoTemplate.checkTicket(ticket);
return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket);
}
/**
@@ -97,7 +92,7 @@ public class SaSsoUtil {
* @return 账号id
*/
public static Object checkTicket(String ticket, String client) {
return ssoTemplate.checkTicket(ticket, client);
return SaSsoServerProcessor.instance.ssoServerTemplate.checkTicket(ticket, client);
}
/**
@@ -105,7 +100,7 @@ public class SaSsoUtil {
* @return see note
*/
public static String getAllowUrl() {
return ssoTemplate.getAllowUrl();
return SaSsoServerProcessor.instance.ssoServerTemplate.getAllowUrl();
}
/**
@@ -113,7 +108,7 @@ public class SaSsoUtil {
* @param url 下放ticket的url地址
*/
public static void checkRedirectUrl(String url) {
ssoTemplate.checkRedirectUrl(url);
SaSsoServerProcessor.instance.ssoServerTemplate.checkRedirectUrl(url);
}
@@ -126,16 +121,17 @@ public class SaSsoUtil {
* @return 构建完毕的URL
*/
public static String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) {
return ssoTemplate.buildCheckTicketUrl(ticket, ssoLogoutCallUrl);
return SaSsoClientProcessor.instance.ssoClientTemplate.buildCheckTicketUrl(ticket, ssoLogoutCallUrl);
}
/**
* 为指定账号id注册单点注销回调URL
* @param loginId 账号id
* @param client 指定客户端标识可为null
* @param sloCallbackUrl 单点注销时的回调URL
*/
public static void registerSloCallbackUrl(Object loginId, String sloCallbackUrl) {
ssoTemplate.registerSloCallbackUrl(loginId, sloCallbackUrl);
public static void registerSloCallbackUrl(Object loginId, String client, String sloCallbackUrl) {
SaSsoServerProcessor.instance.ssoServerTemplate.registerSloCallbackUrl(loginId, client, sloCallbackUrl);
}
/**
@@ -144,7 +140,7 @@ public class SaSsoUtil {
* @return 单点注销URL
*/
public static String buildSloUrl(Object loginId) {
return ssoTemplate.buildSloUrl(loginId);
return SaSsoClientProcessor.instance.ssoClientTemplate.buildSloUrl(loginId);
}
/**
@@ -152,7 +148,7 @@ public class SaSsoUtil {
* @param loginId 指定账号
*/
public static void ssoLogout(Object loginId) {
ssoTemplate.ssoLogout(loginId);
SaSsoServerProcessor.instance.ssoServerTemplate.ssoLogout(loginId);
}
/**
@@ -161,7 +157,7 @@ public class SaSsoUtil {
* @return 查询结果
*/
public static Object getData(Map<String, Object> paramMap) {
return ssoTemplate.getData(paramMap);
return SaSsoClientProcessor.instance.ssoClientTemplate.getData(paramMap);
}
/**
@@ -171,7 +167,7 @@ public class SaSsoUtil {
* @return 查询结果
*/
public static Object getData(String path, Map<String, Object> paramMap) {
return ssoTemplate.getData(path, paramMap);
return SaSsoClientProcessor.instance.ssoClientTemplate.getData(path, paramMap);
}
@@ -184,7 +180,7 @@ public class SaSsoUtil {
* @return [SSO-Server端-认证地址 ]
*/
public static String buildServerAuthUrl(String clientLoginUrl, String back) {
return ssoTemplate.buildServerAuthUrl(clientLoginUrl, back);
return SaSsoClientProcessor.instance.ssoClientTemplate.buildServerAuthUrl(clientLoginUrl, back);
}
/**
@@ -195,7 +191,7 @@ public class SaSsoUtil {
* @return see note
*/
public static String buildRedirectUrl(Object loginId, String client, String redirect) {
return ssoTemplate.buildRedirectUrl(loginId, client, redirect);
return SaSsoServerProcessor.instance.ssoServerTemplate.buildRedirectUrl(loginId, client, redirect);
}
/**
@@ -204,7 +200,7 @@ public class SaSsoUtil {
* @return /
*/
public static String buildGetDataUrl(Map<String, Object> paramMap) {
return ssoTemplate.buildGetDataUrl(paramMap);
return SaSsoClientProcessor.instance.ssoClientTemplate.buildGetDataUrl(paramMap);
}
/**
@@ -213,62 +209,8 @@ public class SaSsoUtil {
* @return /
*/
public static String buildCustomPathUrl(String path, Map<String, Object> paramMap) {
return ssoTemplate.buildCustomPathUrl(path, paramMap);
return SaSsoClientProcessor.instance.ssoClientTemplate.buildCustomPathUrl(path, paramMap);
}
// ------------------- 发起请求 -------------------
/**
* 发出请求并返回 SaResult 结果
* @param url 请求地址
* @return 返回的结果
*/
public static SaResult request(String url) {
return ssoTemplate.request(url);
}
/**
* paramMap 追加 sign 等参数并序列化为kv字符串拼接到url后面
* @param url 请求地址
* @param paramMap 请求原始参数列表
* @return 加工后的url
*/
public static String joinParamMapAndSign(String url, Map<String, Object> paramMap) {
return ssoTemplate.joinLoginIdAndSign(url, paramMap);
}
/**
* url 拼接 loginId 参数并拼接 sign 等参数
* @param url 链接
* @param loginId 账号id
* @return 加工后的url
*/
public static String joinLoginIdAndSign(String url, Object loginId) {
return ssoTemplate.joinLoginIdAndSign(url, loginId);
}
// -------- 以下方法已废弃仅为兼容旧版本而保留 --------
/**
* 构建URLServer端 账号资料查询地址
* @param loginId 账号id
* @return Server端 账号资料查询地址
*/
@Deprecated
public static String buildUserinfoUrl(Object loginId) {
return ssoTemplate.buildUserinfoUrl(loginId);
}
/**
* 获取账号资料
* @param loginId 账号id
* @return 账号资料
*/
@Deprecated
public static Object getUserinfo(Object loginId) {
return ssoTemplate.getUserinfo(loginId);
}
}
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cn.dev33.satoken.sso;
package cn.dev33.satoken.sso.util;
/**
* Sa-Token-SSO模块相关常量
@@ -24,8 +24,12 @@ package cn.dev33.satoken.sso;
public class SaSsoConsts {
/** Client端单点注销回调URL的Set集合,存储在Session中使用的key */
@Deprecated
public static final String SLO_CALLBACK_SET_KEY = "SLO_CALLBACK_SET_KEY_";
/** Client 端 Model 信息的 List 集合,存储在 SaSession 中使用的key */
public static final String SSO_CLIENT_MODEL_LIST_KEY_ = "SSO_CLIENT_MODEL_LIST_KEY_";
/** 表示OK的返回结果 */
public static final String OK = "ok";
@@ -18,8 +18,8 @@ package cn.dev33.satoken.solon.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.sso.SaSsoTemplate;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.sso.template.SaSsoTemplate;
import cn.dev33.satoken.sso.template.SaSsoUtil;
import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Condition;
import org.noear.solon.annotation.Configuration;
@@ -15,17 +15,18 @@
*/
package cn.dev33.satoken.spring.sso;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
import cn.dev33.satoken.sso.template.SaSsoClientTemplate;
import cn.dev33.satoken.sso.template.SaSsoServerTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.sso.SaSsoTemplate;
import cn.dev33.satoken.sso.SaSsoUtil;
/**
* 注入 Sa-Token-SSO 所需要的 Bean
* 注入 Sa-Token SSO 所需要的 Bean
*
* @author click33
* @since 1.34.0
@@ -34,24 +35,43 @@ import cn.dev33.satoken.sso.SaSsoUtil;
public class SaSsoBeanInject {
/**
* 注入 Sa-Token-SSO 配置类
* 注入 Sa-Token SSO Server 端 配置类
*
* @param saSsoConfig 配置对象
* @param serverConfig 配置对象
*/
@Autowired(required = false)
public void setSaOAuth2Config(SaSsoConfig saSsoConfig) {
SaSsoManager.setConfig(saSsoConfig);
public void setSaSsoServerConfig(SaSsoServerConfig serverConfig) {
SaSsoManager.setServerConfig(serverConfig);
}
/**
* 注入 SSO 模板代码
*
* @param ssoTemplate SaSsoTemplate 对象
* 注入 Sa-Token SSO Client 端 配置
*
* @param clientConfig 配置对象
*/
@Autowired(required = false)
public void setSaSsoTemplate(SaSsoTemplate ssoTemplate) {
SaSsoUtil.ssoTemplate = ssoTemplate;
SaSsoProcessor.instance.ssoTemplate = ssoTemplate;
public void setSaSsoClientConfig(SaSsoClientConfig clientConfig) {
SaSsoManager.setClientConfig(clientConfig);
}
/**
* 注入 SSO 模板代码类 (Server 端)
*
* @param ssoServerTemplate /
*/
@Autowired(required = false)
public void setSaSsoServerTemplate(SaSsoServerTemplate ssoServerTemplate) {
SaSsoServerProcessor.instance.ssoServerTemplate = ssoServerTemplate;
}
/**
* 注入 SSO 模板代码类 (Client 端)
*
* @param ssoClientTemplate /
*/
@Autowired(required = false)
public void setSaSsoClientTemplate(SaSsoClientTemplate ssoClientTemplate) {
SaSsoClientProcessor.instance.ssoClientTemplate = ssoClientTemplate;
}
}
@@ -15,15 +15,15 @@
*/
package cn.dev33.satoken.spring.sso;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.sso.SaSsoManager;
/**
* 注册 Sa-Token-SSO 所需要的 Bean
* 注册 Sa-Token SSO 所需要的 Bean
*
* @author click33
* @since 1.34.0
@@ -32,13 +32,23 @@ import cn.dev33.satoken.sso.SaSsoManager;
public class SaSsoBeanRegister {
/**
* 获取 SSO 配置对象
* @return 配置对象
* 获取 SSO Server 端 配置对象
* @return 配置对象
*/
@Bean
@ConfigurationProperties(prefix = "sa-token.sso")
public SaSsoConfig getSaSsoConfig() {
return new SaSsoConfig();
@ConfigurationProperties(prefix = "sa-token.sso-server")
public SaSsoServerConfig getSaSsoServerConfig() {
return new SaSsoServerConfig();
}
/**
* 获取 SSO Client 端 配置对象
* @return 配置对象
*/
@Bean
@ConfigurationProperties(prefix = "sa-token.sso-client")
public SaSsoClientConfig getSaSsoClientConfig() {
return new SaSsoClientConfig();
}
}
@@ -17,7 +17,7 @@ package cn.dev33.satoken.integrate.configure.inject;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.sso.SaSsoTemplate;
import cn.dev33.satoken.sso.template.SaSsoTemplate;
/**
* 自定义 Sa-SSO 模板方法