mirror of
https://gitee.com/dromara/sa-token.git
synced 2026-02-27 16:50:24 +08:00
refactor: 调整 solon-sso 示例代码
This commit is contained in:
@@ -55,14 +55,13 @@
|
||||
<artifactId>solon.view.thymeleaf</artifactId>
|
||||
<version>${solon.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Http请求工具(在模式三的单点注销功能下用到,如不需要可以注释掉) -->
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>forest-solon-plugin</artifactId>
|
||||
<version>${solon.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 插件:整合 Forest 请求工具 (模式三需要通过 http 请求推送消息) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-forest</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.pj;
|
||||
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.dao.SaTokenDaoForRedisx;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
|
||||
/**
|
||||
* @author noear 2023/3/13 created
|
||||
*/
|
||||
@Configuration
|
||||
public class SaConfig {
|
||||
|
||||
/**
|
||||
* 构建建 SaToken redis dao(如果不需要 redis;可以注释掉)
|
||||
* */
|
||||
@Bean
|
||||
public SaTokenDao saTokenDaoInit(@Inject("${sa-token.dao.redis}") SaTokenDaoForRedisx saTokenDao) {
|
||||
return saTokenDao;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,9 +2,14 @@ package com.pj;
|
||||
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoManager;
|
||||
import cn.dev33.satoken.sso.model.SaSsoClientInfo;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.SolonMain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SolonMain
|
||||
public class SaSsoServerApp {
|
||||
|
||||
@@ -12,11 +17,25 @@ public class SaSsoServerApp {
|
||||
Solon.start(SaSsoServerApp.class, args);
|
||||
|
||||
System.out.println();
|
||||
System.out.println("---------------------- Solon Sa-Token SSO 统一认证中心启动成功 ----------------------");
|
||||
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();
|
||||
|
||||
// SaSsoClientInfo sci = new SaSsoClientInfo();
|
||||
// sci.setClient("client1");
|
||||
//
|
||||
// List<SaSsoClientInfo> list = new ArrayList<>();
|
||||
// list.add(sci);
|
||||
//
|
||||
// StpUtil.getSessionByLoginId(10001).set("list", list);
|
||||
//
|
||||
// List<SaSsoClientInfo> list2 = (List)StpUtil.getSessionByLoginId(10001).get("list");
|
||||
// for (SaSsoClientInfo info : list2) {
|
||||
// System.out.println(info);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.pj.h5;
|
||||
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Filter;
|
||||
import org.noear.solon.core.handle.FilterChain;
|
||||
|
||||
|
||||
/**
|
||||
* 跨域过滤器
|
||||
* @author click33
|
||||
*/
|
||||
@Component(index = -200)
|
||||
public class CorsFilter implements Filter {
|
||||
|
||||
static final String OPTIONS = "OPTIONS";
|
||||
|
||||
@Override
|
||||
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
|
||||
// 允许指定域访问跨域资源
|
||||
ctx.headerSet("Access-Control-Allow-Origin", "*");
|
||||
// 允许所有请求方式
|
||||
ctx.headerSet("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
|
||||
// 有效时间
|
||||
ctx.headerSet("Access-Control-Max-Age", "3600");
|
||||
// 允许的header参数
|
||||
ctx.headerSet("Access-Control-Allow-Headers", "x-requested-with,satoken");
|
||||
|
||||
// 如果是预检请求,直接返回
|
||||
if (OPTIONS.equals(ctx.method())) {
|
||||
System.out.println("=======================浏览器发来了OPTIONS预检请求==========");
|
||||
ctx.output("");
|
||||
return;
|
||||
}
|
||||
|
||||
// System.out.println("*********************************过滤器被使用**************************");
|
||||
chain.doFilter(ctx);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
package com.pj.h5;
|
||||
|
||||
|
||||
import cn.dev33.satoken.sso.util.SaSsoConsts;
|
||||
import cn.dev33.satoken.sso.template.SaSsoUtil;
|
||||
import cn.dev33.satoken.sso.util.SaSsoConsts;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Render;
|
||||
|
||||
/**
|
||||
* 前后台分离架构下集成SSO所需的代码 (SSO-Server端)
|
||||
@@ -18,40 +16,42 @@ import org.noear.solon.core.handle.Render;
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class H5Controller implements Render {
|
||||
public class H5Controller {
|
||||
|
||||
/**
|
||||
* 获取 redirectUrl
|
||||
*/
|
||||
@Mapping("/sso/getRedirectUrl")
|
||||
private Object getRedirectUrl(String redirect, String mode, String client) {
|
||||
// 未登录情况下,返回 code=401
|
||||
if (StpUtil.isLogin() == false) {
|
||||
public SaResult getRedirectUrl(String client, String redirect, String mode) {
|
||||
// 未登录情况下,返回 code=401
|
||||
if(StpUtil.isLogin() == false) {
|
||||
return SaResult.code(401);
|
||||
}
|
||||
// 已登录情况下,构建 redirectUrl
|
||||
if (SaSsoConsts.MODE_SIMPLE.equals(mode)) {
|
||||
// 模式一
|
||||
SaSsoUtil.checkRedirectUrl(SaFoxUtil.decoderUrl(redirect));
|
||||
// 已登录情况下,构建 redirectUrl
|
||||
redirect = SaFoxUtil.decoderUrl(redirect);
|
||||
if(SaSsoConsts.MODE_SIMPLE.equals(mode)) {
|
||||
// 模式一
|
||||
SaSsoUtil.checkRedirectUrl(client, redirect);
|
||||
return SaResult.data(redirect);
|
||||
} else {
|
||||
// 模式二或模式三
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), client, redirect);
|
||||
// 模式二或模式三
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(client, redirect, StpUtil.getLoginId(), StpUtil.getLoginDeviceId());
|
||||
return SaResult.data(redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制当前类的异常
|
||||
*/
|
||||
@Override
|
||||
public void render(Object data, Context ctx) throws Throwable {
|
||||
if (data instanceof Throwable) {
|
||||
Throwable e = (Throwable) data;
|
||||
e.printStackTrace();
|
||||
ctx.render(SaResult.error(e.getMessage()));
|
||||
} else {
|
||||
ctx.render(data);
|
||||
}
|
||||
}
|
||||
// /**
|
||||
// * 控制当前类的异常
|
||||
// */
|
||||
// @Override
|
||||
// public void render(Object data, Context ctx) throws Throwable {
|
||||
// if (data instanceof Throwable) {
|
||||
// Throwable e = (Throwable) data;
|
||||
// e.printStackTrace();
|
||||
// ctx.render(SaResult.error(e.getMessage()));
|
||||
// } else {
|
||||
// ctx.render(data);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.pj.h5;
|
||||
|
||||
import cn.dev33.satoken.fun.strategy.SaCorsHandleFunction;
|
||||
import cn.dev33.satoken.router.SaHttpMethod;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类 (解决跨域问题)
|
||||
*
|
||||
* @author click33
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure {
|
||||
|
||||
/**
|
||||
* CORS 跨域处理策略
|
||||
*/
|
||||
@Bean
|
||||
public SaCorsHandleFunction corsHandle() {
|
||||
return (req, res, sto) -> {
|
||||
res.
|
||||
// 允许指定域访问跨域资源
|
||||
setHeader("Access-Control-Allow-Origin", "*")
|
||||
// 允许所有请求方式
|
||||
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
|
||||
// 有效时间
|
||||
.setHeader("Access-Control-Max-Age", "3600")
|
||||
// 允许的header参数
|
||||
.setHeader("Access-Control-Allow-Headers", "*");
|
||||
|
||||
// 如果是预检请求,则立即返回到前端
|
||||
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
||||
.back();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,8 +21,8 @@ public class GlobalExceptionFilter implements Filter {
|
||||
chain.doFilter(ctx);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
ctx.render(SaResult.error(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
|
||||
/**
|
||||
* SSO 平台中心模式示例,跳连接进入子系统
|
||||
*/
|
||||
@Controller
|
||||
public class HomeController {
|
||||
|
||||
// 平台化首页
|
||||
@Mapping("/home")
|
||||
public Object index() {
|
||||
// 如果未登录,则先去登录
|
||||
if(!StpUtil.isLogin()) {
|
||||
return SaHolder.getResponse().redirect("/sso/auth");
|
||||
}
|
||||
|
||||
// 拼接各个子系统的地址,格式形如:/sso/auth?client=xxx&redirect=${子系统首页}/sso/login?back=${子系统首页}
|
||||
String link1 = "/sso/auth?client=sso-client3&redirect=http://sa-sso-client1.com:9003/sso/login?back=http://sa-sso-client1.com:9003/";
|
||||
String link2 = "/sso/auth?client=sso-client3&redirect=http://sa-sso-client2.com:9003/sso/login?back=http://sa-sso-client2.com:9003/";
|
||||
String link3 = "/sso/auth?client=sso-client3&redirect=http://sa-sso-client3.com:9003/sso/login?back=http://sa-sso-client3.com:9003/";
|
||||
|
||||
// 组织网页结构返回到前端
|
||||
String title = "<h2>SSO 平台首页 (平台中心模式)</h2>";
|
||||
String client1 = "<p><a href='" + link1 + "' target='_blank'> 进入Client1系统 </a></p>";
|
||||
String client2 = "<p><a href='" + link2 + "' target='_blank'> 进入Client2系统 </a></p>";
|
||||
String client3 = "<p><a href='" + link3 + "' target='_blank'> 进入Client3系统 </a></p>";
|
||||
|
||||
return title + client1 + client2 + client3;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.dao.SaTokenDaoForRedisx;
|
||||
import cn.dev33.satoken.sso.config.SaSsoServerConfig;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import com.dtflys.forest.Forest;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.handle.ModelAndView;
|
||||
|
||||
/**
|
||||
* @author noear 2023/1/3 created
|
||||
*/
|
||||
@Configuration
|
||||
public class SsoConfig {
|
||||
|
||||
/**
|
||||
* 构建建 SaToken redis dao(如果不需要 redis;可以注释掉)
|
||||
* */
|
||||
@Bean
|
||||
public SaTokenDao saTokenDaoInit(@Inject("${sa-token.dao.redis}") SaTokenDaoForRedisx saTokenDao) {
|
||||
return saTokenDao;
|
||||
}
|
||||
|
||||
// 配置SSO相关参数
|
||||
@Bean
|
||||
public void configSso(SaSsoServerConfig ssoServer) { //SaSsoConfig 已自动构建
|
||||
|
||||
// 配置:未登录时返回的View
|
||||
ssoServer.notLoginView = () -> {
|
||||
return new ModelAndView("sa-login.html");
|
||||
};
|
||||
|
||||
// 配置:登录处理函数
|
||||
ssoServer.doLoginHandle = (name, pwd) -> {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
|
||||
}
|
||||
return SaResult.error("登录失败!");
|
||||
};
|
||||
|
||||
// 配置 Http 请求处理器
|
||||
ssoServer.sendHttp = url -> {
|
||||
try {
|
||||
System.out.println("------ 发起请求:" + url);
|
||||
String resStr = Forest.get(url).executeAsString();
|
||||
System.out.println("------ 请求结果:" + resStr);
|
||||
return resStr;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,18 @@
|
||||
package com.pj.sso;
|
||||
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
|
||||
import cn.dev33.satoken.sso.template.SaSsoServerTemplate;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
import org.noear.solon.core.handle.ModelAndView;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO Server端 Controller
|
||||
@@ -11,17 +20,52 @@ import org.noear.solon.annotation.Mapping;
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
@Configuration
|
||||
public class SsoServerController {
|
||||
|
||||
/*
|
||||
* SSO-Server端:处理所有SSO相关请求
|
||||
* http://{host}:{port}/sso/auth -- 单点登录授权地址,接受参数:redirect=授权重定向地址
|
||||
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口,接受参数:name、pwd
|
||||
* http://{host}:{port}/sso/checkTicket -- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选]
|
||||
* http://{host}:{port}/sso/signout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥
|
||||
/**
|
||||
* SSO-Server端:处理所有SSO相关请求
|
||||
* http://{host}:{port}/sso/auth -- 单点登录授权地址
|
||||
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口,接受参数:name、pwd
|
||||
* http://{host}:{port}/sso/signout -- 单点注销地址(isSlo=true时打开)
|
||||
*/
|
||||
@Mapping("/sso/*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoServerProcessor.instance.dister();
|
||||
}
|
||||
|
||||
// 配置SSO相关参数
|
||||
@Bean
|
||||
private void configSso(SaSsoServerTemplate ssoServerTemplate) {
|
||||
|
||||
// 配置:未登录时返回的View
|
||||
ssoServerTemplate.strategy.notLoginView = () -> {
|
||||
return new ModelAndView("sa-login.html");
|
||||
};
|
||||
|
||||
// 配置:登录处理函数
|
||||
ssoServerTemplate.strategy.doLoginHandle = (name, pwd) -> {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据库进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
String deviceId = SaHolder.getRequest().getParam("deviceId", SaFoxUtil.getRandomString(32));
|
||||
StpUtil.login(10001, new SaLoginParameter().setDeviceId(deviceId));
|
||||
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
|
||||
}
|
||||
return SaResult.error("登录失败!");
|
||||
};
|
||||
|
||||
// 添加消息处理器:userinfo (获取用户资料) (用于为 client 端开放拉取数据的接口)
|
||||
ssoServerTemplate.messageHolder.addHandle("userinfo", (ssoTemplate, message) -> {
|
||||
System.out.println("收到消息:" + message);
|
||||
|
||||
// 自定义返回结果(模拟)
|
||||
return SaResult.ok()
|
||||
.set("id", message.get("loginId"))
|
||||
.set("name", "LinXiaoYu")
|
||||
.set("sex", "女")
|
||||
.set("age", 18);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,28 +3,50 @@ server:
|
||||
port: 9000
|
||||
|
||||
# Sa-Token 配置
|
||||
sa-token:
|
||||
# ------- SSO-模式一相关配置 (非模式一不需要配置)
|
||||
# cookie:
|
||||
# 配置 Cookie 作用域
|
||||
# domain: stp.com
|
||||
|
||||
# ------- SSO-模式二相关配置
|
||||
sa-token:
|
||||
# 打印操作日志
|
||||
is-log: true
|
||||
|
||||
# SSO 模式一配置 (非模式一不需要配置)
|
||||
# cookie:
|
||||
# # 配置 Cookie 作用域
|
||||
# domain: stp.com
|
||||
|
||||
# SSO-Server 配置
|
||||
sso-server:
|
||||
# Ticket有效期 (单位: 秒),默认五分钟
|
||||
# Ticket有效期 (单位: 秒),默认五分钟
|
||||
ticket-timeout: 300
|
||||
# 所有允许的授权回调地址
|
||||
# 主页路由:在 /sso/auth 登录页不指定 redirect 参数时,默认跳转的地址
|
||||
home-route: /home
|
||||
# 是否启用匿名 client (开启匿名 client 后,允许客户端接入时不提交 client 参数)
|
||||
allow-anon-client: true
|
||||
# 所有允许的授权回调地址 (匿名 client 使用)
|
||||
allow-url: "*"
|
||||
# 是否打开单点注销功能
|
||||
isSlo: true
|
||||
|
||||
# ------- SSO-模式三相关配置 (下面的配置在SSO模式三并且 is-slo=true 时打开)
|
||||
# 是否打开模式三
|
||||
isHttp: true
|
||||
# 接口调用秘钥(用于SSO模式三的单点注销功能)
|
||||
sign:
|
||||
# API 接口调用秘钥 (全局默认 + 匿名 client 使用)
|
||||
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
# ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明)
|
||||
# 应用列表:配置接入的应用信息
|
||||
clients:
|
||||
# 应用 sso-client1:采用模式一对接 (同域、同Redis)
|
||||
sso-client1:
|
||||
client: sso-client1
|
||||
allowUrl: "*"
|
||||
# 应用 sso-client2:采用模式二对接 (跨域、同Redis)
|
||||
sso-client2:
|
||||
client: sso-client2
|
||||
allowUrl: "*"
|
||||
secretKey: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
# 应用 sso-client3:采用模式三对接 (跨域、跨Redis)
|
||||
sso-client3:
|
||||
# 应用名称
|
||||
client: sso-client3
|
||||
# 允许授权地址
|
||||
allowUrl: "*"
|
||||
# 是否接收消息推送
|
||||
isPush: true
|
||||
# 消息推送地址
|
||||
pushUrl: http://sa-sso-client1.com:9003/sso/pushC
|
||||
# 接口调用秘钥 (如果不配置则使用全局默认秘钥)
|
||||
secretKey: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
|
||||
sa-token.dao: #名字可以随意取
|
||||
redis:
|
||||
@@ -32,11 +54,7 @@ sa-token.dao: #名字可以随意取
|
||||
# password: 123456
|
||||
db: 1
|
||||
maxTotal: 200
|
||||
|
||||
forest:
|
||||
# 关闭 forest 请求日志打印
|
||||
log-enabled: false
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -21,11 +21,6 @@
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Solon 依赖 -->
|
||||
<!--<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-api</artifactId>
|
||||
</dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-web</artifactId>
|
||||
|
||||
@@ -18,12 +18,12 @@ public class SaSso1ClientApp {
|
||||
System.out.println("\nSa-Token SSO模式一 Client端启动成功");
|
||||
|
||||
System.out.println();
|
||||
System.out.println("---------------------- Solon Sa-Token SSO 模式一 Client 端启动成功 ----------------------");
|
||||
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("测试前需要根据官网文档修改 hosts 文件,测试账号密码:sa / 123456");
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.sso.SaSsoManager;
|
||||
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
@@ -21,12 +24,16 @@ public class SsoClientController implements Render {
|
||||
@Produces(MimeType.TEXT_HTML_VALUE)
|
||||
@Mapping("/")
|
||||
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>" +
|
||||
"<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>";
|
||||
String url = SaFoxUtil.encodeUrl( SaFoxUtil.joinParam(SaHolder.getRequest().getUrl(), Context.current().queryString()) );
|
||||
SaSsoClientConfig cfg = SaSsoManager.getClientConfig();
|
||||
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端 (模式一)</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")</p>" +
|
||||
"<p>" +
|
||||
"<a href='" + cfg.splicingAuthUrl() + "?mode=simple&client=" + cfg.getClient() + "&redirect=" + url + "'>登录</a> - " +
|
||||
"<a href='" + cfg.splicingSignoutUrl() + "?singleDeviceIdLogout=true&back=" + url + "'>单浏览器注销</a> - " +
|
||||
"<a href='" + cfg.splicingSignoutUrl() + "?back=" + url + "'>全端注销</a> " +
|
||||
"</p>";
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -36,7 +43,6 @@ public class SsoClientController implements Render {
|
||||
if(data instanceof Exception){
|
||||
data = SaResult.error(((Exception)data).getMessage());
|
||||
}
|
||||
|
||||
ctx.render(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,14 @@ server:
|
||||
port: 9001
|
||||
|
||||
# Sa-Token 配置
|
||||
sa-token:
|
||||
sa-token:
|
||||
# 打印操作日志
|
||||
is-log: true
|
||||
|
||||
# SSO-相关配置
|
||||
sso-client:
|
||||
# client 标识
|
||||
client: sso-client1
|
||||
# SSO-Server端 - 主机地址
|
||||
server-url: http://sso.stp.com:9000
|
||||
|
||||
|
||||
@@ -53,12 +53,14 @@
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Http 请求工具 -->
|
||||
<!-- Sa-Token 插件:整合 Forest 请求工具 -->
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>forest-solon-plugin</artifactId>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-forest</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ package com.pj;
|
||||
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.dao.SaTokenDaoForRedisx;
|
||||
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
|
||||
import com.dtflys.forest.Forest;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
@@ -22,15 +20,4 @@ public class SaConfig {
|
||||
return saTokenDao;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public void configSso(SaSsoClientConfig ssoClient) {
|
||||
// 配置Http请求处理器
|
||||
ssoClient.sendHttp = url -> {
|
||||
System.out.println("------ 发起请求:" + url);
|
||||
String resStr = Forest.get(url).executeAsString();
|
||||
System.out.println("------ 请求结果:" + resStr);
|
||||
return resStr;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.pj;
|
||||
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoManager;
|
||||
import org.noear.solon.Solon;
|
||||
import org.noear.solon.annotation.SolonMain;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.pj.h5;
|
||||
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Filter;
|
||||
import org.noear.solon.core.handle.FilterChain;
|
||||
|
||||
|
||||
/**
|
||||
* 跨域过滤器
|
||||
* @author click33
|
||||
*/
|
||||
@Component(index = -200)
|
||||
public class CorsFilter implements Filter {
|
||||
static final String OPTIONS = "OPTIONS";
|
||||
|
||||
@Override
|
||||
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
|
||||
// 允许指定域访问跨域资源
|
||||
ctx.headerSet("Access-Control-Allow-Origin", "*");
|
||||
// 允许所有请求方式
|
||||
ctx.headerSet("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
|
||||
// 有效时间
|
||||
ctx.headerSet("Access-Control-Max-Age", "3600");
|
||||
// 允许的header参数
|
||||
ctx.headerSet("Access-Control-Allow-Headers", "x-requested-with,satoken");
|
||||
|
||||
// 如果是预检请求,直接返回
|
||||
if (OPTIONS.equals(ctx.method())) {
|
||||
System.out.println("=======================浏览器发来了OPTIONS预检请求==========");
|
||||
ctx.output("");
|
||||
return;
|
||||
}
|
||||
|
||||
// System.out.println("*********************************过滤器被使用**************************");
|
||||
chain.doFilter(ctx);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
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.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Render;
|
||||
|
||||
/**
|
||||
* 前后台分离架构下集成SSO所需的代码 (SSO-Client端)
|
||||
@@ -16,40 +16,30 @@ import org.noear.solon.core.handle.Render;
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class H5Controller implements Render {
|
||||
public class H5Controller {
|
||||
|
||||
// 当前是否登录
|
||||
// 判断当前是否登录
|
||||
@Mapping("/sso/isLogin")
|
||||
public Object isLogin() {
|
||||
return SaResult.data(StpUtil.isLogin());
|
||||
return SaResult.data(StpUtil.isLogin()).set("loginId", StpUtil.getLoginIdDefaultNull());
|
||||
}
|
||||
|
||||
// 返回SSO认证中心登录地址
|
||||
|
||||
// 返回SSO认证中心登录地址
|
||||
@Mapping("/sso/getSsoAuthUrl")
|
||||
public SaResult getSsoAuthUrl(String clientLoginUrl) {
|
||||
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
|
||||
String serverAuthUrl = SaSsoClientUtil.buildServerAuthUrl(clientLoginUrl, "");
|
||||
return SaResult.data(serverAuthUrl);
|
||||
}
|
||||
|
||||
// 根据ticket进行登录
|
||||
|
||||
// 根据 ticket 进行登录
|
||||
@Mapping("/sso/doLoginByTicket")
|
||||
public SaResult doLoginByTicket(String ticket) {
|
||||
Object loginId = SaSsoClientProcessor.instance.checkTicket(ticket, "/sso/doLoginByTicket");
|
||||
if(loginId != null) {
|
||||
StpUtil.login(loginId);
|
||||
return SaResult.data(StpUtil.getTokenValue());
|
||||
}
|
||||
return SaResult.error("无效ticket:" + ticket);
|
||||
SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket);
|
||||
StpUtil.login(ctr.loginId, new SaLoginParameter()
|
||||
.setTimeout(ctr.remainTokenTimeout)
|
||||
.setDeviceId(ctr.deviceId)
|
||||
);
|
||||
return SaResult.data(StpUtil.getTokenValue());
|
||||
}
|
||||
|
||||
// 全局异常拦截并转换
|
||||
@Override
|
||||
public void render(Object data, Context ctx) throws Throwable {
|
||||
if(data instanceof Exception){
|
||||
data = SaResult.error(((Exception)data).getMessage());
|
||||
}
|
||||
|
||||
ctx.render(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.pj.h5;
|
||||
|
||||
import cn.dev33.satoken.fun.strategy.SaCorsHandleFunction;
|
||||
import cn.dev33.satoken.router.SaHttpMethod;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类 (解决跨域问题)
|
||||
*
|
||||
* @author click33
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure {
|
||||
|
||||
/**
|
||||
* CORS 跨域处理策略
|
||||
*/
|
||||
@Bean
|
||||
public SaCorsHandleFunction corsHandle() {
|
||||
return (req, res, sto) -> {
|
||||
res.
|
||||
// 允许指定域访问跨域资源
|
||||
setHeader("Access-Control-Allow-Origin", "*")
|
||||
// 允许所有请求方式
|
||||
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
|
||||
// 有效时间
|
||||
.setHeader("Access-Control-Max-Age", "3600")
|
||||
// 允许的header参数
|
||||
.setHeader("Access-Control-Allow-Headers", "*");
|
||||
|
||||
// 如果是预检请求,则立即返回到前端
|
||||
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
||||
.back();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.pj.sso;
|
||||
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Filter;
|
||||
import org.noear.solon.core.handle.FilterChain;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class GlobalExceptionFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
|
||||
try {
|
||||
chain.doFilter(ctx);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
ctx.render(SaResult.error(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +1,83 @@
|
||||
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.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
import org.noear.solon.annotation.Produces;
|
||||
import org.noear.solon.annotation.*;
|
||||
import org.noear.solon.boot.web.MimeType;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Render;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO Client端 Controller
|
||||
* @author click33
|
||||
*/
|
||||
@Controller
|
||||
public class SsoClientController implements Render {
|
||||
@Configuration
|
||||
public class SsoClientController {
|
||||
|
||||
// 首页
|
||||
@Produces(MimeType.TEXT_HTML_VALUE)
|
||||
@Mapping("/")
|
||||
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>";
|
||||
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 端接收消息推送地址
|
||||
*/
|
||||
@Mapping("/sso/*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoClientProcessor.instance.dister();
|
||||
}
|
||||
|
||||
// 全局异常拦截并转换
|
||||
@Override
|
||||
public void render(Object data, Context ctx) throws Throwable {
|
||||
if(data instanceof Exception){
|
||||
data = SaResult.error(((Exception)data).getMessage());
|
||||
// 配置SSO相关参数
|
||||
@Bean
|
||||
private void configSso(SaSsoClientTemplate ssoClientTemplate) {
|
||||
|
||||
}
|
||||
|
||||
// 当前应用独自注销 (不退出其它应用)
|
||||
@Mapping("/sso/logoutByAlone")
|
||||
public Object logoutByAlone() {
|
||||
StpUtil.logout();
|
||||
return SaSsoClientProcessor.instance._ssoLogoutBack(SaHolder.getRequest(), SaHolder.getResponse());
|
||||
}
|
||||
|
||||
// 查询我的账号信息:sso-client 前端 -> sso-center 后端 -> sso-server 后端
|
||||
@Mapping("/sso/myInfo")
|
||||
public Object myInfo() {
|
||||
// 如果尚未登录
|
||||
if( ! StpUtil.isLogin()) {
|
||||
return "尚未登录,无法获取";
|
||||
}
|
||||
|
||||
ctx.render(data);
|
||||
// 获取本地 loginId
|
||||
Object loginId = StpUtil.getLoginId();
|
||||
|
||||
// 推送消息
|
||||
SaSsoMessage message = new SaSsoMessage();
|
||||
message.setType("userinfo");
|
||||
message.set("loginId", loginId);
|
||||
SaResult result = SaSsoClientUtil.pushMessageAsSaResult(message);
|
||||
|
||||
// 返回给前端
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,18 +2,21 @@
|
||||
server:
|
||||
port: 9002
|
||||
|
||||
# sa-token配置
|
||||
sa-token:
|
||||
# sa-token配置
|
||||
sa-token:
|
||||
# 打印操作日志
|
||||
is-log: true
|
||||
|
||||
# SSO-相关配置
|
||||
sso-client:
|
||||
# SSO-Server端 主机地址
|
||||
# 应用标识
|
||||
client: sso-client2
|
||||
# SSO-Server 端主机地址
|
||||
server-url: http://sa-sso-server.com:9000
|
||||
# 在 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
|
||||
# 是否打开单点注销接口
|
||||
is-slo: true
|
||||
sign:
|
||||
# API 接口调用秘钥
|
||||
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
# API 接口调用秘钥 (单点注销时会用到)
|
||||
secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
|
||||
# 配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
|
||||
sa-token.dao: #名字可以随意取
|
||||
|
||||
@@ -21,23 +21,12 @@
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Solon 依赖 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.noear</groupId>-->
|
||||
<!-- <artifactId>solon-api</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-web</artifactId>
|
||||
<version>${solon.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Http 请求工具 -->
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>forest-solon-plugin</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
@@ -59,6 +48,13 @@
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.pj.h5;
|
||||
|
||||
import cn.dev33.satoken.sso.model.SaCheckTicketResult;
|
||||
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
|
||||
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.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
|
||||
/**
|
||||
* 前后台分离架构下集成SSO所需的代码 (SSO-Client端)
|
||||
* <p>(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)</p>
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class H5Controller {
|
||||
|
||||
// 判断当前是否登录
|
||||
@Mapping("/sso/isLogin")
|
||||
public Object isLogin() {
|
||||
return SaResult.data(StpUtil.isLogin()).set("loginId", StpUtil.getLoginIdDefaultNull());
|
||||
}
|
||||
|
||||
// 返回SSO认证中心登录地址
|
||||
@Mapping("/sso/getSsoAuthUrl")
|
||||
public SaResult getSsoAuthUrl(String clientLoginUrl) {
|
||||
String serverAuthUrl = SaSsoClientUtil.buildServerAuthUrl(clientLoginUrl, "");
|
||||
return SaResult.data(serverAuthUrl);
|
||||
}
|
||||
|
||||
// 根据 ticket 进行登录
|
||||
@Mapping("/sso/doLoginByTicket")
|
||||
public SaResult doLoginByTicket(String ticket) {
|
||||
SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket);
|
||||
StpUtil.login(ctr.loginId, new SaLoginParameter()
|
||||
.setTimeout(ctr.remainTokenTimeout)
|
||||
.setDeviceId(ctr.deviceId)
|
||||
);
|
||||
return SaResult.data(StpUtil.getTokenValue());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.pj.h5;
|
||||
|
||||
import cn.dev33.satoken.fun.strategy.SaCorsHandleFunction;
|
||||
import cn.dev33.satoken.router.SaHttpMethod;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类 (解决跨域问题)
|
||||
*
|
||||
* @author click33
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure {
|
||||
|
||||
/**
|
||||
* CORS 跨域处理策略
|
||||
*/
|
||||
@Bean
|
||||
public SaCorsHandleFunction corsHandle() {
|
||||
return (req, res, sto) -> {
|
||||
res.
|
||||
// 允许指定域访问跨域资源
|
||||
setHeader("Access-Control-Allow-Origin", "*")
|
||||
// 允许所有请求方式
|
||||
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
|
||||
// 有效时间
|
||||
.setHeader("Access-Control-Max-Age", "3600")
|
||||
// 允许的header参数
|
||||
.setHeader("Access-Control-Allow-Headers", "*");
|
||||
|
||||
// 如果是预检请求,则立即返回到前端
|
||||
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
||||
.back();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.pj.sso;
|
||||
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Filter;
|
||||
import org.noear.solon.core.handle.FilterChain;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
* @author click33
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class GlobalExceptionFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
|
||||
try {
|
||||
chain.doFilter(ctx);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
ctx.render(SaResult.error(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +1,82 @@
|
||||
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.SaSsoUtil;
|
||||
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.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
import org.noear.solon.annotation.Produces;
|
||||
import org.noear.solon.annotation.*;
|
||||
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
|
||||
*/
|
||||
@Controller
|
||||
public class SsoClientController implements Render {
|
||||
@Configuration
|
||||
public class SsoClientController {
|
||||
|
||||
// SSO-Client端:首页
|
||||
@Produces(MimeType.TEXT_HTML_VALUE)
|
||||
@Mapping("/")
|
||||
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>";
|
||||
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 端接收消息推送地址
|
||||
*/
|
||||
@Mapping("/sso/*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoClientProcessor.instance.dister();
|
||||
}
|
||||
|
||||
// 查询我的账号信息
|
||||
|
||||
// 配置SSO相关参数
|
||||
@Bean
|
||||
private void configSso(SaSsoClientTemplate ssoClientTemplate) {
|
||||
|
||||
}
|
||||
|
||||
// 当前应用独自注销 (不退出其它应用)
|
||||
@Mapping("/sso/logoutByAlone")
|
||||
public Object logoutByAlone() {
|
||||
StpUtil.logout();
|
||||
return SaSsoClientProcessor.instance._ssoLogoutBack(SaHolder.getRequest(), SaHolder.getResponse());
|
||||
}
|
||||
|
||||
// 查询我的账号信息:sso-client 前端 -> sso-center 后端 -> sso-server 后端
|
||||
@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;
|
||||
}
|
||||
|
||||
// 全局异常拦截并转换
|
||||
@Override
|
||||
public void render(Object data, Context ctx) throws Throwable {
|
||||
if(data instanceof Exception){
|
||||
data = SaResult.error(((Exception)data).getMessage());
|
||||
// 如果尚未登录
|
||||
if( ! StpUtil.isLogin()) {
|
||||
return "尚未登录,无法获取";
|
||||
}
|
||||
|
||||
ctx.render(data);
|
||||
// 获取本地 loginId
|
||||
Object loginId = StpUtil.getLoginId();
|
||||
|
||||
// 推送消息
|
||||
SaSsoMessage message = new SaSsoMessage();
|
||||
message.setType("userinfo");
|
||||
message.set("loginId", loginId);
|
||||
SaResult result = SaSsoClientUtil.pushMessageAsSaResult(message);
|
||||
|
||||
// 返回给前端
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import cn.dev33.satoken.sso.config.SaSsoClientConfig;
|
||||
import com.dtflys.forest.Forest;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author noear 2023/3/13 created
|
||||
*/
|
||||
@Configuration
|
||||
public class SsoConfig {
|
||||
// 配置SSO相关参数
|
||||
@Bean
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,31 +3,29 @@ server:
|
||||
port: 9003
|
||||
|
||||
# sa-token配置
|
||||
sa-token:
|
||||
# SSO-相关配置
|
||||
sso-client:
|
||||
# SSO-Server端 主机地址
|
||||
server-url: http://sa-sso-server.com:9000
|
||||
# 使用Http请求校验ticket
|
||||
is-http: true
|
||||
# 打开单点注销功能
|
||||
is-slo: true
|
||||
sign:
|
||||
# 接口调用秘钥
|
||||
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
sa-token:
|
||||
# 打印操作日志
|
||||
is-log: true
|
||||
|
||||
# sso-client 相关配置
|
||||
sso-client:
|
||||
# 应用标识
|
||||
client: sso-client3
|
||||
# sso-server 端主机地址
|
||||
server-url: http://sa-sso-server.com:9000
|
||||
# 在 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
|
||||
|
||||
# 配置 Sa-Token Dao(此处与SSO-Server端连接不同的Redis)
|
||||
sa-token.dao: #名字可以随意取
|
||||
redis:
|
||||
server: "localhost:6379"
|
||||
# password: 123456
|
||||
db: 2
|
||||
db: 4
|
||||
maxTotal: 200
|
||||
|
||||
forest:
|
||||
# 关闭 forest 请求日志打印
|
||||
log-enabled: false
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package com.pj.h5;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.fun.strategy.SaCorsHandleFunction;
|
||||
import cn.dev33.satoken.router.SaHttpMethod;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类 (解决跨域问题)
|
||||
@@ -15,50 +12,29 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
* @author click33
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
public class SaTokenConfigure {
|
||||
|
||||
/**
|
||||
* 注册 [Sa-Token 全局过滤器]
|
||||
* CORS 跨域处理策略
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
public SaCorsHandleFunction corsHandle() {
|
||||
return (req, res, sto) -> {
|
||||
res.
|
||||
// 允许指定域访问跨域资源
|
||||
setHeader("Access-Control-Allow-Origin", "*")
|
||||
// 允许所有请求方式
|
||||
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
|
||||
// 有效时间
|
||||
.setHeader("Access-Control-Max-Age", "3600")
|
||||
// 允许的header参数
|
||||
.setHeader("Access-Control-Allow-Headers", "*");
|
||||
|
||||
// 指定 [拦截路由] 与 [放行路由]
|
||||
.addInclude("/**").addExclude("/favicon.ico")
|
||||
|
||||
// 认证函数: 每次请求执行
|
||||
.setAuth(obj -> {
|
||||
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
|
||||
// ...
|
||||
})
|
||||
|
||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
||||
.setError(e -> {
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
|
||||
// 前置函数:在每次认证函数之前执行
|
||||
.setBeforeAuth(obj -> {
|
||||
SaHolder.getResponse()
|
||||
|
||||
// ---------- 设置跨域响应头 ----------
|
||||
// 允许指定域访问跨域资源
|
||||
.setHeader("Access-Control-Allow-Origin", "*")
|
||||
// 允许所有请求方式
|
||||
.setHeader("Access-Control-Allow-Methods", "*")
|
||||
// 允许的header参数
|
||||
.setHeader("Access-Control-Allow-Headers", "*")
|
||||
// 有效时间
|
||||
.setHeader("Access-Control-Max-Age", "3600")
|
||||
;
|
||||
|
||||
// 如果是预检请求,则立即返回到前端
|
||||
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
||||
.back();
|
||||
})
|
||||
;
|
||||
// 如果是预检请求,则立即返回到前端
|
||||
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
||||
.back();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ server:
|
||||
port: 9001
|
||||
|
||||
# Sa-Token 配置
|
||||
sa-token:
|
||||
sa-token:
|
||||
# 打印操作日志
|
||||
is-log: true
|
||||
|
||||
# SSO-相关配置
|
||||
sso-client:
|
||||
# client 标识
|
||||
|
||||
@@ -70,7 +70,7 @@ public class CustomSaSsoServerTemplate extends SaSsoServerTemplate {
|
||||
```
|
||||
|
||||
|
||||
### 2、在 sso-server 端开启匿名 client 接入
|
||||
### 2、在 sso-client 端不要配置 client 字段
|
||||
|
||||
然后在对应的应用端不要配置 client 字段,例如:
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public class SaSsoClientModel implements Serializable {
|
||||
/**
|
||||
* 所有允许的授权回调地址,多个用逗号隔开 (不在此列表中的 URL 将禁止下放 ticket )
|
||||
*/
|
||||
public String allowUrl = "*";
|
||||
public String allowUrl = "";
|
||||
|
||||
/**
|
||||
* 是否接收推送消息
|
||||
|
||||
Reference in New Issue
Block a user