2021-08-24 02:05:16 +08:00
|
|
|
|
# SSO整合-前后端分离架构下的整合方案
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
## SSO-Client 前后端分离
|
2021-08-24 02:05:16 +08:00
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
要在前后端分离的环境中接入 SSO,思路不难,主要的工作是吧后端 `/sso/login` 接口的路由中转工作拿到前端来,以`sa-token-demo-sso3-client`为例:
|
|
|
|
|
|
|
|
|
|
### 1、在 sso-client 后端新建`H5Controller`,开放接口:
|
2021-08-24 02:05:16 +08:00
|
|
|
|
|
|
|
|
|
``` java
|
|
|
|
|
/**
|
2025-05-08 20:52:52 +08:00
|
|
|
|
* 前后台分离架构下集成 SSO 所需的代码 (SSO-Client端)
|
2021-08-24 02:05:16 +08:00
|
|
|
|
*/
|
|
|
|
|
@RestController
|
|
|
|
|
public class H5Controller {
|
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
// 判断当前是否登录
|
2022-10-27 19:44:49 +08:00
|
|
|
|
@RequestMapping("/sso/isLogin")
|
2021-08-24 02:05:16 +08:00
|
|
|
|
public Object isLogin() {
|
2025-05-08 20:52:52 +08:00
|
|
|
|
return SaResult.data(StpUtil.isLogin()).set("loginId", StpUtil.getLoginIdDefaultNull());
|
2021-08-24 02:05:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 返回SSO认证中心登录地址
|
2022-08-20 12:05:36 +08:00
|
|
|
|
@RequestMapping("/sso/getSsoAuthUrl")
|
2021-08-24 02:05:16 +08:00
|
|
|
|
public SaResult getSsoAuthUrl(String clientLoginUrl) {
|
2025-05-08 20:52:52 +08:00
|
|
|
|
String serverAuthUrl = SaSsoClientUtil.buildServerAuthUrl(clientLoginUrl, "");
|
2021-08-24 02:05:16 +08:00
|
|
|
|
return SaResult.data(serverAuthUrl);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
// 根据 ticket 进行登录
|
2022-08-20 12:05:36 +08:00
|
|
|
|
@RequestMapping("/sso/doLoginByTicket")
|
2021-08-24 02:05:16 +08:00
|
|
|
|
public SaResult doLoginByTicket(String ticket) {
|
2025-05-08 20:52:52 +08:00
|
|
|
|
SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket);
|
|
|
|
|
StpUtil.login(ctr.loginId, new SaLoginParameter()
|
|
|
|
|
.setTimeout(ctr.remainTokenTimeout)
|
|
|
|
|
.setDeviceId(ctr.deviceId)
|
|
|
|
|
);
|
2024-05-17 11:41:42 +08:00
|
|
|
|
return SaResult.data(StpUtil.getTokenValue());
|
2021-08-24 02:05:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
|
|
|
|
|
### 2、增加跨域处理策略
|
|
|
|
|
|
|
|
|
|
``` java
|
|
|
|
|
/**
|
|
|
|
|
* [Sa-Token 权限认证] 配置类
|
|
|
|
|
*/
|
|
|
|
|
@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();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
详细参考:[解决跨域问题](/fun/cors-filter)
|
|
|
|
|
|
2021-08-24 02:05:16 +08:00
|
|
|
|
|
|
|
|
|
### 3、新建前端项目
|
2021-10-09 05:43:31 +08:00
|
|
|
|
任意文件夹新建前端项目:`sa-token-demo-sso-client-h5`,在根目录添加测试文件:`index.html`
|
2025-05-08 20:52:52 +08:00
|
|
|
|
``` html
|
2021-08-24 02:05:16 +08:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8">
|
2025-05-08 20:52:52 +08:00
|
|
|
|
<title>Sa-Token-SSO-Client端-测试页(前后端分离版-原生h5)</title>
|
2021-08-24 02:05:16 +08:00
|
|
|
|
</head>
|
|
|
|
|
<body>
|
2025-05-08 20:52:52 +08:00
|
|
|
|
<h2>Sa-Token SSO-Client 应用端(前后端分离版-原生h5)</h2>
|
2021-08-24 02:05:16 +08:00
|
|
|
|
<p>当前是否登录:<b class="is-login"></b></p>
|
|
|
|
|
<p>
|
2025-05-08 20:52:52 +08:00
|
|
|
|
<a href="javascript: login();">登录</a> -
|
|
|
|
|
<a href="javascript: doLogoutByAlone();">单应用注销</a> -
|
|
|
|
|
<a href="javascript: doLogoutBySingleDeviceId();">单浏览器注销</a> -
|
|
|
|
|
<a href="javascript: doLogout();">全端注销</a> -
|
|
|
|
|
<a href="javascript: doMyInfo();">账号资料</a>
|
2021-08-24 02:05:16 +08:00
|
|
|
|
</p>
|
2025-05-08 20:52:52 +08:00
|
|
|
|
<script src="sso-common.js"></script>
|
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
|
|
|
|
|
// 登录
|
|
|
|
|
function login() {
|
|
|
|
|
location.href = 'sso-login.html?back=' + encodeURIComponent(location.href);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 单应用注销
|
|
|
|
|
function doLogoutByAlone() {
|
|
|
|
|
ajax('/sso/logoutByAlone', {}, function(res){
|
|
|
|
|
doIsLogin();
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 单浏览器注销
|
|
|
|
|
function doLogoutBySingleDeviceId() {
|
|
|
|
|
ajax('/sso/logout', { singleDeviceIdLogout: true }, function(res){
|
|
|
|
|
doIsLogin();
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 全端注销
|
|
|
|
|
function doLogout() {
|
|
|
|
|
ajax('/sso/logout', { }, function(res){
|
|
|
|
|
doIsLogin();
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 账号资料
|
|
|
|
|
function doMyInfo() {
|
|
|
|
|
ajax('/sso/myInfo', { }, function(res){
|
|
|
|
|
alert(JSON.stringify(res));
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 判断是否登录
|
|
|
|
|
function doIsLogin() {
|
|
|
|
|
ajax('/sso/isLogin', {}, function(res){
|
|
|
|
|
if(res.data) {
|
|
|
|
|
setHtml('.is-login', res.data + ' (' + res.loginId + ')');
|
|
|
|
|
} else {
|
|
|
|
|
setHtml('.is-login', res.data);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
doIsLogin();
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 4、添加单点登录登录中转页
|
|
|
|
|
|
|
|
|
|
在根目录创建文件:`sso-login.html`
|
|
|
|
|
|
|
|
|
|
``` html
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<title>Sa-Token-SSO-Client端-登录中转页页</title>
|
|
|
|
|
<style type="text/css">
|
|
|
|
|
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<div class="login-box">
|
|
|
|
|
加载中 ...
|
|
|
|
|
</div>
|
|
|
|
|
<script src="sso-common.js"></script>
|
2021-08-24 02:05:16 +08:00
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
var back = getParam('back', '/');
|
|
|
|
|
var ticket = getParam('ticket');
|
|
|
|
|
|
|
|
|
|
window.onload = function(){
|
|
|
|
|
if(ticket) {
|
|
|
|
|
doLoginByTicket(ticket);
|
|
|
|
|
} else {
|
|
|
|
|
goSsoAuthUrl();
|
2021-08-24 02:05:16 +08:00
|
|
|
|
}
|
2025-05-08 20:52:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 重定向至认证中心
|
|
|
|
|
function goSsoAuthUrl() {
|
|
|
|
|
ajax('/sso/getSsoAuthUrl', {clientLoginUrl: location.href}, function(res) {
|
|
|
|
|
location.href = res.data;
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 根据ticket值登录
|
|
|
|
|
function doLoginByTicket(ticket) {
|
|
|
|
|
ajax('/sso/doLoginByTicket', {ticket: ticket}, function(res) {
|
|
|
|
|
localStorage.setItem('satoken', res.data);
|
|
|
|
|
location.href = decodeURIComponent(back);
|
|
|
|
|
})
|
|
|
|
|
}
|
2021-08-24 02:05:16 +08:00
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
```
|
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
|
|
|
|
|
### 5、添加公共 js文件
|
|
|
|
|
新建 `sso-common.js`:
|
|
|
|
|
|
|
|
|
|
``` js
|
|
|
|
|
// 服务器接口主机地址
|
|
|
|
|
// var baseUrl = "http://sa-sso-client1.com:9002"; // 模式二后端
|
|
|
|
|
var baseUrl = "http://sa-sso-client1.com:9003"; // 模式三后端
|
|
|
|
|
|
|
|
|
|
// 封装一下Ajax
|
|
|
|
|
function ajax(path, data, successFn, errorFn) {
|
|
|
|
|
console.log('发起请求:', baseUrl + path, JSON.stringify(data));
|
|
|
|
|
fetch(baseUrl + path, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
|
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
|
|
|
'satoken': localStorage.getItem('satoken')
|
|
|
|
|
},
|
|
|
|
|
body: serializeToQueryString(data),
|
|
|
|
|
})
|
|
|
|
|
.then(response => response.json())
|
|
|
|
|
.then(res => {
|
|
|
|
|
console.log('返回数据:', res);
|
|
|
|
|
if(res.code === 500) {
|
|
|
|
|
return alert(res.msg);
|
|
|
|
|
}
|
|
|
|
|
successFn(res);
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('请求失败:', error);
|
|
|
|
|
return alert("异常:" + JSON.stringify(error));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ------------ 工具方法 ---------------
|
|
|
|
|
|
|
|
|
|
// 从url中查询到指定名称的参数值
|
|
|
|
|
function getParam(name, defaultValue) {
|
|
|
|
|
var query = window.location.search.substring(1);
|
|
|
|
|
var vars = query.split("&");
|
|
|
|
|
for (var i = 0; i < vars.length; i++) {
|
|
|
|
|
var pair = vars[i].split("=");
|
|
|
|
|
if (pair[0] == name) {
|
|
|
|
|
return pair[1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return (defaultValue == undefined ? null : defaultValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将 json 对象序列化为kv字符串,形如:name=Joh&age=30&active=true
|
|
|
|
|
function serializeToQueryString(obj) {
|
|
|
|
|
return Object.entries(obj)
|
|
|
|
|
.filter(([_, value]) => value != null) // 过滤 null 和 undefined
|
|
|
|
|
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
|
|
|
.join('&');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 向指定标签里 set 内容
|
|
|
|
|
function setHtml(select, html) {
|
|
|
|
|
const dom = document.querySelector('.is-login');
|
|
|
|
|
if(dom) {
|
|
|
|
|
dom.innerHTML = html;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 6、测试运行
|
|
|
|
|
先启动 Server 服务端与 Client 服务端,再随便找个能预览html的工具打开前端项目(比如[HBuilderX](https://www.dcloud.io/hbuilderx.html)),测试流程与一体版一致,暂不赘述。
|
|
|
|
|
|
|
|
|
|
|
2021-08-24 02:05:16 +08:00
|
|
|
|
|
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
> [!TIP| label:另附其它技术栈的前后端分离 demo 示例:]
|
|
|
|
|
> - sso-client 前后端分离 - 原生h5:[源码链接](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-h5)
|
|
|
|
|
> - sso-client 前后端分离 - vue2:[源码链接](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-vue2)
|
|
|
|
|
> - sso-client 前后端分离 - vue3:[源码链接](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-vue3)
|
2021-08-24 02:05:16 +08:00
|
|
|
|
|
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
## SSO-Server 前后端分离
|
2021-08-24 02:05:16 +08:00
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
解决思路与 SSO-Client 一样,我们需要把原本在 “后端处理的授权重定向逻辑” 拿到前端来实现。
|
2021-09-01 03:43:58 +08:00
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
由于集成代码与 Client 端类似,这里暂不贴详细代码,我们可以下载官方仓库,里面有搭建好的demo。
|
2021-09-01 03:43:58 +08:00
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
使用前端 ide 导入项目 `/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server-h5`,浏览器访问 `sso-auth.html` 页面:
|
2021-09-01 03:43:58 +08:00
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
复制上述地址,将其配置到 Client 端的配置项 `sa-token.sso-client.auth-url` ,例如:
|
2021-09-01 03:43:58 +08:00
|
|
|
|
|
2022-10-20 13:06:36 +08:00
|
|
|
|
<!---------------------------- tabs:start ---------------------------->
|
|
|
|
|
<!------------- tab:yaml 风格 ------------->
|
|
|
|
|
``` yaml
|
|
|
|
|
sa-token:
|
2024-05-01 11:33:17 +08:00
|
|
|
|
sso-client:
|
2025-05-08 20:52:52 +08:00
|
|
|
|
# sso-server 端主机地址
|
|
|
|
|
server-url: http://sa-sso-server.com:9000
|
|
|
|
|
# 在 sso-server 端前后端分离时需要单独配置 auth-url 参数(上面的不要注释,auth-url 配置项和 server-url 要同时存在)
|
2021-10-09 05:43:31 +08:00
|
|
|
|
auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
|
2021-09-01 03:43:58 +08:00
|
|
|
|
```
|
2022-10-20 13:06:36 +08:00
|
|
|
|
<!------------- tab:properties 风格 ------------->
|
|
|
|
|
``` properties
|
|
|
|
|
# SSO-Server端 统一认证地址
|
2025-05-08 20:52:52 +08:00
|
|
|
|
sa-token.sso-client.server-url=http://sa-sso-server.com:9000
|
|
|
|
|
# 在 sso-server 端前后端分离时需要单独配置 auth-url 参数(上面的不要注释,auth-url 配置项和 server-url 要同时存在)
|
2024-05-01 11:33:17 +08:00
|
|
|
|
sa-token.sso-client.auth-url=http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
|
2022-10-20 13:06:36 +08:00
|
|
|
|
```
|
|
|
|
|
<!---------------------------- tabs:end ---------------------------->
|
|
|
|
|
|
2021-09-01 03:43:58 +08:00
|
|
|
|
|
2025-05-08 20:52:52 +08:00
|
|
|
|
然后我们启动项目 sso-server 与 sso-client ,按照之前的测试步骤访问:
|
|
|
|
|
[http://sa-sso-client1.com:9003/](http://sa-sso-client1.com:9003/),即可以前后端分离模式完成 SSO-Server 端的授权登录。
|
2021-08-24 02:05:16 +08:00
|
|
|
|
|