添加示例项目:sa-token-demo-sso-server-solon

This commit is contained in:
noear 2023-01-03 11:57:22 +08:00
parent d09366602a
commit c744bd7744
23 changed files with 532 additions and 3 deletions

View File

@ -24,9 +24,10 @@
<module>sa-token-plugin</module>
<!-- <module>sa-token-test</module> -->
<!-- <module>sa-token-demo/sa-token-demo-solon</module> -->
</modules>
<!-- 开源协议 apache 2.0 -->
<!-- <module>sa-token-demo/sa-token-demo-sso-server-solon</module> -->
</modules>
<!-- 开源协议 apache 2.0 -->
<licenses>
<license>
<name>Apache 2</name>

View File

@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.idea/
.factorypath

View File

@ -0,0 +1,68 @@
<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-sso-server-solon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>1.12.0</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.33.0</sa-token.version>
</properties>
<dependencies>
<!-- SpringBoot Web依赖 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-solon-plugin</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 插件整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redisx</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 视图引擎(在前后端不分离模式下提供视图支持) -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.view.thymeleaf</artifactId>
</dependency>
<!-- Http请求工具在模式三的单点注销功能下用到如不需要可以注释掉 -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>forest-solon-plugin</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,13 @@
package com.pj;
import org.noear.solon.Solon;
public class SaSsoServerApp {
public static void main(String[] args) {
Solon.start(SaSsoServerApp.class, args);
System.out.println("\n------ Sa-Token-SSO 统一认证中心启动成功 ");
}
}

View File

@ -0,0 +1,39 @@
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 kong
*/
@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);
}
}

View File

@ -0,0 +1,57 @@
package com.pj.h5;
import cn.dev33.satoken.sso.SaSsoConsts;
import cn.dev33.satoken.sso.SaSsoUtil;
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端
* <p>如果不需要前后端分离架构下集成SSO可删除此包下所有代码</p>
* @author kong
*
*/
@Controller
public class H5Controller implements Render {
/**
* 获取 redirectUrl
*/
@Mapping("/sso/getRedirectUrl")
private Object getRedirectUrl(String redirect, String mode, String client) {
// 未登录情况下返回 code=401
if (StpUtil.isLogin() == false) {
return SaResult.code(401);
}
// 已登录情况下构建 redirectUrl
if (SaSsoConsts.MODE_SIMPLE.equals(mode)) {
// 模式一
SaSsoUtil.checkRedirectUrl(SaFoxUtil.decoderUrl(redirect));
return SaResult.data(redirect);
} else {
// 模式二或模式三
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), client, redirect);
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);
}
}
}

View File

@ -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 kong
*
*/
@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()));
}
}
}

View File

@ -0,0 +1,66 @@
package com.pj.sso;
import cn.dev33.satoken.config.SaSsoConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoOfRedis;
import cn.dev33.satoken.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 {
/**
* 构建 SaSsoConfig bean
* */
@Bean
public SaSsoConfig getSaSsoConfig(@Inject("${sa-token.sso}") SaSsoConfig ssoConfig) {
return ssoConfig;
}
/**
* 构建建 SaToken redis dao如果不需要 redis可以注释掉
* */
@Bean
public SaTokenDao saTokenDaoInit(@Inject("${sa-token-dao.redis}") SaTokenDaoOfRedis saTokenDao) {
return saTokenDao;
}
// 配置SSO相关参数
@Bean
public void configSso(SaSsoConfig sso) {
// 配置未登录时返回的View
sso.setNotLoginView(() -> {
return new ModelAndView("sa-login.html");
});
// 配置登录处理函数
sso.setDoLoginHandle((name, pwd) -> {
// 此处仅做模拟登录真实环境应该查询数据进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
}
return SaResult.error("登录失败!");
});
// 配置 Http 请求处理器 在模式三的单点注销功能下用到如不需要可以注释掉
sso.setSendHttp(url -> {
try {
// 发起 http 请求
System.out.println("------ 发起请求:" + url);
return Forest.get(url).executeAsString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
});
}
}

View File

