完善单点登录demo

This commit is contained in:
click33 2021-08-26 23:17:39 +08:00
parent 38903d5a46
commit 35cf52c684
21 changed files with 644 additions and 201 deletions

View File

@ -41,6 +41,14 @@ cd sa-token-demo-sso1
call mvn clean call mvn clean
cd .. cd ..
cd sa-token-demo-sso1-server
call mvn clean
cd ..
cd sa-token-demo-sso1-client
call mvn clean
cd ..
cd sa-token-demo-sso2-server cd sa-token-demo-sso2-server
call mvn clean call mvn clean
cd .. cd ..

View File

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

View File

@ -0,0 +1,60 @@
<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-sso1-client</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.25.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>
<!-- Sa-Token插件权限缓存与业务缓存分离 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis</artifactId>
<version>${sa-token-version}</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,19 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* SSO模式一Client端 Demo
* @author kong
*
*/
@SpringBootApplication
public class SaSsoClientApplication {
public static void main(String[] args) {
SpringApplication.run(SaSsoClientApplication.class, args);
System.out.println("\nSa-Token-SSO Client端启动成功");
}
}

View File

@ -0,0 +1,39 @@
package com.pj.sso;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token-SSO Client端 Controller
* @author kong
*/
@RestController
public class SsoClientController {
/*
* SSO-Client端首页
*/
@RequestMapping("/")
public String index() {
String authUrl = SaManager.getConfig().getSso().getAuthUrl();
String solUrl = SaManager.getConfig().getSso().getSloUrl();
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='" + authUrl + "?redirect=' + encodeURIComponent(location.href);\">登录</a> " +
"<a href=\"javascript:location.href='" + solUrl + "?back=' + encodeURIComponent(location.href);\">注销</a> </p>";
return str;
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@ -0,0 +1,40 @@
# 端口
server:
port: 9001
# sa-token配置
sa-token:
# SSO-相关配置
sso:
# SSO-Server端 单点登录地址
auth-url: http://sso.stp.com:9000/sso/auth
# SSO-Server端 单点注销地址
slo-url: http://sso.stp.com:9000/sso/logout
# 配置Sa-Token单独使用的Redis连接 此处需要和SSO-Server端连接同一个Redis
alone-redis:
# Redis数据库索引
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

View File

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

View File

@ -0,0 +1,57 @@
<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-sso1-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.25.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>
<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,19 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* SSO模式一Server端 Demo
* @author kong
*
*/
@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,62 @@
package com.pj.sso;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token-SSO Server端 Controller
* @author kong
*
*/
@RestController
public class SsoServerController {
/*
* SSO-Server端统一登录地址
*/
@RequestMapping("/sso/auth")
public Object ssoAuth(String redirect) {
// 如果尚未登录则返回登录视图进行登录
if(StpUtil.isLogin() == false) {
return new ModelAndView("sa-login.html");
}
// 如果已登录则原路返回到 Client端
return SaHolder.getResponse().redirect(redirect);
}
// 处理登录请求
@RequestMapping("/sso/doLogin")
public SaResult ssoDoLogin(String name, String pwd) {
// 模拟登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功!");
}
return SaResult.error("登录失败!");
}
// 单点注销地址
@RequestMapping("/sso/logout")
public Object ssoLogout(String back) {
StpUtil.logout();
if(SaFoxUtil.isEmpty(back)) {
return SaResult.ok();
}
return SaHolder.getResponse().redirect(back);
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@ -0,0 +1,38 @@
# 端口
server:
port: 9000
# Sa-Token配置
sa-token:
# 写入Cookie时显式指定的作用域, 用于单点登录二级域名共享Cookie
cookie-domain: stp.com
spring:
# Redis配置
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

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: " + "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="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://www.layuicdn.com/layer-v3.1.1/layer.js"></script>
<script src="./sa-res/login.js"></script>
</body>
</html>

View File

@ -8,9 +8,29 @@ import cn.dev33.satoken.SaManager;
@SpringBootApplication @SpringBootApplication
public class SaSsoApplication { public class SaSsoApplication {
/*
demo 意在使用最少的代码演示一下SSO模式一的认证原理
1改hosts(C:\windows\system32\drivers\etc\hosts)
127.0.0.1 sso.stp.com
127.0.0.1 s1.stp.com
127.0.0.1 s2.stp.com
127.0.0.1 s3.stp.com
2运行项目
启动 SaSsoApplication
3浏览器访问
http://s1.stp.com:8081/sso/isLogin
http://s2.stp.com:8081/sso/isLogin
http://s3.stp.com:8081/sso/isLogin
均显示未登录
4然后访问任意节点的登录接口
http://s1.stp.com:8081/sso/doLogin
5重复步骤3刷新三个地址
均显示已登录
*/
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SaSsoApplication.class, args); SpringApplication.run(SaSsoApplication.class, args);
System.out.println("\n启动成功Sa-Token配置如下" + SaManager.getConfig()); System.out.println("\n启动成功Sa-Token配置如下" + SaManager.getConfig());
} }
} }

