feat(sso): 调整 SSO 示例适配最新版 & 新增 sso-resdk 示例 & 新增 sso-anon 示例

This commit is contained in:
click33 2025-05-06 23:26:49 +08:00
parent 213d98d848
commit 2ecd52b3be
62 changed files with 1090 additions and 359 deletions

View File

@ -149,8 +149,10 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
* @return 对象自身
*/
public SaResult setMap(Map<String, ?> map) {
for (String key : map.keySet()) {
this.put(key, map.get(key));
if(map != null) {
for (String key : map.keySet()) {
this.put(key, map.get(key));
}
}
return this;
}

View File

@ -44,8 +44,9 @@
<module>sa-token-demo-sso/sa-token-demo-sso1-client</module>
<module>sa-token-demo-sso/sa-token-demo-sso2-client</module>
<module>sa-token-demo-sso/sa-token-demo-sso3-client</module>
<module>sa-token-demo-sso/sa-token-demo-sso3-client-test2</module>
<module>sa-token-demo-sso/sa-token-demo-sso3-client-nosdk</module>
<module>sa-token-demo-sso/sa-token-demo-sso3-client-resdk</module>
<module>sa-token-demo-sso/sa-token-demo-sso3-client-anon</module>
<module>sa-token-demo-sso-for-solon/sa-token-demo-sso-server-solon</module>
<module>sa-token-demo-sso-for-solon/sa-token-demo-sso1-client-solon</module>
<module>sa-token-demo-sso-for-solon/sa-token-demo-sso2-client-solon</module>

View File

@ -8,22 +8,59 @@
<h2>Sa-Token SSO-Client 应用端(前后端分离版-原生h5</h2>
<p>当前是否登录:<b class="is-login"></b></p>
<p>
<a class="login-btn">登录</a>
<a class="logout-btn">注销</a>
<a href="javascript: login();">登录</a> -
<a href="javascript: doLogoutByAlone();">单应用注销</a> -
<a href="javascript: doLogoutBySingleDeviceId();">单浏览器注销</a> -
<a href="javascript: doLogout();">全端注销</a> -
<a href="javascript: doMyInfo();">账号资料</a>
</p>
<script src="common.js"></script>
<script src="sso-common.js"></script>
<script type="text/javascript">
document.querySelector('.login-btn').href = 'sso-login.html?back=' + encodeURIComponent(location.href);
document.querySelector('.logout-btn').href = baseUrl + '/sso/logout?satoken=' + localStorage.satoken + '&back=' + encodeURIComponent(location.href);
// 登录
function login() {
location.href = 'sso-login.html?back=' + encodeURIComponent(location.href);
}
ajax('/sso/isLogin', {}, function(res){
if(res.data) {
setHtml('.is-login', res.data + ' (' + res.loginId + ')');
} else {
setHtml('.is-login', res.data);
}
})
// 单应用注销
function doLogoutByAlone() {
ajax('/sso/logoutByAlone', {}, function(res){
doIsLogin();
})
}
// 单浏览器注销
function doLogoutBySingleDeviceId() {
ajax('/sso/logout', { singleDeviceIdLogout: true }, function(res){
doIsLogin();
})
}
// 全端注销
function doLogout() {
ajax('/sso/logout', { }, function(res){
doIsLogin();
})
}
// 账号资料
function doMyInfo() {
ajax('/sso/myInfo', { }, function(res){
alert(JSON.stringify(res));
})
}
// 判断是否登录
function doIsLogin() {
ajax('/sso/isLogin', {}, function(res){
if(res.data) {
setHtml('.is-login', res.data + ' (' + res.loginId + ')');
} else {
setHtml('.is-login', res.data);
}
})
}
doIsLogin();
</script>
</body>

View File

@ -1,5 +1,6 @@
// 服务器接口主机地址
var baseUrl = "http://sa-sso-client1.com:9003";
// var baseUrl = "http://sa-sso-client1.com:9002"; // 模式二后端
var baseUrl = "http://sa-sso-client1.com:9003"; // 模式三后端
// 封装一下Ajax
function ajax(path, data, successFn, errorFn) {

View File

@ -2,16 +2,16 @@
<html>
<head>
<meta charset="utf-8">
<title>Sa-Token-SSO-Client端-登录页</title>
<title>Sa-Token-SSO-Client端-登录中转页</title>
<style type="text/css">
</style>
</head>
<body>
<div class="login-box">
加载中 ...
</div>
<script src="common.js"></script>
<script src="sso-common.js"></script>
<script type="text/javascript">
var back = getParam('back', '/');

View File

@ -1,10 +1,12 @@
import axios from 'axios'
// sso-client 的后端服务地址
export const baseUrl = "http://sa-sso-client1.com:9001";
// export const baseUrl = "http://sa-sso-client1.com:9002"; // 模式二后端
export const baseUrl = "http://sa-sso-client1.com:9003"; // 模式三后端
// 封装一下 Ajax 方法
export const ajax = function(path, data, successFn) {
console.log('发起请求:', baseUrl + path, JSON.stringify(data));
axios({
url: baseUrl + path,
method: 'post',
@ -16,9 +18,14 @@ export const ajax = function(path, data, successFn) {
}).
then(function (response) { // 成功时执行
const res = response.data;
console.log('返回数据:', res);
if(res.code === 500) {
return alert(res.msg);
}
successFn(res);
}).
catch(function (error) {
console.error('请求失败:', error);
return alert("异常:" + JSON.stringify(error));
})
}

View File

@ -2,35 +2,76 @@
<template>
<div>
<h2> Sa-Token SSO-Client 应用端前后端分离版-Vue2 </h2>
<p>当前是否登录<b>{{isLogin}}</b></p>
<p>当前是否登录<b>{{isLogin}} ({{ loginId }})</b></p>
<p>
<router-link :to="loginUrl">登录</router-link>&nbsp;&nbsp;
<a :href="logoutUrl">注销</a>
<a href="javascript: null;" @click="login()" >登录</a> -
<a href="javascript: null;" @click="doLogoutByAlone()" >单应用注销</a> -
<a href="javascript: null;" @click="doLogoutBySingleDeviceId();">单浏览器注销</a> -
<a href="javascript: null;" @click="doLogout();">全端注销</a> -
<a href="javascript: null;" @click="doMyInfo();">账号资料</a>
</p>
</div>
</template>
<script>
import {baseUrl, ajax} from './method-util.js'
import {ajax} from './sso-common.js'
import router from "@/router";
export default {
name: 'App',
data() {
return {
//
loginUrl: '/sso-login?back=' + encodeURIComponent(location.href),
//
logoutUrl: baseUrl + '/sso/logout?satoken=' + localStorage.satoken + '&back=' + encodeURIComponent(location.href),
//
isLogin: false
isLogin: false,
//
loginId: ''
}
},
methods: {
//
login: function() {
router.push('/sso-login?back=' + encodeURIComponent(location.href));
},
//
doLogoutByAlone: function() {
ajax('/sso/logoutByAlone', {}, function(){
this.doIsLogin();
}.bind(this))
},
//
doLogoutBySingleDeviceId: function() {
ajax('/sso/logout', { singleDeviceIdLogout: true }, function(){
this.doIsLogin();
}.bind(this))
},
//
doLogout: function () {
ajax('/sso/logout', { }, function(){
this.doIsLogin();
}.bind(this))
},
//
doMyInfo: function() {
ajax('/sso/myInfo', { }, function(res){
alert(JSON.stringify(res));
})
},
//
doIsLogin: function() {
ajax('/sso/isLogin', {}, function(res){
this.isLogin = res.data;
this.loginId = res.loginId;
}.bind(this))
}
},
created() {
//
ajax('/sso/isLogin', {}, function (res) {
console.log('/isLogin 返回数据:', res);
this.isLogin = res.data;
}.bind(this))
this.doIsLogin();
}
}
</script>

View File

@ -1,10 +1,10 @@
<!-- Sa-Token-SSO-Client端-登录-->
<!-- Sa-Token-SSO-Client端-登录中转-->
<template>
<div></div>
<div>加载中...</div>
</template>
<script>
import {ajax, getParam} from './method-util.js';
import {ajax, getParam} from './sso-common.js';
import router from '../router';
@ -31,14 +31,12 @@ export default {
//
goSsoAuthUrl: function() {
ajax('/sso/getSsoAuthUrl', {clientLoginUrl: location.href}, function(res) {
console.log('/sso/getSsoAuthUrl 返回数据', res);
location.href = res.data;
})
},
// ticket
doLoginByTicket: function(ticket) {
ajax('/sso/doLoginByTicket', {ticket: ticket}, function(res) {
console.log('/sso/doLoginByTicket 返回数据', res);
if(res.code === 200) {
localStorage.setItem('satoken', res.data);
location.href = decodeURIComponent(this.back);

View File

@ -1,10 +1,10 @@
import { createRouter, createWebHashHistory } from 'vue-router';
import { createRouter, createWebHistory } from 'vue-router';
/**
* 创建 vue-router 实例
*/
const router = createRouter({
history: createWebHashHistory(),
history: createWebHistory(),
routes: [
// 首页
{

View File

@ -1,10 +1,12 @@
import axios from 'axios'
// sso-client 的后端服务地址
export const baseUrl = "http://sa-sso-client1.com:9001";
// export const baseUrl = "http://sa-sso-client1.com:9002"; // 模式二后端
export const baseUrl = "http://sa-sso-client1.com:9003"; // 模式三后端
// 封装一下 Ajax 方法
export const ajax = function(path, data, successFn) {
console.log('发起请求:', baseUrl + path, JSON.stringify(data));
axios({
url: baseUrl + path,
method: 'post',
@ -16,9 +18,14 @@ export const ajax = function(path, data, successFn) {
}).
then(function (response) { // 成功时执行
const res = response.data;
console.log('返回数据:', res);
if(res.code === 500) {
return alert(res.msg);
}
successFn(res);
}).
catch(function (error) {
console.error('请求失败:', error);
return alert("异常:" + JSON.stringify(error));
})
}

View File

@ -1,29 +1,70 @@
<!-- 项目首页 -->
<template>
<h2> Sa-Token SSO-Client 应用端前后端分离版-Vue3 </h2>
<p>当前是否登录<b>{{isLogin}}</b></p>
<p>
<router-link :to="loginUrl">登录</router-link>&nbsp;&nbsp;
<a :href="logoutUrl">注销</a>
</p>
<div>
<h2> Sa-Token SSO-Client 应用端前后端分离版-Vue3 </h2>
<p>当前是否登录<b>{{ state.isLogin }} ({{ state.loginId }})</b></p>
<p>
<a href="javascript: null;" @click="login()" >登录</a> -
<a href="javascript: null;" @click="doLogoutByAlone()" >单应用注销</a> -
<a href="javascript: null;" @click="doLogoutBySingleDeviceId();">单浏览器注销</a> -
<a href="javascript: null;" @click="doLogout();">全端注销</a> -
<a href="javascript: null;" @click="doMyInfo();">账号资料</a>
</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import {baseUrl, ajax} from './method-util.js'
import { reactive } from 'vue'
import { ajax } from './sso-common.js'
import router from "../router/index.js";
//
const loginUrl = '/sso-login?back=' + encodeURIComponent(location.href);
//
const logoutUrl = baseUrl + '/sso/logout?satoken=' + localStorage.satoken + '&back=' + encodeURIComponent(location.href);
//
const isLogin = ref(false);
//
ajax('/sso/isLogin', {}, function (res) {
console.log('/isLogin 返回数据:', res);
isLogin.value = res.data;
//
const state = reactive({
isLogin: false,
loginId: '',
})
//
function login() {
router.push('/sso-login?back=' + encodeURIComponent(location.href));
}
//
function doLogoutByAlone() {
ajax('/sso/logoutByAlone', {}, function(res){
doIsLogin();
})
}
//
function doLogoutBySingleDeviceId() {
ajax('/sso/logout', { singleDeviceIdLogout: true }, function(res){
doIsLogin();
})
}
//
function doLogout() {
ajax('/sso/logout', { }, function(res){
doIsLogin();
})
}
//
function doMyInfo() {
ajax('/sso/myInfo', { }, function(res){
alert(JSON.stringify(res));
})
}
//
function doIsLogin() {
ajax('/sso/isLogin', {}, function(res){
state.isLogin = res.data;
state.loginId = res.loginId;
})
}
doIsLogin();
</script>

View File

@ -1,11 +1,11 @@
<!-- Sa-Token-SSO-Client端-登录页 -->
<template>
<div>加载中...</div>
</template>
<script setup>
import {onMounted} from "vue";
import {ajax, getParam} from './method-util.js';
import {ajax, getParam} from './sso-common.js';
import router from '../router';
//
@ -27,7 +27,6 @@ onMounted(() => {
//
function goSsoAuthUrl() {
ajax('/sso/getSsoAuthUrl', {clientLoginUrl: location.href}, function(res) {
console.log('/sso/getSsoAuthUrl 返回数据', res);
location.href = res.data;
})
}
@ -35,7 +34,6 @@ function goSsoAuthUrl() {
// ticket
function doLoginByTicket(ticket) {
ajax('/sso/doLoginByTicket', {ticket: ticket}, function(res) {
console.log('/sso/doLoginByTicket 返回数据', res);
if(res.code === 200) {
localStorage.setItem('satoken', res.data);
location.href = decodeURIComponent(back);

View File

@ -14,7 +14,7 @@ public class SaSsoServerApplication {
System.out.println("---------------------- Sa-Token SSO 统一认证中心启动成功 ----------------------");
System.out.println("配置信息:" + SaSsoManager.getServerConfig());
System.out.println("统一认证登录地址http://sa-sso-server.com:9000/sso/auth");
System.out.println("测试前需要根据官网文档修改hosts文件测试账号密码sa / 123456");
System.out.println("测试前需要根据官网文档修改 hosts 文件测试账号密码sa / 123456");
System.out.println();
}

View File

@ -1,4 +1,4 @@
package com.pj.home;
package com.pj.sso;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.stp.StpUtil;

View File

@ -1,7 +1,6 @@
package com.pj.sso;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.sign.SaSignUtil;
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
import cn.dev33.satoken.sso.template.SaSsoServerTemplate;
import cn.dev33.satoken.stp.StpUtil;
@ -23,10 +22,9 @@ public class SsoServerController {
/**
* SSO-Server端处理所有SSO相关请求
* http://{host}:{port}/sso/auth -- 单点登录授权地址接受参数redirect=授权重定向地址
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口接受参数namepwd
* http://{host}:{port}/sso/checkTicket -- Ticket校验接口isHttp=true时打开接受参数ticket=ticket码ssoLogoutCall=单点注销回调地址 [可选]
* http://{host}:{port}/sso/signout -- 单点注销地址isSlo=true时打开接受参数loginId=账号idsign=参数签名
* http://{host}:{port}/sso/auth -- 单点登录授权地址
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口接受参数namepwd
* http://{host}:{port}/sso/signout -- 单点注销地址isSlo=true时打开
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
@ -53,24 +51,19 @@ public class SsoServerController {
return SaResult.error("登录失败!");
};
}
// 添加消息处理器userinfo (获取用户资料) 用于在模式三下 client 端开放拉取数据的接口
ssoServerTemplate.messageHolder.addHandle("userinfo", (ssoTemplate, message) -> {
System.out.println("收到消息:" + message);
System.out.println("loginId=" + message.get("loginId"));
// 示例获取数据接口用于在模式三下 client 端开放拉取数据的接口
@RequestMapping("/sso/getData")
public SaResult getData(String apiType, String loginId) {
System.out.println("---------------- 获取数据 ----------------");
System.out.println("apiType=" + apiType);
System.out.println("loginId=" + loginId);
// 自定义返回结果模拟
return SaResult.ok()
.set("id", message.get("loginId"))
.set("name", "LinXiaoYu")
.set("sex", "")
.set("age", 18);
});
// 校验签名只有拥有正确秘钥发起的请求才能通过校验
SaSignUtil.checkRequest(SaHolder.getRequest());
// 自定义返回结果模拟
return SaResult.ok()
.set("id", loginId)
.set("name", "LinXiaoYu")
.set("sex", "")
.set("age", 18);
}
}

View File

@ -7,43 +7,57 @@ sa-token:
# 打印操作日志
is-log: true
# ------- SSO-模式一相关配置 (非模式一不需要配置)
# cookie:
# 配置 Cookie 作用域
# domain: stp.com
# ------- SSO 模式一配置 (非模式一不需要配置)
# cookie:
# # 配置 Cookie 作用域
# domain: stp.com
# ------- SSO-模式二相关配置
# SSO-Server 配置
sso-server:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址
# 主页路由,在不指定 redirect 参数时,默认跳转的地址
home-route: /home
# 是否启用匿名 client (开启匿名 client 后,允许客户端接入时不提交 client 参数)
allow-anon-client: true
# 所有允许的授权回调地址 (匿名 client 使用)
allow-url: "*"
# API 接口调用秘钥 (默认)
# API 接口调用秘钥 (全局默认 + 匿名 client 使用)
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# 是否启用匿名 client
allow-anon-client: false
# ------- SSO-模式三相关配置 下面的配置在使用SSO模式三时打开
# 是否打开模式三
is-http: true
# 应用列表:配置接入的应用信息
clients:
# 应用 sso-client2采用模式二对接 (跨域、同Redis)
# 应用 sso-client2采用模式二对接 (跨域、同Redis)
sso-client2:
client: sso-client2
allow-url: "*"
# 应用 sso-client3采用模式三对接 (跨域、跨Redis)
secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# 应用 sso-client3采用模式三对接 (跨域、跨Redis)
sso-client3:
# 应用名称
client: sso-client3
# 允许授权地址
allow-url: "*"
secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# 是否接收消息推送
is-push: true
# 消息推送地址
push-url: http://sa-sso-client1.com:9003/sso/pushC
# ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器文档有步骤说明
# 接口调用秘钥 (如果不配置则使用全局默认秘钥)
secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# 应用 sso-client3-resdk采用 ReSdk 模式对接
sso-client3-resdk:
# 应用名称
client: sso-client3-resdk
# 允许授权地址
allow-url: "*"
# 是否接收消息推送
is-push: true
# 消息推送地址
push-url: http://sa-sso-client1.com:9005/sso/pushC
# 接口调用秘钥 (如果不配置则使用全局默认秘钥)
secret-key: SSO-C3-ReSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
spring:
# Redis配置 SSO模式一和模式二使用Redis来同步会话
# Redis配置 SSO模式一和模式二使用 Redis 来同步会话)
redis:
# Redis数据库索引默认为0
database: 1

View File

@ -21,7 +21,7 @@ public class SaSso1ClientApplication {
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("测试前需要根据官网文档修改 hosts 文件测试账号密码sa / 123456");
System.out.println();
}

View File

@ -18,11 +18,11 @@ public class SsoClientController {
@RequestMapping("/")
public String index() {
String authUrl = SaSsoManager.getClientConfig().splicingAuthUrl();
String solUrl = SaSsoManager.getClientConfig().splicingSignoutUrl();
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
String signoutUrl = SaSsoManager.getClientConfig().splicingSignoutUrl();
String str = "<h2>Sa-Token SSO-Client 应用端 (模式一)</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")</p>" +
"<p><a href=\"javascript:location.href='" + authUrl + "?mode=simple&redirect=' + encodeURIComponent(location.href);\">登录</a> " +
"<a href=\"javascript:location.href='" + solUrl + "?back=' + encodeURIComponent(location.href);\">注销</a> </p>";
"<a href=\"javascript:location.href='" + signoutUrl + "?back=' + encodeURIComponent(location.href);\">注销</a> </p>";
return str;
}

View File

@ -9,7 +9,7 @@ sa-token:
# SSO-Server端主机地址
server-url: http://sso.stp.com:9000
# 配置 Sa-Token 单独使用的Redis连接 此处需要和SSO-Server端连接同一个Redis
# 配置 Sa-Token 单独使用的Redis连接 (需要引入 sa-token-alone-redis 依赖) (此处需要和 SSO-Server 端连接同一个Redis
alone-redis:
# Redis数据库索引
database: 1

View File

@ -61,11 +61,11 @@
<version>${sa-token.version}</version>
</dependency>
<!-- Http请求工具 -->
<!-- Sa-Token 插件:整合 Forest 请求工具 -->
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-spring-boot-starter</artifactId>
<version>1.5.26</version>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-forest</artifactId>
<version>${sa-token.version}</version>
</dependency>
</dependencies>

View File

@ -16,7 +16,7 @@ public class SaSso2ClientApplication {
System.out.println("测试访问应用端一: http://sa-sso-client1.com:9002");
System.out.println("测试访问应用端二: http://sa-sso-client2.com:9002");
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9002");
System.out.println("测试前需要根据官网文档修改hosts文件测试账号密码sa / 123456");
System.out.println("测试前需要根据官网文档修改 hosts 文件测试账号密码sa / 123456");
System.out.println();
}

View File

@ -2,11 +2,10 @@ package com.pj.h5;
import cn.dev33.satoken.sso.model.SaCheckTicketResult;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoUtil;
import cn.dev33.satoken.sso.template.SaSsoClientUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
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;
@ -28,14 +27,14 @@ public class H5Controller {
// 返回SSO认证中心登录地址
@RequestMapping("/sso/getSsoAuthUrl")
public SaResult getSsoAuthUrl(String clientLoginUrl) {
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
String serverAuthUrl = SaSsoClientUtil.buildServerAuthUrl(clientLoginUrl, "");
return SaResult.data(serverAuthUrl);
}
// 根据ticket进行登录
@RequestMapping("/sso/doLoginByTicket")
public SaResult doLoginByTicket(String ticket) {
SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket, "/sso/doLoginByTicket");
SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket);
StpUtil.login(ctr.loginId, new SaLoginParameter()
.setTimeout(ctr.remainTokenTimeout)
.setDeviceId(ctr.deviceId)
@ -43,11 +42,4 @@ public class H5Controller {
return SaResult.data(StpUtil.getTokenValue());
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@ -7,7 +7,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* [Sa-Token 权限认证] 配置类 解决跨域问题
* [Sa-Token 权限认证] 配置类
*
* @author click33
*/

View File

@ -0,0 +1,22 @@
package com.pj.sso;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
* @author click33
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@ -1,12 +1,13 @@
package com.pj.sso;
import cn.dev33.satoken.sso.SaSsoManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.sso.message.SaSsoMessage;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoClientTemplate;
import cn.dev33.satoken.sso.template.SaSsoClientUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -20,19 +21,23 @@ public class SsoClientController {
// 首页
@RequestMapping("/")
public String index() {
String solUrl = SaSsoManager.getClientConfig().splicingSignoutUrl();
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a> " +
"<a href=\"javascript:location.href='" + solUrl + "?back=' + encodeURIComponent(location.href);\">注销</a> </p>";
String str = "<h2>Sa-Token SSO-Client 应用端 (模式二)</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")</p>" +
"<p> " +
"<a href='/sso/login?back=/'>登录</a> - " +
"<a href='/sso/logoutByAlone?back=/'>单应用注销</a> - " +
"<a href='/sso/logout?back=self&singleDeviceIdLogout=true'>单浏览器注销</a> - " +
"<a href='/sso/logout?back=self'>全端注销</a> - " +
"<a href='/sso/myInfo' target='_blank'>账号资料</a>" +
"</p>";
return str;
}
/*
* SSO-Client端处理所有SSO相关请求
* http://{host}:{port}/sso/login -- Client端登录地址接受参数back=登录后的跳转地址
* http://{host}:{port}/sso/logout -- Client单点注销地址isSlo=true时打开接受参数back=注销后的跳转地址
* http://{host}:{port}/sso/logoutCall -- Client端单点注销回调地址isSlo=true时打开此接口为框架回调开发者无需关心
* http://{host}:{port}/sso/login -- Client 端登录地址
* http://{host}:{port}/sso/logout -- Client 端注销地址isSlo=true时打开
* http://{host}:{port}/sso/pushC -- Client 端接收消息推送地址
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
@ -45,11 +50,32 @@ public class SsoClientController {
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
// 当前应用独自注销 (不退出其它应用)
@RequestMapping("/sso/logoutByAlone")
public Object logoutByAlone() {
StpUtil.logout();
return SaSsoClientProcessor.instance._ssoLogoutBack(SaHolder.getRequest(), SaHolder.getResponse());
}
// 查询我的账号信息sso-client 前端 -> sso-center 后端 -> sso-server 后端
@RequestMapping("/sso/myInfo")
public Object myInfo() {
// 如果尚未登录
if( ! StpUtil.isLogin()) {
return "尚未登录,无法获取";
}
// 获取本地 loginId
Object loginId = StpUtil.getLoginId();
// 推送消息
SaSsoMessage message = new SaSsoMessage();
message.setType("userinfo");
message.set("loginId", loginId);
SaResult result = SaSsoClientUtil.pushMessageAsSaResult(message);
// 返回给前端
return result;
}
}

View File

@ -6,18 +6,17 @@ server:
sa-token:
# SSO-相关配置
sso-client:
# 应用标识
client: sso-client2
# SSO-Server 端主机地址
server-url: http://sa-sso-server.com:9000
# 在 sso-server 端前后端分离时打开这个上面的不要注释auth-url 配置项和 server-url 要同时存在)
# 在 sso-server 端前后端分离时需要单独配置 auth-url 参数上面的不要注释auth-url 配置项和 server-url 要同时存在)
# auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
client: sso-client2
sign:
# API 接口调用秘钥
# API 接口调用秘钥 (单点注销时会用到)
secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# 配置Sa-Token单独使用的Redis连接 此处需要和SSO-Server端连接同一个Redis
# 配置 Sa-Token 单独使用的Redis连接此处需要和 SSO-Server 端连接同一个 Redis
# 注:使用 alone-redis 需要在 pom.xml 引入 sa-token-alone-redis 依赖
alone-redis:
# Redis数据库索引
database: 1

View File

@ -3,7 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-sso3-client-test2</artifactId>
<artifactId>sa-token-demo-sso3-client-anon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
@ -53,15 +53,15 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Http请求工具 -->
<!-- Sa-Token 插件:整合 Forest 请求工具 -->
<dependency>
<groupId>com.dtflys.forest</groupId>
<artifactId>forest-spring-boot-starter</artifactId>
<version>1.5.26</version>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-forest</artifactId>
<version>${sa-token.version}</version>
</dependency>
</dependencies>

View File

@ -0,0 +1,23 @@
package com.pj;
import cn.dev33.satoken.sso.SaSsoManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SaSso3ClientAnonApplication {
public static void main(String[] args) {
SpringApplication.run(SaSso3ClientAnonApplication.class, args);
System.out.println();
System.out.println("---------------------- Sa-Token SSO 模式三 (匿名应用) Client 端启动成功 ----------------------");
System.out.println("配置信息:" + SaSsoManager.getClientConfig());
System.out.println("测试访问应用端一: http://sa-sso-client1.com:9006");
System.out.println("测试访问应用端二: http://sa-sso-client2.com:9006");
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9006");
System.out.println("测试前需要根据官网文档修改 hosts 文件测试账号密码sa / 123456");
System.out.println();
}
}

View File

@ -0,0 +1,22 @@
package com.pj.sso;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
* @author click33
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@ -0,0 +1,88 @@
package com.pj.sso;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.sso.message.SaSsoMessage;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoClientTemplate;
import cn.dev33.satoken.sso.template.SaSsoClientUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Sa-Token-SSO Client端 Controller
* @author click33
*/
@RestController
public class SsoClientController {
// 首页
@RequestMapping("/")
public String index() {
String str = "<h2>Sa-Token SSO-Client 应用端 (模式三-匿名应用)</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")</p>" +
"<p> " +
"<a href='/sso/login?back=/'>登录</a> - " +
"<a href='/sso/logoutByAlone?back=/'>单应用注销</a> - " +
"<a href='/sso/logout?back=self&singleDeviceIdLogout=true'>单浏览器注销</a> - " +
"<a href='/sso/logout?back=self'>全端注销</a> - " +
"<a href='/sso/myInfo' target='_blank'>账号资料</a>" +
"</p>";
return str;
}
/*
* SSO-Client端处理所有SSO相关请求
* http://{host}:{port}/sso/login -- Client 端登录地址
* http://{host}:{port}/sso/logout -- Client 端注销地址isSlo=true时打开
* http://{host}:{port}/sso/pushC -- Client 端接收消息推送地址
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoClientProcessor.instance.dister();
}
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoClientTemplate ssoClientTemplate) {
// // centerId 转换为 loginId 的函数
// ssoClientTemplate.strategy.convertCenterIdToLoginId = (centerId) -> {
// return "Stu" + centerId;
// };
// // loginId 转换为 centerId 的函数
// ssoClientTemplate.strategy.convertLoginIdToCenterId = (loginId) -> {
// return loginId.toString().substring(3);
// };
}
// 当前应用独自注销 (不退出其它应用)
@RequestMapping("/sso/logoutByAlone")
public Object logoutByAlone() {
StpUtil.logout();
return SaSsoClientProcessor.instance._ssoLogoutBack(SaHolder.getRequest(), SaHolder.getResponse());
}
// 查询我的账号信息sso-client 前端 -> sso-center 后端 -> sso-server 后端
@RequestMapping("/sso/myInfo")
public Object myInfo() {
// 如果尚未登录
if( ! StpUtil.isLogin()) {
return "尚未登录,无法获取";
}
// 获取本地 loginId 对应的认证中心 centerId
Object centerId = SaSsoClientUtil.getSsoTemplate().strategy.convertLoginIdToCenterId.run(StpUtil.getLoginId());
// 推送消息
SaSsoMessage message = new SaSsoMessage();
message.setType("userinfo");
message.set("loginId", centerId);
SaResult result = SaSsoClientUtil.pushMessageAsSaResult(message);
// 返回给前端
return result;
}
}

View File

@ -1,19 +1,21 @@
# 端口
server:
port: 9032
port: 9006
# sa-token配置
sa-token:
# 配置一个不同的 token-name以避免在和模式三 demo 一起测试时发生数据覆盖
token-name: satoken-client-anon
# sso-client 相关配置
sso-client:
# client 标识
client: sso-client3-test2
# client 标识 匿名应用就是指不配置 client 标识的应用
# client: sso-client3
# sso-server 端主机地址
server-url: http://sa-sso-server.com:9000
# 使用 Http 请求校验ticket (模式三)
is-http: true
sign:
# 是否在登录时注册单点登录回调接口 (匿名应用想要参与单点注销必须打开这个)
reg-logout-call: true
# API 接口调用秘钥
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
@ -21,7 +23,7 @@ spring:
# 配置 Redis 连接 此处与SSO-Server端连接不同的Redis
redis:
# Redis数据库索引
database: 4
database: 6
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口

View File

@ -8,15 +8,17 @@ public class SaSsoClientNoSdkApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoClientNoSdkApplication.class, args);
System.out.println("\nSa-Token SSO模式三 Client端 无SDK版本 启动成功");
System.out.println();
System.out.println("---------------------- Sa-Token SSO 模式三 NoSdk 模式 demo 启动成功 ----------------------");
System.out.println("---------------------- Sa-Token SSO 模式三 (NoSdk版) demo 启动成功 ----------------------");
System.out.println("测试访问应用端一: http://sa-sso-client1.com:9004");
System.out.println("测试访问应用端二: http://sa-sso-client2.com:9004");
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9004");
System.out.println("测试前需要根据官网文档修改hosts文件测试账号密码sa / 123456");
System.out.println();
System.err.println("自 v1.43.0 版本起Sa-Token SSO 不再维护 NoSdk 示例,此项目仅做留档");
System.err.println("如您需要非 Sa-Token 技术栈项目接入 SSO-Server 认证中心,请参考 ReSdk 版本示例");
}
}

View File

@ -0,0 +1,54 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-sso3-client-resdk</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.14</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.42.0</sa-token.version>
</properties>
<dependencies>
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 插件整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 插件:整合 Forest 请求工具 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-forest</artifactId>
<version>${sa-token.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -1,23 +1,21 @@
package com.pj;
import cn.dev33.satoken.sso.SaSsoManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SaSso3ClientTest2Application {
public class SaSsoClientReSdkApplication {
public static void main(String[] args) {
SpringApplication.run(SaSso3ClientTest2Application.class, args);
SpringApplication.run(SaSsoClientReSdkApplication.class, args);
System.out.println();
System.out.println("---------------------- Sa-Token SSO 模式三 Client 端启动成功 ----------------------");
System.out.println("配置信息:" + SaSsoManager.getClientConfig());
System.out.println("测试访问应用端一: http://sa-sso-client1.com:9032");
System.out.println("测试访问应用端二: http://sa-sso-client2.com:9032");
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9032");
System.out.println("---------------------- Sa-Token SSO 模式三 (ReSdk版) demo 启动成功 ----------------------");
System.out.println("测试访问应用端一: http://sa-sso-client1.com:9005");
System.out.println("测试访问应用端二: http://sa-sso-client2.com:9005");
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9005");
System.out.println("测试前需要根据官网文档修改hosts文件测试账号密码sa / 123456");
System.out.println();
}
}

View File

@ -0,0 +1,34 @@
package com.pj.resdk;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.springframework.stereotype.Component;
/**
* 记录所有已创建的 HttpSession 对象
*
* <b> 此种方式有性能问题仅做demo示例真实项目中请更换为其它方案记录用户会话数据 </b>
*
* @author click33
* @since 2022-4-30
*/
@Component
public class MyHttpSessionHolder implements HttpSessionListener {
public static List<HttpSession> sessionList = new ArrayList<>();
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
sessionList.add(httpSessionEvent.getSession());
}
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
HttpSession session = httpSessionEvent.getSession();
sessionList.remove(session);
}
}

View File

@ -0,0 +1,69 @@
package com.pj.resdk;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
import javax.servlet.http.HttpSession;
import java.util.Objects;
/**
* 会话对象 - httpSession
*
* @author click33
* @since 2025/5/6
*/
public class StpLogicForHttpSession extends StpLogic {
/**
* 初始化 StpLogic, 并指定账号类型
*
* @param type /
*
*/
public StpLogicForHttpSession(String type) {
super(type);
}
// 判断当前会话是否已登录
@Override
public boolean isLogin() {
return SpringMVCUtil.getRequest().getSession().getAttribute("userId") != null;
}
// 获取当前会话的登录ID
@Override
public Object getLoginId() {
Object userId = SpringMVCUtil.getRequest().getSession().getAttribute("userId");
if(userId == null) {
throw new RuntimeException("当前会话未登录");
}
return userId;
}
// 获取当前登录设备 id
@Override
public String getLoginDeviceId() {
return null;
}
// 当前会话注销
@Override
public void logout(SaLogoutParameter logoutParameter) {
SpringMVCUtil.getRequest().getSession().removeAttribute("userId");
}
// 当前账号id注销
@Override
public void _logout(Object loginId, SaLogoutParameter logoutParameter) {
System.out.println("--- 注销账号id" + loginId);
for (HttpSession session: MyHttpSessionHolder.sessionList) {
Object userId = session.getAttribute("userId");
if(Objects.equals(String.valueOf(userId), String.valueOf(loginId))) {
session.removeAttribute("userId");
}
}
}
}

View File

@ -0,0 +1,22 @@
package com.pj.sso;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
* @author click33
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@ -0,0 +1,92 @@
package com.pj.sso;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.sso.message.SaSsoMessage;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoClientTemplate;
import cn.dev33.satoken.sso.template.SaSsoClientUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.pj.resdk.StpLogicForHttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
/**
* SSO Client端 Controller
* @author click33
*/
@RestController
public class SsoClientController {
// SSO-Client端首页
@RequestMapping("/")
public String index(HttpSession session) {
boolean isLogin = session.getAttribute("userId") != null;
Object loginId = session.getAttribute("userId");
String str = "<h2>Sa-Token SSO-Client 应用端 (模式三-ReSdk)</h2>" +
"<p>当前会话是否登录:" + isLogin + " (" + loginId + ")</p>" +
"<p> " +
"<a href='/sso/login?back=/'>登录</a> - " +
"<a href='/sso/logoutByAlone?back=/'>单应用注销</a> - " +
"<a href='/sso/logout?back=self'>全端注销</a> - " +
"<a href='/sso/myInfo' target='_blank'>账号资料</a>" +
"</p>";
return str;
}
/*
* SSO-Client端处理所有 SSO 相关请求
* http://{host}:{port}/sso/login -- Client 端登录地址
* http://{host}:{port}/sso/logout -- Client 端注销地址isSlo=true时打开
* http://{host}:{port}/sso/pushC -- Client 端接收消息推送地址
*/
@RequestMapping("/sso/*")
public Object ssoLogin() {
return SaSsoClientProcessor.instance.dister();
}
// 当前应用独自注销 (不退出其它应用)
@RequestMapping("/sso/logoutByAlone")
public Object logoutByAlone(HttpSession session) {
session.removeAttribute("userId");
return SaSsoClientProcessor.instance._ssoLogoutBack(SaHolder.getRequest(), SaHolder.getResponse());
}
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoClientTemplate ssoClientTemplate) {
// 自定义底层使用的会话操作对象
ssoClientTemplate.setStpLogic(new StpLogicForHttpSession(StpUtil.TYPE));
// 自定义校验 ticket 返回值的处理逻辑 每次从认证中心获取校验 ticket 的结果后调用
ssoClientTemplate.strategy.ticketResultHandle = (ctr, back) -> {
HttpSession session = SpringMVCUtil.getRequest().getSession();
session.setAttribute("userId", ctr.loginId);
return SaHolder.getResponse().redirect(back);
};
}
// 查询我的账号信息sso-client 前端 -> sso-center 后端 -> sso-server 后端
@RequestMapping("/sso/myInfo")
public Object myInfo(HttpSession session) {
// 如果尚未登录
if(session.getAttribute("userId") == null) {
return "尚未登录,无法获取";
}
// 推送消息
SaSsoMessage message = new SaSsoMessage();
message.setType("userinfo");
message.set("loginId", session.getAttribute("userId"));
SaResult result = SaSsoClientUtil.pushMessageAsSaResult(message);
// 返回给前端
return result;
}
}

View File

@ -0,0 +1,18 @@
# 端口
server:
port: 9005
# sa-token 配置
sa-token:
# 是否打印操作日志
is-log: true
# sso-client 相关配置
sso-client:
# client 标识
client: sso-client3-resdk
# sso-server 端主机地址
server-url: http://sa-sso-server.com:9000
# 使用 Http 请求校验ticket (模式三)
is-http: true
# API 接口调用秘钥
secret-key: SSO-C3-ReSdk-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

View File

@ -1,78 +0,0 @@
package com.pj.sso;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* Sa-Token-SSO Client端 Controller
* @author click33
*/
@RestController
public class SsoClientController {
// SSO-Client端首页
@RequestMapping("/")
public String index() {
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
" <a href='/sso/logout?back=self'>注销</a></p>";
return str;
}
/*
* SSO-Client端处理所有SSO相关请求
* http://{host}:{port}/sso/login -- Client端登录地址接受参数back=登录后的跳转地址
* http://{host}:{port}/sso/logout -- Client端单点注销地址isSlo=true时打开接受参数back=注销后的跳转地址
* http://{host}:{port}/sso/logoutCall -- Client端单点注销回调地址isSlo=true时打开此接口为框架回调开发者无需关心
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoClientProcessor.instance.dister();
}
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoClientConfig ssoClient) {
// 配置Http请求处理器
ssoClient.sendHttp = url -> {
System.out.println("------ 发起请求:" + url);
String resStr = Forest.get(url).executeAsString();
System.out.println("------ 请求结果:" + resStr);
return resStr;
};
}
// 查询我的账号信息
@RequestMapping("/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;
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@ -16,7 +16,7 @@ public class SaSso3ClientApplication {
System.out.println("测试访问应用端一: http://sa-sso-client1.com:9003");
System.out.println("测试访问应用端二: http://sa-sso-client2.com:9003");
System.out.println("测试访问应用端三: http://sa-sso-client3.com:9003");
System.out.println("测试前需要根据官网文档修改hosts文件测试账号密码sa / 123456");
System.out.println("测试前需要根据官网文档修改 hosts 文件测试账号密码sa / 123456");
System.out.println();
}

View File

@ -2,19 +2,18 @@ package com.pj.h5;
import cn.dev33.satoken.sso.model.SaCheckTicketResult;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoUtil;
import cn.dev33.satoken.sso.template.SaSsoClientUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
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;
/**
* 前后台分离架构下集成SSO所需的代码 SSO-Client端
* <p>如果不需要前后端分离架构下集成SSO可删除此包下所有代码</p>
* @author click33
*
* @author click33
*/
@RestController
public class H5Controller {
@ -28,7 +27,7 @@ public class H5Controller {
// 返回SSO认证中心登录地址
@RequestMapping("/sso/getSsoAuthUrl")
public SaResult getSsoAuthUrl(String clientLoginUrl) {
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
String serverAuthUrl = SaSsoClientUtil.buildServerAuthUrl(clientLoginUrl, "");
return SaResult.data(serverAuthUrl);
}
@ -43,11 +42,4 @@ public class H5Controller {
return SaResult.data(StpUtil.getTokenValue());
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@ -7,7 +7,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* [Sa-Token 权限认证] 配置类 解决跨域问题
* [Sa-Token 权限认证] 配置类
*
* @author click33
*/

View File

@ -0,0 +1,22 @@
package com.pj.sso;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理
* @author click33
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@ -1,18 +1,16 @@
package com.pj.sso;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.sso.message.SaSsoMessage;
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
import cn.dev33.satoken.sso.template.SaSsoClientTemplate;
import cn.dev33.satoken.sso.template.SaSsoUtil;
import cn.dev33.satoken.sso.template.SaSsoClientUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* Sa-Token-SSO Client端 Controller
* @author click33
@ -23,18 +21,23 @@ public class SsoClientController {
// SSO-Client端首页
@RequestMapping("/")
public String index() {
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")</p>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
" <a href='/sso/logout?back=self'>注销</a></p>";
String str = "<h2>Sa-Token SSO-Client 应用端 (模式三)</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")</p>" +
"<p> " +
"<a href='/sso/login?back=/'>登录</a> - " +
"<a href='/sso/logoutByAlone?back=/'>单应用注销</a> - " +
"<a href='/sso/logout?back=self&singleDeviceIdLogout=true'>单浏览器注销</a> - " +
"<a href='/sso/logout?back=self'>全端注销</a> - " +
"<a href='/sso/myInfo' target='_blank'>账号资料</a>" +
"</p>";
return str;
}
/*
* SSO-Client端处理所有SSO相关请求
* http://{host}:{port}/sso/login -- Client端登录地址接受参数back=登录后的跳转地址
* http://{host}:{port}/sso/logout -- Client单点注销地址isSlo=true时打开接受参数back=注销后的跳转地址
* http://{host}:{port}/sso/logoutCall -- Client端单点注销回调地址isSlo=true时打开此接口为框架回调开发者无需关心
* SSO-Client端处理所有SSO相关请求
* http://{host}:{port}/sso/login -- Client 端登录地址
* http://{host}:{port}/sso/logout -- Client 端注销地址isSlo=true时打开
* http://{host}:{port}/sso/pushC -- Client 端接收消息推送地址
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
@ -44,36 +47,35 @@ public class SsoClientController {
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoClientTemplate ssoClientTemplate) {
// centerId 转换为 loginId 的函数
ssoClientTemplate.strategy.convertCenterIdToLoginId = (centerId) -> {
return "Stu_" + centerId;
};
// loginId 转换为 centerId 的函数
ssoClientTemplate.strategy.convertLoginIdToCenterId = (loginId) -> {
return loginId.toString().substring(4);
};
}
// 查询我的账号信息
// 当前应用独自注销 (不退出其它应用)
@RequestMapping("/sso/logoutByAlone")
public Object logoutByAlone() {
StpUtil.logout();
return SaSsoClientProcessor.instance._ssoLogoutBack(SaHolder.getRequest(), SaHolder.getResponse());
}
// 查询我的账号信息sso-client 前端 -> sso-center 后端 -> sso-server 后端
@RequestMapping("/sso/myInfo")
public Object myInfo() {
// 组织请求参数
Map<String, Object> map = new HashMap<>();
map.put("apiType", "userinfo");
map.put("loginId", StpUtil.getLoginId());
// 如果尚未登录
if( ! StpUtil.isLogin()) {
return "尚未登录,无法获取";
}
// 发起请求
Object resData = SaSsoUtil.getData(map);
System.out.println("sso-server 返回的信息:" + resData);
return resData;
// 获取本地 loginId
Object loginId = StpUtil.getLoginId();
// 推送消息
SaSsoMessage message = new SaSsoMessage();
message.setType("userinfo");
message.set("loginId", loginId);
SaResult result = SaSsoClientUtil.pushMessageAsSaResult(message);
// 返回给前端
return result;
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@ -13,13 +13,15 @@ sa-token:
client: sso-client3
# sso-server 端主机地址
server-url: http://sa-sso-server.com:9000
# 使用 Http 请求校验ticket (模式三)
# 在 sso-server 端前后端分离时需要单独配置 auth-url 参数上面的不要注释auth-url 配置项和 server-url 要同时存在)
# auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
# 使用 Http 请求校验 ticket (模式三)
is-http: true
# API 接口调用秘钥
secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
spring:
# 配置 Redis 连接 (此处与SSO-Server端连接不同的Redis
# 配置 Redis 连接 (此处与 SSO-Server 端连接不同的 Redis
redis:
# Redis数据库索引
database: 3

View File

@ -81,7 +81,7 @@ public class SaSsoClientConfig implements Serializable {
public Boolean isHttp = false;
/**
* 是否打开单点注销功能 ( true 接收单点注销回调消息推送)
* 是否打开单点注销功能 ( true 开放 /sso/logout 接口以及接收单点注销回调消息推送)
*/
public Boolean isSlo = true;
@ -153,14 +153,14 @@ public class SaSsoClientConfig implements Serializable {
}
/**
* @return 是否打开单点注销功能 ( true 接收单点注销回调消息推送)
* @return 是否打开单点注销功能 ( true 开放 /sso/logout 接口以及接收单点注销回调消息推送)
*/
public Boolean getIsSlo() {
return isSlo;
}
/**
* @param isSlo 是否打开单点注销功能 ( true 接收单点注销回调消息推送)
* @param isSlo 是否打开单点注销功能 ( true 开放 /sso/logout 接口以及接收单点注销回调消息推送)
* @return 对象自身
*/
public SaSsoClientConfig setIsSlo(Boolean isSlo) {

View File

@ -82,7 +82,7 @@ public class SaSsoServerConfig implements Serializable {
/**
* 是否允许匿名 Client 接入
*/
public Boolean allowAnonClient = true;
public Boolean allowAnonClient = false;
/**
* 所有允许的授权回调地址多个用逗号隔开 (不在此列表中的URL将禁止下放ticket) (匿名 client 使用)

View File

@ -0,0 +1,35 @@
/*
* 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 cn.dev33.satoken.sso.message.SaSsoMessage;
import cn.dev33.satoken.sso.template.SaSsoTemplate;
/**
* 函数式接口处理 SSO 消息的函数式接口
*
* <p> 参数ssoTemplate 模板对象, 要处理的 message 消息 </p>
* <p> 返回任意值 </p>
*
* @author click33
* @since 1.38.0
*/
@FunctionalInterface
public interface SaSsoMessageHandleFunction {
Object execute(SaSsoTemplate ssoTemplate, SaSsoMessage message);
}

View File

@ -27,6 +27,6 @@ import java.util.function.Function;
* @since 1.38.0
*/
@FunctionalInterface
public interface SendHttpFunction extends Function<String, String> {
public interface SendRequestFunction extends Function<String, String> {
}

View File

@ -18,7 +18,9 @@ package cn.dev33.satoken.sso.message;
import cn.dev33.satoken.sso.error.SaSsoErrorCode;
import cn.dev33.satoken.sso.exception.SaSsoException;
import cn.dev33.satoken.sso.function.SaSsoMessageHandleFunction;
import cn.dev33.satoken.sso.message.handle.SaSsoMessageHandle;
import cn.dev33.satoken.sso.message.handle.SaSsoMessageSimpleHandle;
import cn.dev33.satoken.sso.template.SaSsoTemplate;
import java.util.LinkedHashMap;
@ -68,6 +70,18 @@ public class SaSsoMessageHolder {
return this;
}
/**
* 添加指定类型的简单消息处理器
*
* @param type 要处理的消息类型
* @param handle 要执行的方法
* @return 对象自身
*/
public SaSsoMessageHolder addHandle(String type, SaSsoMessageHandleFunction handle) {
messageHandleMap.put(type, new SaSsoMessageSimpleHandle(type, handle));
return this;
}
/**
* 获取指定类型的消息处理器
*

View File

@ -0,0 +1,67 @@
/*
* 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.message.handle;
import cn.dev33.satoken.sso.function.SaSsoMessageHandleFunction;
import cn.dev33.satoken.sso.message.SaSsoMessage;
import cn.dev33.satoken.sso.template.SaSsoTemplate;
/**
* SSO 消息处理器 - 简单实现方便 lambda 表达式编程
*
* @author click33
* @since 1.43.0
*/
public class SaSsoMessageSimpleHandle implements SaSsoMessageHandle{
public String type;
public SaSsoMessageHandleFunction handle;
/**
* SSO 消息处理器 - 简单实现方便 lambda 表达式编程
* @param type 要处理的消息类型
* @param handle 要执行的方法
*/
public SaSsoMessageSimpleHandle(String type, SaSsoMessageHandleFunction handle) {
this.type = type;
this.handle = handle;
}
/**
* 获取所要处理的消息类型
*
* @return /
*/
@Override
public String getHandlerType() {
return type;
}
/**
* 具体要执行的处理方法
*
* @param ssoTemplate /
* @param message /
* @return /
*/
@Override
public Object handle(SaSsoTemplate ssoTemplate, SaSsoMessage message){
return handle.execute(ssoTemplate, message);
}
}

View File

@ -16,8 +16,6 @@
package cn.dev33.satoken.sso.message.handle.client;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.sso.message.SaSsoMessage;
import cn.dev33.satoken.sso.message.handle.SaSsoMessageHandle;
import cn.dev33.satoken.sso.name.ParamName;
@ -56,7 +54,7 @@ public class SaSsoMessageLogoutCallHandle implements SaSsoMessageHandle {
// 1获取对象
SaSsoClientTemplate ssoClientTemplate = (SaSsoClientTemplate) ssoTemplate;
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
StpLogic stpLogic = ssoClientTemplate.getStpLogicOrGlobal();
ParamName paramName = ssoClientTemplate.paramName;
// 2判断当前应用是否开启单点注销功能

View File

@ -55,7 +55,7 @@ public class SaSsoMessageCheckTicketHandle implements SaSsoMessageHandle {
// 1获取对象
SaSsoServerTemplate ssoServerTemplate = (SaSsoServerTemplate) ssoTemplate;
ParamName paramName = ssoServerTemplate.paramName;
StpLogic stpLogic = ssoServerTemplate.getStpLogic();
StpLogic stpLogic = ssoServerTemplate.getStpLogicOrGlobal();
String client = message.getString(paramName.client);
String ticket = message.getValueNotNull(paramName.ticket).toString();
String sloCallback = message.getString(paramName.ssoLogoutCall);

View File

@ -65,7 +65,7 @@ public class SaSsoMessageSignoutHandle implements SaSsoMessageHandle {
String deviceId = message.getString(paramName.deviceId);
// 4单点注销
SaLogoutParameter logoutParameter = ssoServerTemplate.getStpLogic().createSaLogoutParameter().setDeviceId(deviceId);
SaLogoutParameter logoutParameter = ssoServerTemplate.getStpLogicOrGlobal().createSaLogoutParameter().setDeviceId(deviceId);
ssoServerTemplate.ssoLogout(loginId, logoutParameter);
// 5响应

View File

@ -164,7 +164,7 @@ public class SaSsoClientProcessor {
// 获取对象
SaRequest req = SaHolder.getRequest();
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
StpLogic stpLogic = ssoClientTemplate.getStpLogicOrGlobal();
ParamName paramName = ssoClientTemplate.paramName;
SaSsoClientConfig ssoConfig = ssoClientTemplate.getClientConfig();
@ -181,7 +181,7 @@ public class SaSsoClientProcessor {
}
// 注销当前应用端会话
SaLogoutParameter logoutParameter = ssoClientTemplate.getStpLogic().createSaLogoutParameter();
SaLogoutParameter logoutParameter = ssoClientTemplate.getStpLogicOrGlobal().createSaLogoutParameter();
stpLogic.logout(loginId, logoutParameter.setDeviceId(deviceId));
// 响应
@ -199,7 +199,7 @@ public class SaSsoClientProcessor {
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaSsoClientConfig cfg = ssoClientTemplate.getClientConfig();
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
StpLogic stpLogic = ssoClientTemplate.getStpLogicOrGlobal();
ParamName paramName = ssoClientTemplate.paramName;
// 获取参数
@ -229,7 +229,7 @@ public class SaSsoClientProcessor {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
StpLogic stpLogic = ssoClientTemplate.getStpLogicOrGlobal();
ParamName paramName = ssoClientTemplate.paramName;
ApiName apiName = ssoClientTemplate.apiName;
@ -239,8 +239,6 @@ public class SaSsoClientProcessor {
// 1校验 ticket获取 loginId 等数据
SaCheckTicketResult ctr = checkTicket(ticket, apiName.ssoLogin);
ctr.centerId = ctr.loginId;
ctr.loginId = ssoClientTemplate.strategy.convertCenterIdToLoginId.run(ctr.centerId);
// 2如果开发者自定义了 ticket 结果值处理函数则使用自定义的函数
if(ssoClientTemplate.strategy.ticketResultHandle != null) {
@ -263,7 +261,7 @@ public class SaSsoClientProcessor {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
StpLogic stpLogic = ssoClientTemplate.getStpLogicOrGlobal();
boolean singleDeviceIdLogout = req.isParam(ssoClientTemplate.paramName.singleDeviceIdLogout, "true");
// 如果未登录则无需注销
@ -292,6 +290,16 @@ public class SaSsoClientProcessor {
return _ssoLogoutBack(req, res);
}
/**
* 封装校验ticket取出loginId如果 ticket 无效则抛出异常 适用于模式二或模式三
*
* @param ticket ticket码
* @return SaCheckTicketResult
*/
public SaCheckTicketResult checkTicket(String ticket) {
return checkTicket(ticket, null);
}
/**
* 封装校验ticket取出loginId如果 ticket 无效则抛出异常 适用于模式二或模式三
*
@ -357,6 +365,10 @@ public class SaSsoClientProcessor {
ctr.remainSessionTimeout = result.get(paramName.remainSessionTimeout, Long.class);
ctr.result = result;
// 转换 loginId centerId
ctr.centerId = ctr.loginId;
ctr.loginId = ssoClientTemplate.strategy.convertCenterIdToLoginId.run(ctr.centerId);
return ctr;
}
@ -373,7 +385,7 @@ public class SaSsoClientProcessor {
// 可能会导致调用失败注意是可能而非一定主要取决于你是否改变了数据读写格式
// 解决方案为在当前 sso-client 端也按照 sso-server 端的格式重写 SaSsoClientProcessor 里的方法
StpLogic stpLogic = ssoClientTemplate.getStpLogic();
StpLogic stpLogic = ssoClientTemplate.getStpLogicOrGlobal();
TicketModel ticketModel = SaSsoServerProcessor.instance.ssoServerTemplate.checkTicketParamAndDelete(ticket, ssoClientTemplate.getClient());
SaCheckTicketResult ctr = new SaCheckTicketResult();
@ -384,6 +396,10 @@ public class SaSsoClientProcessor {
ctr.remainSessionTimeout = stpLogic.getSessionTimeoutByLoginId(ticketModel.getLoginId());
ctr.result = null;
// 转换 loginId centerId
ctr.centerId = ctr.loginId;
ctr.loginId = ssoClientTemplate.strategy.convertCenterIdToLoginId.run(ctr.centerId);
return ctr;
}

View File

@ -101,7 +101,7 @@ public class SaSsoServerProcessor {
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
SaSsoServerConfig cfg = ssoServerTemplate.getServerConfig();
StpLogic stpLogic = ssoServerTemplate.getStpLogic();
StpLogic stpLogic = ssoServerTemplate.getStpLogicOrGlobal();
ParamName paramName = ssoServerTemplate.paramName;
// 两种情况
@ -177,7 +177,7 @@ public class SaSsoServerProcessor {
// 获取对象
SaRequest req = SaHolder.getRequest();
SaResponse res = SaHolder.getResponse();
StpLogic stpLogic = ssoServerTemplate.getStpLogic();
StpLogic stpLogic = ssoServerTemplate.getStpLogicOrGlobal();
Object loginId = stpLogic.getLoginIdDefaultNull();
boolean singleDeviceIdLogout = req.isParam(ssoServerTemplate.paramName.singleDeviceIdLogout, "true");
@ -191,7 +191,7 @@ public class SaSsoServerProcessor {
}
// 完成
return SaSsoProcessorHelper.ssoLogoutBack(req, res, ssoServerTemplate.paramName);
return _ssoLogoutBack(req, res);
}
/**
@ -228,4 +228,14 @@ public class SaSsoServerProcessor {
return ssoServerTemplate.handleMessage(message);
}
/**
* 封装单点注销成功后返回结果
* @param req SaRequest对象
* @param res SaResponse对象
* @return 返回结果
*/
public Object _ssoLogoutBack(SaRequest req, SaResponse res) {
return SaSsoProcessorHelper.ssoLogoutBack(req, res, ssoServerTemplate.paramName);
}
}

View File

@ -17,7 +17,7 @@ package cn.dev33.satoken.sso.strategy;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.fun.SaParamRetFunction;
import cn.dev33.satoken.sso.function.SendHttpFunction;
import cn.dev33.satoken.sso.function.SendRequestFunction;
import cn.dev33.satoken.sso.function.TicketResultHandleFunction;
import cn.dev33.satoken.util.SaResult;
@ -34,7 +34,7 @@ public class SaSsoClientStrategy {
/**
* 发送 Http 请求的处理函数
*/
public SendHttpFunction sendHttp = url -> {
public SendRequestFunction sendHttp = url -> {
return SaManager.getSaHttpTemplate().get(url);
};

View File

@ -16,11 +16,12 @@
package cn.dev33.satoken.sso.strategy;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.fun.SaParamFunction;
import cn.dev33.satoken.sso.function.CheckTicketAppendDataFunction;
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.sso.function.SendRequestFunction;
import cn.dev33.satoken.util.SaResult;
import java.util.Map;
@ -36,10 +37,19 @@ public class SaSsoServerStrategy {
/**
* 发送 Http 请求的处理函数
*/
public SendHttpFunction sendHttp = url -> {
public SendRequestFunction sendRequest = url -> {
return SaManager.getSaHttpTemplate().get(url);
};
/**
* 使用异步模式执行一个任务
*/
public SaParamFunction<SaFunction> asyncRun = fun -> {
new Thread(() -> {
fun.run();
}).start();
};
/**
* 未登录时返回的 View
*/
@ -75,7 +85,7 @@ public class SaSsoServerStrategy {
* @return 返回的结果
*/
public SaResult requestAsSaResult(String url) {
String body = sendHttp.apply(url);
String body = sendRequest.apply(url);
Map<String, Object> map = SaManager.getSaJsonTemplate().jsonToMap(body);
return new SaResult(map);
}

View File

@ -213,7 +213,6 @@ public class SaSsoClientTemplate extends SaSsoTemplate {
* @return 单点注销URL
*/
public SaSsoMessage buildSloMessage(Object loginId, SaLogoutParameter logoutParameter) {
SaSsoClientConfig ssoConfig = getClientConfig();
SaSsoMessage message = new SaSsoMessage();
message.setType(SaSsoConsts.MESSAGE_SIGNOUT);
message.set(paramName.client, getClient());

View File

@ -507,7 +507,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
return;
}
SaSession session = getStpLogic().getSessionByLoginId(loginId);
SaSession session = getStpLogicOrGlobal().getSessionByLoginId(loginId);
// 取出原来的
List<SaSsoClientInfo> scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new);
@ -522,7 +522,9 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
for (;;) {
if(scmList.size() > maxRegClient) {
SaSsoClientInfo removeScm = scmList.remove(0);
notifyClientLogout(loginId, null, removeScm, true, true);
strategy.asyncRun.run(() -> {
notifyClientLogout(loginId, null, removeScm, true, true);
});
} else {
break;
}
@ -562,7 +564,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
* @param loginId 指定账号
*/
public void ssoLogout(Object loginId) {
ssoLogout(loginId, getStpLogic().createSaLogoutParameter());
ssoLogout(loginId, getStpLogicOrGlobal().createSaLogoutParameter());
}
/**
@ -577,32 +579,37 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
pushToAllClientByLogoutCall(loginId, logoutParameter);
// 2SaSession 挂载的 Client 端注销会话
SaSession session = getStpLogic().getSessionByLoginId(loginId, false);
SaSession session = getStpLogicOrGlobal().getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
List<SaSsoClientInfo> scmList = session.get(SaSsoConsts.SSO_CLIENT_MODEL_LIST_KEY_, ArrayList::new);
scmList.forEach(scm -> {
notifyClientLogout(loginId, logoutParameter.getDeviceId(), scm, false, false);
strategy.asyncRun.run(() -> {
notifyClientLogout(loginId, logoutParameter.getDeviceId(), scm, false, false);
});
});
// 3Server 端本身注销
getStpLogic().logout(loginId, logoutParameter);
getStpLogicOrGlobal().logout(loginId, logoutParameter);
}
/**
* 通知指定账号的指定客户端注销
*
* @param loginId 指定账号
* @param deviceId 指定设备 id
* @param scm 客户端信息对象
* @param autoLogout 是否为超过 maxRegClient 的自动注销
* @param isPushWork 如果该 client 没有注册注销回调地址是否使用 push 消息的方式进行注销回调通知
*
* @return /
*/
public void notifyClientLogout(Object loginId, String deviceId, SaSsoClientInfo scm, boolean autoLogout, boolean isPushWork) {
public String notifyClientLogout(Object loginId, String deviceId, SaSsoClientInfo scm, boolean autoLogout, boolean isPushWork) {
// 如果给个null值不进行任何操作
if(scm == null || scm.mode != SaSsoConsts.SSO_MODE_3) {
return;
return null;
}
// 如果此 Client 并没有注册 单点注销 回调地址
@ -610,9 +617,10 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
if(SaFoxUtil.isEmpty(sloCallUrl)) {
// TODO 代码有效性待验证
if(isPushWork && SaFoxUtil.isNotEmpty(scm.getClient())) {
pushToClientByLogoutCall(getClient(scm.getClient()), loginId, getStpLogic().createSaLogoutParameter());
SaSsoClientModel client = getClient(scm.getClient());
return pushToClientByLogoutCall(client, loginId, getStpLogicOrGlobal().createSaLogoutParameter());
}
return;
return null;
}
// 参数
@ -627,7 +635,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
String finalUrl = SaFoxUtil.joinParam(sloCallUrl, signParamsStr);
// 发起请求
strategy.sendHttp.apply(finalUrl);
return strategy.sendRequest.apply(finalUrl);
}
@ -644,7 +652,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
String noticeUrl = clientModel.splicingPushUrl();
String paramsStr = getSignTemplate(clientModel.getClient()).addSignParamsAndJoin(message);
String finalUrl = SaFoxUtil.joinParam(noticeUrl, paramsStr);
return strategy.sendHttp.apply(finalUrl);
return strategy.sendRequest.apply(finalUrl);
}
/**
@ -691,7 +699,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
public void pushToAllClient(SaSsoMessage message) {
List<SaSsoClientModel> needPushClients = getNeedPushClients();
for (SaSsoClientModel client : needPushClients) {
pushMessage(client, message);
strategy.asyncRun.run(() -> pushMessage(client, message));
}
}
@ -705,7 +713,9 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
List<SaSsoClientModel> npClients = getNeedPushClients();
for (SaSsoClientModel client : npClients) {
if(client.getIsSlo()) {
pushToClientByLogoutCall(client, loginId, logoutParameter);
strategy.asyncRun.run(() -> {
pushToClientByLogoutCall(client, loginId, logoutParameter);
});
}
}
}
@ -715,13 +725,14 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
*
* @param loginId /
* @param logoutParameter 注销参数
* @return /
*/
public void pushToClientByLogoutCall(SaSsoClientModel client, Object loginId, SaLogoutParameter logoutParameter) {
public String pushToClientByLogoutCall(SaSsoClientModel client, Object loginId, SaLogoutParameter logoutParameter) {
SaSsoMessage message = new SaSsoMessage();
message.setType(SaSsoConsts.MESSAGE_LOGOUT_CALL);
message.set(paramName.loginId, loginId);
message.set(paramName.deviceId, logoutParameter.getDeviceId());
pushMessage(client, message);
return pushMessage(client, message);
}
@ -766,7 +777,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
* @return key
*/
public String splicingTicketModelSaveKey(String ticket) {
return getStpLogic().getConfigOrGlobal().getTokenName() + ":ticket:" + ticket;
return getStpLogicOrGlobal().getConfigOrGlobal().getTokenName() + ":ticket:" + ticket;
}
/**
@ -780,7 +791,7 @@ public class SaSsoServerTemplate extends SaSsoTemplate {
if(SaFoxUtil.isEmpty(client) || SaSsoConsts.CLIENT_WILDCARD.equals(client)) {
client = SaSsoConsts.CLIENT_ANON;
}
return getStpLogic().getConfigOrGlobal().getTokenName() + ":ticket-index:" + client + ":" + id;
return getStpLogicOrGlobal().getConfigOrGlobal().getTokenName() + ":ticket-index:" + client + ":" + id;
}
}

View File

@ -61,11 +61,39 @@ public class SaSsoTemplate {
}
/**
* 获取底层使用的会话对象
* 底层使用的 StpLogic 对象
*/
StpLogic stpLogic;
/**
* 写入底层使用的会话对象
*
* @param stpLogic /
* @return /
*/
public SaSsoTemplate setStpLogic(StpLogic stpLogic) {
this.stpLogic = stpLogic;
return this;
}
/**
* 获取底层使用的会话对象
* @return /
*/
public StpLogic getStpLogic() {
return StpUtil.stpLogic;
return this.stpLogic;
}
/**
* 获取底层使用的会话对象如果没有配置则返回全局默认 StpLogic
* @return /
*/
public StpLogic getStpLogicOrGlobal() {
StpLogic stpLogic = getStpLogic();
if (stpLogic == null) {
return StpUtil.stpLogic;
}
return stpLogic;
}
// ----------- 消息处理