@ -0,0 +1,27 @@
package com.pj.sso;
import cn.dev33.satoken.sso.SaSsoProcessor;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Mapping;
/**
* Sa-Token-SSO Server端 Controller
* @author kong
*
*/
@Controller
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=账号idsecretkey=接口调用秘钥
*/
@Mapping("/sso/*")
public Object ssoRequest() {
return SaSsoProcessor.instance.serverDister();
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
/*! layer mobile-v2.0.0 Web弹层组件 MIT License http://layer.layui.com/mobile By 贤心 */
;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'<h3 style="'+(e?n.title[1]:"")+'">'+(e?n.title[0]:n.title)+"</h3>":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e='<span yes type="1">'+n.btn[0]+"</span>",2===t&&(e='<span no type="0">'+n.btn[1]+"</span>"+e),'<div class="layui-m-layerbtn">'+e+"</div>"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='<i></i><i class="layui-m-layerload"></i><i></i><p>'+(n.content||"")+"</p>"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"<div "+("string"==typeof n.shade?'style="'+n.shade+'"':"")+' class="layui-m-layershade"></div>':"")+'<div class="layui-m-layermain" '+(n.fixed?"":'style="position:static;"')+'><div class="layui-m-layersection"><div class="layui-m-layerchild '+(n.skin?"layui-m-layer-"+n.skin+" ":"")+(n.className?n.className:"")+" "+(n.anim?"layui-m-anim-"+n.anim:"")+'" '+(n.style?'style="'+n.style+'"':"")+">"+l+'<div class="layui-m-layercont">'+n.content+"</div>"+c+"</div></div></div>",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;o<r;o++)l.touch(s[o],a);if(e.shade&&e.shadeClose){var c=t[i]("layui-m-layershade")[0];l.touch(c,function(){layer.close(n.index,e.end)})}e.end&&(l.end[n.index]=e.end)},e.layer={v:"2.0",index:r,open:function(e){var t=new c(e||{});return t.index},close:function(e){var n=a("#"+o[0]+e)[0];n&&(n.innerHTML="",t.body.removeChild(n),clearTimeout(l.timer[e]),delete l.timer[e],"function"==typeof l.end[e]&&l.end[e](),delete l.end[e])},closeAll:function(){for(var e=t[i](o[0]),n=0,a=e.length;n<a;n++)layer.close(0|e[0].getAttribute("index"))}},"function"==typeof define?define(function(){return layer}):function(){var e=document.scripts,n=e[e.length-1],i=n.src,a=i.substring(0,i.lastIndexOf("/")+1);n.getAttribute("merge")||document.head.appendChild(function(){var e=t.createElement("link");return e.href=a+"need/layer.css?2.0",e.type="text/css",e.rel="styleSheet",e.id="layermcss",e}())}()}(window);

View File