View File

@ -4,9 +4,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.pj.util.AjaxJson;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/** /**
* 测试: 同域单点登录 * 测试: 同域单点登录
@ -15,21 +14,21 @@ import cn.dev33.satoken.stp.StpUtil;
@RestController @RestController
@RequestMapping("/sso/") @RequestMapping("/sso/")
public class SsoController { public class SsoController {
// 测试进行登录 // 测试进行登录
@RequestMapping("doLogin") @RequestMapping("doLogin")
public AjaxJson doLogin(@RequestParam(defaultValue = "10001") String id) { public SaResult doLogin(@RequestParam(defaultValue = "10001") String id) {
System.out.println("---------------- 进行登录 "); System.out.println("---------------- 进行登录 ");
StpUtil.login(id); StpUtil.login(id);
return AjaxJson.getSuccess("登录成功: " + id); return SaResult.ok("登录成功: " + id);
} }
// 测试是否登录 // 测试是否登录
@RequestMapping("isLogin") @RequestMapping("isLogin")
public AjaxJson isLogin() { public SaResult isLogin() {
System.out.println("---------------- 是否登录 "); System.out.println("---------------- 是否登录 ");
boolean isLogin = StpUtil.isLogin(); boolean isLogin = StpUtil.isLogin();
return AjaxJson.getSuccess("是否登录: " + isLogin); return SaResult.ok("是否登录: " + isLogin);
} }
} }

View File

@ -1,162 +0,0 @@
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

@ -1,11 +1,13 @@
# Sa-Token-SSO 单点登录模块 # Sa-Token-SSO 单点登录模块
凡是稍微上点规模的系统,统一认证中心都是绕不过去的槛。而单点登录——便是我们搭建统一认证中心的关键。
--- ---
### 什么是单点登录?解决什么问题? ### 什么是单点登录?解决什么问题?
举个场景假设我们的系统被切割为N个部分商城、论坛、直播、社交…… 如果用户每访问一个模块都要登录一次,那么用户将会疯掉, 举个场景假设我们的系统被切割为N个部分商城、论坛、直播、社交…… 如果用户每访问一个模块都要登录一次,那么用户将会疯掉,
为了优化用户体验我们急需一套机制将这N个系统的认证授权互通共享让用户在一个系统登录之后便可以畅通无阻的访问其它所有系统 为了优化用户体验我们急需一套机制将这N个系统的认证授权互通共享让用户在一个系统登录之后便可以畅通无阻的访问其它所有系统
单点登录——就是为了解决这个问题而生! 单点登录——就是为了解决这个问题而生!
@ -13,19 +15,19 @@
### 架构选型 ### 架构选型
对于单点登录网上教程大多以CAS模式为主其实对于不同的系统架构实现单点登录的步骤也大为不同Sa-Token由简入难将其划分为三种模式 对于单点登录网上教程大多以CAS模式为主其实对于不同的系统架构实现单点登录的步骤也大为不同Sa-Token 由简入难将其划分为三种模式:
| 系统架构 | 采用模式 | 简介 | 文档链接 | | 系统架构 | 采用模式 | 简介 | 文档链接 |
| :-------- | :-------- | :-------- | :-------- | | :-------- | :-------- | :-------- | :-------- |
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso1) | | 前端同域 + 后端同 Redis | 模式一 | 共享 Cookie 同步会话 | [文档](/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso1) |
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso2-server) | | 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso2-server) |
| 前端不同域 + 后端 不同Redis | 模式三 | Http请求获取会话 | [文档](/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso3-server) | | 前端不同域 + 后端不同 Redis | 模式三 | Http请求获取会话 | [文档](/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso3-server) |
1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com`、`c2.domain.com`、`c3.domain.com` 1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com`、`c2.domain.com`、`c3.domain.com`
2. 后端同Redis就是指多个系统可以连接同一个Redis其它的缓存数据中心亦可。PS这里并不需要把所有项目的数据都放在同一个Redis中Sa-Token提供了 **`[权限缓存与业务缓存分离]`** 的解决方案,详情戳:[Alone独立Redis插件](http://sa-token.dev33.cn/doc/index.html#/plugin/alone-redis) 2. 后端同Redis就是指多个系统可以连接同一个Redis其它的缓存数据中心亦可。PS这里并不需要把所有项目的数据都放在同一个Redis中Sa-Token提供了 **`[权限缓存与业务缓存分离]`** 的解决方案,详情戳:[Alone独立Redis插件](http://sa-token.dev33.cn/doc/index.html#/plugin/alone-redis)
3. 如果既无法做到前端同域也无法做到后端同Redis那么只能走模式三Http请求获取会话Sa-Token对SSO提供了完整的封装你只需要按照示例从文档上复制几段代码便可以轻松集成 3. 如果既无法做到前端同域也无法做到后端同Redis那么只能走模式三Http请求获取会话Sa-Token对SSO提供了完整的封装你只需要按照示例从文档上复制几段代码便可以轻松集成
4. 技术选型一定要根据系统架构对症下药,切不可胡乱选择 4. 技术选型一定要根据系统架构对症下药,切不可胡乱选择
### Sa-Token-SSO 特性 ### Sa-Token-SSO 特性

View File

@ -1,29 +1,29 @@
# SSO模式一 共享Cookie同步会话 # SSO模式一 共享Cookie同步会话
如果我们的多个系统可以做到前端同域、后端同Redis那么便可以使用 **`[共享Cookie同步会话]`** 的方式做到单点登录 如果我们的多个系统可以做到前端同域、后端同Redis那么便可以使用 **`[共享Cookie同步会话]`** 的方式做到单点登录
--- ---
### 0、解决思路 ### 0、解决思路
首先我们分析一下多个系统之间,为什么无法同步登录状态? 首先我们分析一下多个系统之间,为什么无法同步登录状态?
1. 前端的`Token`无法在多个系统下共享 1. 前端的`Token`无法在多个系统下共享
2. 后端的`Session`无法在多个系统间共享 2. 后端的`Session`无法在多个系统间共享
所以单点登录第一招,就是对症下药: 所以单点登录第一招,就是对症下药:
1. 使用`共享Cookie`来解决Token共享问题 1. 使用`共享Cookie`来解决Token共享问题
2. 使用`Redis`来解决Session共享问题 2. 使用`Redis`来解决Session共享问题
所谓共享Cookie就是主域名Cookie在二级域名下的共享举个例子写在父域名`stp.com`下的Cookie在`s1.stp.com`、`s2.stp.com`等子域名都是可以共享访问的 所谓共享Cookie就是主域名Cookie在二级域名下的共享举个例子写在父域名`stp.com`下的Cookie在`s1.stp.com`、`s2.stp.com`等子域名都是可以共享访问的
而共享Redis并不需要我们把所有项目的数据都放在同一个Redis中Sa-Token提供了 **[权限缓存与业务缓存分离]** 的解决方案,详情戳:[Alone独立Redis插件](/plugin/alone-redis) 而共享Redis并不需要我们把所有项目的数据都放在同一个Redis中Sa-Token提供了 **[权限缓存与业务缓存分离]** 的解决方案,详情戳:[Alone独立Redis插件](/plugin/alone-redis)
> PS这里建议不要用B项目去连接A项目的Redis也不要A项目连接B项目的Redis而是抽离出一个单独的 SSO-RedisA 和 B 一起连接这个 SSO-Redis <!-- > PS这里建议不要用B项目去连接A项目的Redis也不要A项目连接B项目的Redis而是抽离出一个单独的 SSO-RedisA 和 B 一起连接这个 SSO-Redis -->
OK所有理论就绪下面开始实战 OK所有理论就绪下面开始实战
> Sa-Token整合同域单点登录非常简单相比于正常的登录你只需增加配置 `sa-token.cookie-domain=xxx.com` 指定一下Cookie写入时的父级域名即可 <br> > Sa-Token整合同域单点登录非常简单相比于正常的登录你只需增加配置 `sa-token.cookie-domain=xxx.com` 指定一下Cookie写入时的父级域名即可 <br>
> 整合示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso1/`,如遇到难点可结合源码进行测试学习 > 整合示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso1/`,如遇到难点可结合源码进行测试学习
@ -31,13 +31,17 @@ OK所有理论就绪下面开始实战
首先修改hosts文件`(C:\windows\system32\drivers\etc\hosts)`添加以下IP映射方便我们进行测试 首先修改hosts文件`(C:\windows\system32\drivers\etc\hosts)`添加以下IP映射方便我们进行测试
``` url ``` url
127.0.0.1 sso.stp.com
127.0.0.1 s1.stp.com 127.0.0.1 s1.stp.com
127.0.0.1 s2.stp.com 127.0.0.1 s2.stp.com
127.0.0.1 s3.stp.com 127.0.0.1 s3.stp.com
``` ```
其中:`sso.stp.com`为统一认证地址,当用户在其它 Client 端发起登录请求时,均将其重定向至认证中心,待到登录成功之后再原路返回到 Client 端。
### 2、指定Cookie的作用域 ### 2、指定Cookie的作用域
在`s1.stp.com`访问服务器其Cookie也只能写入到`s1.stp.com`下为了将Cookie写入到其父级域名`stp.com`下,我们需要新增配置: 在`s1.stp.com`访问服务器其Cookie也只能写入到`s1.stp.com`下为了将Cookie写入到其父级域名`stp.com`下,我们需要新增配置
``` yml ``` yml
sa-token: sa-token:
# 写入Cookie时显式指定的作用域, 用于单点登录二级域名共享Cookie # 写入Cookie时显式指定的作用域, 用于单点登录二级域名共享Cookie
@ -57,18 +61,18 @@ public class SsoController {
// 测试:进行登录 // 测试:进行登录
@RequestMapping("doLogin") @RequestMapping("doLogin")
public AjaxJson doLogin(@RequestParam(defaultValue = "10001") String id) { public SaResult doLogin(@RequestParam(defaultValue = "10001") String id) {
System.out.println("---------------- 进行登录 "); System.out.println("---------------- 进行登录 ");
StpUtil.login(id); StpUtil.login(id);
return AjaxJson.getSuccess("登录成功: " + id); return SaResult.ok("登录成功: " + id);
} }
// 测试:是否登录 // 测试:是否登录
@RequestMapping("isLogin") @RequestMapping("isLogin")
public AjaxJson isLogin() { public SaResult isLogin() {
System.out.println("---------------- 是否登录 "); System.out.println("---------------- 是否登录 ");
boolean isLogin = StpUtil.isLogin(); boolean isLogin = StpUtil.isLogin();
return AjaxJson.getSuccess("是否登录: " + isLogin); return SaResult.ok("是否登录: " + isLogin);
} }
} }
@ -104,10 +108,48 @@ public class SaSsoApplication {
![sso-type1-yd.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type1-yd.png 's-w-sh') ![sso-type1-yd.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type1-yd.png 's-w-sh')
测试完毕 测试完毕
### 5、跨域模式下的解决方案 ### 5、搭建统一认证中心
上面的示例我们简单的演示了SSO模式一的认证原理。
当然在实际的正式项目中我们肯定不会每个系统都内置一个登录接口一般的做法是搭建一个独立的SSO认证中心我们所有 Client 端的登录请求都会被重定向至认证中心,
待到登录成功之后再原路返回到 Client 端。
我们可以运行一下官方仓库的示例,里面有制作好的登录页面
> 下载官方示例,依次运行:
> - `/sa-token-demo/sa-token-demo-sso1-server/`
> - `/sa-token-demo/sa-token-demo-sso1-client/`
访问三个应用端:
- [http://s1.stp.com:9001/](http://s1.stp.com:9001/)
- [http://s2.stp.com:9001/](http://s2.stp.com:9001/)
- [http://s3.stp.com:9001/](http://s3.stp.com:9001/)
均返回:
![sso1--index.png](https://oss.dev33.cn/sa-token/doc/sso/sso1--index.png 's-w-sh')
然后点击登录被重定向至SSO认证中心
![sso1--login-page.png](https://oss.dev33.cn/sa-token/doc/sso/sso1--login-page.png 's-w-sh')
输入默认测试账号:`sa / 123456`,点击登录
![sso1-login-ok.png](https://oss.dev33.cn/sa-token/doc/sso/sso1-login-ok.png 's-w-sh')
刷新另外两个Client端均显示已登录
![sso1-login-ok2.png](https://oss.dev33.cn/sa-token/doc/sso/sso1-login-ok2.png 's-w-sh')
测试完成
### 6、跨域模式下的解决方案
如上,我们使用极其简单的步骤实现了同域下的单点登录,聪明如你😏,马上想到了这种模式有着一个不小的限制: 如上,我们使用极其简单的步骤实现了同域下的单点登录,聪明如你😏,马上想到了这种模式有着一个不小的限制:

View File

@ -306,7 +306,12 @@ public class SaSsoClientApplication {
以上示例,虽然完整的复现了单点登录的过程,但是页面还是有些简陋,我们可以运行一下官方仓库的示例,里面有制作好的登录页面 以上示例,虽然完整的复现了单点登录的过程,但是页面还是有些简陋,我们可以运行一下官方仓库的示例,里面有制作好的登录页面
> 下载官方示例,依次运行 `/sa-token-demo/sa-token-demo-sso-client/``/sa-token-demo/sa-token-demo-sso-server/`,访问:[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/) > 下载官方示例,依次运行:
> - `/sa-token-demo/sa-token-demo-sso2-server/`
> - `/sa-token-demo/sa-token-demo-sso2-client/`
>
> 然后访问:
> - [http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/)
![sso-server-login-hua](https://oss.dev33.cn/sa-token/doc/sso/sso-server-login-hua.png 's-w-sh') ![sso-server-login-hua](https://oss.dev33.cn/sa-token/doc/sso/sso-server-login-hua.png 's-w-sh')

View File

@ -106,7 +106,9 @@ implementation 'cn.dev33:sa-token-core:${sa.top.version}'
├── sa-token-demo-solon // [示例] Sa-Token 集成 Solon ├── sa-token-demo-solon // [示例] Sa-Token 集成 Solon
├── sa-token-demo-quick-login // [示例] Sa-Token 集成 quick-login 模块 ├── sa-token-demo-quick-login // [示例] Sa-Token 集成 quick-login 模块
├── sa-token-demo-alone-redis // [示例] Sa-Token 集成 alone-redis 模块 ├── sa-token-demo-alone-redis // [示例] Sa-Token 集成 alone-redis 模块
├── sa-token-demo-sso1 // [示例] Sa-Token 集成 SSO单点登录-模式一 ├── sa-token-demo-sso1 // [示例] Sa-Token 集成 SSO单点登录-模式一简单测试
├── sa-token-demo-sso1-server // [示例] Sa-Token 集成 SSO单点登录-模式一 认证中心
├── sa-token-demo-sso1-client // [示例] Sa-Token 集成 SSO单点登录-模式一 应用端
├── sa-token-demo-sso2-server // [示例] Sa-Token 集成 SSO单点登录-模式二 认证中心 ├── sa-token-demo-sso2-server // [示例] Sa-Token 集成 SSO单点登录-模式二 认证中心
├── sa-token-demo-sso2-client // [示例] Sa-Token 集成 SSO单点登录-模式二 应用端 ├── sa-token-demo-sso2-client // [示例] Sa-Token 集成 SSO单点登录-模式二 应用端
├── sa-token-demo-sso3-server // [示例] Sa-Token 集成 SSO单点登录-模式三 认证中心 ├── sa-token-demo-sso3-server // [示例] Sa-Token 集成 SSO单点登录-模式三 认证中心