SSO 三种模式

This commit is contained in:
click33
2021-06-29 23:32:35 +08:00
parent 33318c6835
commit d2e00b341d
73 changed files with 1908 additions and 246 deletions

View File

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

View File

@@ -0,0 +1,59 @@
<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-sso2-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/>
</parent>
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.20.0</sa-token-version>
</properties>
<dependencies>
<!-- SpringBoot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- 提供redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 视图引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,14 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SaSsoServerApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoServerApplication.class, args);
System.out.println("\nSa-Token-SSO 认证中心启动成功");
}
}

View File

@@ -0,0 +1,21 @@
package com.pj.sso;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.pj.util.AjaxJson;
/**
* 全局异常处理
*/
@RestControllerAdvice
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ExceptionHandler
public AjaxJson handlerException(Exception e) {
e.printStackTrace();
return AjaxJson.getError(e.getMessage());
}
}

View File

@@ -0,0 +1,49 @@
package com.pj.sso;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.sso.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
/**
* Sa-Token-SSO Server端 Controller
* @author kong
*
*/
@RestController
public class SsoServerController {
// SSO-Server端授权地址跳转到登录页面
@RequestMapping("ssoAuth")
public Object ssoAuth(String redirect) {
/*
* 此处两种情况分开处理:
* 1、如果在SSO认证中心尚未登录则先去登登录
* 2、如果在SSO认证中心尚已登录则开始对redirect地址下放ticket引导授权
*/
// 情况1尚未登录
if(StpUtil.isLogin() == false) {
// return "当前会话在SSO-Server端尚未登录请先访问<a href='/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>进行登录之后,刷新页面开始授权";
return new ModelAndView("sa-login.html");
}
// 情况2已经登录开始构建授权重定向地址下放ticket
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
return new ModelAndView("redirect:" + redirectUrl);
}
// SSO-Server端登录接口
@RequestMapping("doLogin")
public AjaxJson doLogin(String name, String pwd) {
// 此处仅做模拟登录,真实环境应该查询数据进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return AjaxJson.getSuccess("登录成功!");
}
return AjaxJson.getError("登录失败!");
}
}

View File

@@ -0,0 +1,162 @@
package com.pj.util;
import java.io.Serializable;
import java.util.List;
/**
* ajax请求返回Json格式数据的封装
*/
public class AjaxJson implements Serializable{
private static final long serialVersionUID = 1L; // 序列化版本号
public static final int CODE_SUCCESS = 200; // 成功状态码
public static final int CODE_ERROR = 500; // 错误状态码
public static final int CODE_WARNING = 501; // 警告状态码
public static final int CODE_NOT_JUR = 403; // 无权限状态码
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
public int code; // 状态码
public String msg; // 描述信息
public Object data; // 携带对象
public Long dataCount; // 数据总数,用于分页
/**
* 返回code
* @return
*/
public int getCode() {
return this.code;
}
/**
* 给msg赋值连缀风格
*/
public AjaxJson setMsg(String msg) {
this.msg = msg;
return this;
}
public String getMsg() {
return this.msg;
}
/**
* 给data赋值连缀风格
*/
public AjaxJson setData(Object data) {
this.data = data;
return this;
}
/**
* 将data还原为指定类型并返回
*/
@SuppressWarnings("unchecked")
public <T> T getData(Class<T> cs) {
return (T) data;
}
// ============================ 构建 ==================================
public AjaxJson(int code, String msg, Object data, Long dataCount) {
this.code = code;
this.msg = msg;
this.data = data;
this.dataCount = dataCount;
}
// 返回成功
public static AjaxJson getSuccess() {
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
}
public static AjaxJson getSuccess(String msg) {
return new AjaxJson(CODE_SUCCESS, msg, null, null);
}
public static AjaxJson getSuccess(String msg, Object data) {
return new AjaxJson(CODE_SUCCESS, msg, data, null);
}
public static AjaxJson getSuccessData(Object data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
public static AjaxJson getSuccessArray(Object... data) {
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
}
// 返回失败
public static AjaxJson getError() {
return new AjaxJson(CODE_ERROR, "error", null, null);
}
public static AjaxJson getError(String msg) {
return new AjaxJson(CODE_ERROR, msg, null, null);
}
// 返回警告
public static AjaxJson getWarning() {
return new AjaxJson(CODE_ERROR, "warning", null, null);
}
public static AjaxJson getWarning(String msg) {
return new AjaxJson(CODE_WARNING, msg, null, null);
}
// 返回未登录
public static AjaxJson getNotLogin() {
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
}
// 返回没有权限的
public static AjaxJson getNotJur(String msg) {
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
}
// 返回一个自定义状态码的
public static AjaxJson get(int code, String msg){
return new AjaxJson(code, msg, null, null);
}
// 返回分页和数据的
public static AjaxJson getPageData(Long dataCount, Object data){
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
}
// 返回,根据受影响行数的(大于0=ok小于0=error)
public static AjaxJson getByLine(int line){
if(line > 0){
return getSuccess("ok", line);
}
return getError("error").setData(line);
}
// 返回,根据布尔值来确定最终结果的 (true=okfalse=error)
public static AjaxJson getByBoolean(boolean b){
return b ? getSuccess("ok") : getError("error");
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@SuppressWarnings("rawtypes")
@Override
public String toString() {
String data_string = null;
if(data == null){
} else if(data instanceof List){
data_string = "List(length=" + ((List)data).size() + ")";
} else {
data_string = data.toString();
}
return "{"
+ "\"code\": " + this.getCode()
+ ", \"msg\": \"" + this.getMsg() + "\""
+ ", \"data\": " + data_string
+ ", \"dataCount\": " + dataCount
+ "}";
}
}

View File

@@ -0,0 +1,42 @@
# 端口
server:
port: 9000
spring:
# Sa-Token配置
sa-token:
# SSO-相关配置
sso:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址 (此处为了方便测试配置为*,线上生产环境一定要配置为详细地地址)
allow-url: http://sa-sso-client1.com:9001/ssoLogin, http://sa-sso-client2.com:9001/ssoLogin, http://sa-sso-client3.com:9001/ssoLogin
# Redis配置
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间(毫秒)
timeout: 10ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

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

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

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: "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: " + "http://sa-token.dev33.cn/";
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="@{/}" />
<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>