@ -0,0 +1,59 @@
*{margin: 0; padding: 0;}
body{font-family: Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;}
::-webkit-input-placeholder{color: #ccc;}
/* 视图盒子 */
.view-box{position: relative; width: 100vw; height: 100vh; overflow: hidden;}
/* 背景 EAEFF3 */
.bg-1{height: 50%; background: linear-gradient(to bottom right, #0466c5, #3496F5);}
.bg-2{height: 50%; background-color: #EAEFF3;}
/* 渐变背景 */
/*.bg-1{
background-size: 500%;
background-image: linear-gradient(125deg,#0466c5,#3496F5,#0466c5,#3496F5,#0466c5,#2496F5);
animation: bganimation 30s infinite;
}
@keyframes bganimation{
0%{background-position: 0% 50%;}
50%{background-position: 100% 50%;}
100%{background-position: 0% 50%;}
} */
/* 背景 */
.bg-1{background: #101C34;}
.bg-2{background: #101C34;}
/* .bg-1{height: 100%; background-image: url(./login-bg.png); background-size: 100% 100%;} */
/* 内容盒子 */
.content-box{position: absolute; width: 100vw; height: 100vh; top: 0px;}
/* 登录盒子 */
/* .login-box{width: 400px; height: 400px; position: absolute; left: calc(50% - 200px); top: calc(50% - 200px); max-width: 90%; } */
.login-box{width: 400px; margin: auto; max-width: 90%; height: 100%;}
.login-box{display: flex; align-items: center; text-align: center;}
/* 表单 */
.from-box{flex: 1; padding: 20px 50px; background-color: #FFF;}
.from-box{border-radius: 1px; box-shadow: 1px 1px 20px #666;}
.from-title{margin-top: 20px; margin-bottom: 30px; text-align: center;}
/* 输入框 */
.from-item{border: 0px #000 solid; margin-bottom: 15px;}
.s-input{width: 100%; line-height: 32px; height: 32px; text-indent: 1em; outline: 0; border: 1px #ccc solid; border-radius: 3px; transition: all 0.2s;}
.s-input{font-size: 12px;}
.s-input:focus{border-color: #409eff}
/* 登录按钮 */
.s-btn{ text-indent: 0; cursor: pointer; background-color: #409EFF; border-color: #409EFF; color: #FFF;}
.s-btn:hover{background-color: #50aEFF;}
/* 重置按钮 */
.reset-box{text-align: left; font-size: 12px;}
.reset-box a{text-decoration: none;}
.reset-box a:hover{text-decoration: underline;}
/* loading框样式 */
.ajax-layer-load.layui-layer-dialog{min-width: 0px !important; background-color: rgba(0,0,0,0.85);}
.ajax-layer-load.layui-layer-dialog .layui-layer-content{padding: 10px 20px 10px 40px; color: #FFF;}
.ajax-layer-load.layui-layer-dialog .layui-layer-content .layui-layer-ico{width: 20px; height: 20px; background-size: 20px 20px; top: 12px; }

View File

@ -0,0 +1,65 @@
// sa
var sa = {};
// 打开loading
sa.loading = function(msg) {
layer.closeAll(); // 开始前先把所有弹窗关了
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load' });
};
// 隐藏loading
sa.hideLoading = function() {
layer.closeAll();
};
// ----------------------------------- 登录事件 -----------------------------------
$('.login-btn').click(function(){
sa.loading("正在登录...");
// 开始登录
setTimeout(function() {
$.ajax({
url: "sso/doLogin",
type: "post",
data: {
name: $('[name=name]').val(),
pwd: $('[name=pwd]').val()
},
dataType: 'json',
success: function(res){
console.log('返回数据:', res);
sa.hideLoading();
if(res.code == 200) {
layer.msg('登录成功', {anim: 0, icon: 6 });
setTimeout(function() {
location.reload();
}, 800)
} else {
layer.msg(res.msg, {anim: 6, icon: 2 });
}
},
error: function(xhr, type, errorThrown){
sa.hideLoading();
if(xhr.status == 0){
return layer.alert('无法连接到服务器,请检查网络');
}
return layer.alert("异常:" + JSON.stringify(xhr));
}
});
}, 400);
});
// 绑定回车事件
$('[name=name],[name=pwd]').bind('keypress', function(event){
if(event.keyCode == "13") {
$('.login-btn').click();
}
});
// 输入框获取焦点
$("[name=name]").focus();
// 打印信息
var str = "This page is provided by Sa-Token, Please refer to: " + "https://sa-token.cc/";
console.log(str);

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Sa-SSO-Server 认证中心-登录</title>
<meta charset="utf-8">
<base th:href="@{/static}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="./sa-res/login.css">
</head>
<body>
<div class="view-box">
<div class="bg-1"></div>
<div class="bg-2"></div>
<div class="content-box">
<div class="login-box">
<div class="from-box">
<h2 class="from-title">Sa-SSO-Server 认证中心</h2>
<div class="from-item">
<input class="s-input" name="name" placeholder="请输入账号" />
</div>
<div class="from-item">
<input class="s-input" name="pwd" type="password" placeholder="请输入密码" />
</div>
<div class="from-item">
<button class="s-input s-btn login-btn">登录</button>
</div>
<div class="from-item reset-box">
<a href="javascript: location.reload();" >刷新</a>
</div>
</div>
</div>
</div>
<!-- 底部 版权 -->
<div style="position: absolute; bottom: 40px; width: 100%; text-align: center; color: #666;">
This page is provided by Sa-Token-SSO
</div>
</div>
<!-- scripts -->
<script src="./sa-res/jquery.min.js"></script>
<script src="./sa-res/layer/layer.js"></script>
<script src="./sa-res/login.js"></script>
</body>
</html>

View File

@ -0,0 +1,41 @@
# 端口
server:
port: 9000
# Sa-Token 配置
sa-token:
# ------- SSO-模式一相关配置 (非模式一不需要配置)
# cookie:
# 配置 Cookie 作用域
# domain: stp.com
# ------- SSO-模式二相关配置
sso:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址
allow-url: "*"
# 是否打开单点注销功能
is-slo: true
# ------- SSO-模式三相关配置 下面的配置在SSO模式三并且 is-slo=true 时打开)
# 是否打开模式三
isHttp: true
# 接口调用秘钥用于SSO模式三的单点注销功能
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器文档有步骤说明
sa-token-dao: #名字可以随意取
redis:
server: "localhost:6379"
password: 123456
db: 1
maxTotal: 200
forest:
# 关闭 forest 请求日志打印
log-enabled: false