前后端分离模式下接入SSO的示例

This commit is contained in:
click33
2021-08-16 19:20:44 +08:00
parent 1644a1c5f3
commit 798a5548f9
9 changed files with 356 additions and 4 deletions

View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sa-Token-SSO-Client端-测试页(前后端分离版)</title>
</head>
<body>
<h2>Sa-Token SSO-Client 应用端(前后端分离版)</h2>
<p>当前是否登录:<b class="is-login"></b></p>
<p>
<a href="javascript:location.href='sso-login.html?back=' + encodeURIComponent(location.href);">登录</a>
<a href="javascript:location.href=baseUrl + '/sso/logout?satoken=' + localStorage.satoken + '&back=' + encodeURIComponent(location.href);">注销</a>
</p>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// 后端接口地址
var baseUrl = "http://sa-sso-client1.com:9001";
// 查询当前会话是否登录
$.ajax({
url: baseUrl + '/isLogin',
type: "post",
dataType: 'json',
headers: {
"X-Requested-With": "XMLHttpRequest",
"satoken": localStorage.getItem("satoken")
},
success: function(res){
$('.is-login').html(res.data + '');
},
error: function(xhr, type, errorThrown){
return alert("异常:" + JSON.stringify(xhr));
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,91 @@
<!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="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script>window.jQuery || alert('当前页面CDN服务商已宕机请将所有js包更换为本地依赖')</script>
<script type="text/javascript">
// 后端接口地址
var baseUrl = "http://sa-sso-client1.com:9001";
var back = getParam('back', '/');
var ticket = getParam('ticket');
$(function() {
if(ticket) {
doLoginByTicket(ticket);
} else {
goSsoAuthUrl();
}
})
// 重定向至认证中心
function goSsoAuthUrl() {
sa.ajax('/getSsoAuthUrl', {clientLoginUrl: location.href}, function(res) {
console.log(res);
location.href = res.data;
})
}
// 根据ticket值登录
function doLoginByTicket(ticket) {
sa.ajax('/doLoginByTicket', {ticket: ticket}, function(res) {
console.log(res);
if(res.code == 200) {
localStorage.setItem('satoken', res.data);
location.href = decodeURIComponent(back);
}
})
}
// 从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);
}
</script>
<script type="text/javascript">
var sa = {};
// 封装一下Ajax
sa.ajax = function(url, data, successFn) {
// sa.loading("正在努力加载...");
$.ajax({
url: baseUrl + url,
type: "post",
data: data,
dataType: 'json',
headers: {
satoken: localStorage.getItem('satoken')
},
success: function(res){
console.log('返回数据:', res);
successFn(res);
},
error: function(xhr, type, errorThrown){
if(xhr.status == 0){
return alert('无法连接到服务器,请检查网络');
}
return alert("异常:" + JSON.stringify(xhr));
}
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,61 @@
package com.pj.h5;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 跨域过滤器
* @author kong
*/
@Component
@Order(-200)
public class CorsFilter implements Filter {
static final String OPTIONS = "OPTIONS";
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 允许指定域访问跨域资源
response.setHeader("Access-Control-Allow-Origin", "*");
// 允许所有请求方式
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
// 有效时间
response.setHeader("Access-Control-Max-Age", "3600");
// 允许的header参数
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,satoken");
// 如果是预检请求,直接返回
if (OPTIONS.equals(request.getMethod())) {
System.out.println("=======================浏览器发来了OPTIONS预检请求==========");
response.getWriter().print("");
return;
}
// System.out.println("*********************************过滤器被使用**************************");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}

View File

@@ -0,0 +1,50 @@
package com.pj.h5;
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.sso.SaSsoUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* 前后台分离架构下集成SSO所需的代码
* @author kong
*
*/
@RestController
public class H5Controller {
// 当前是否登录
@RequestMapping("/isLogin")
public Object isLogin() {
return SaResult.data(StpUtil.isLogin());
}
// 返回SSO认证中心登录地址
@RequestMapping("/getSsoAuthUrl")
public SaResult getSsoAuthUrl(String clientLoginUrl) {
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
return SaResult.data(serverAuthUrl);
}
// 根据ticket进行登录
@RequestMapping("/doLoginByTicket")
public SaResult doLoginByTicket(String ticket) {
Object loginId = SaSsoUtil.checkTicket(ticket);
if(loginId != null) {
StpUtil.login(loginId);
return SaResult.data(StpUtil.getTokenValue());
}
return SaResult.error("无效ticket" + ticket);
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@@ -9,7 +9,8 @@ sa-token:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
# 所有允许的授权回调地址
allow-url: http://sa-sso-client1.com:9001/sso/login, http://sa-sso-client2.com:9001/sso/login, http://sa-sso-client3.com:9001/sso/login
# allow-url: http://sa-sso-client1.com:9001/sso/login, http://sa-sso-client2.com:9001/sso/login, http://sa-sso-client3.com:9001/sso/login
allow-url: "*"
spring:
# Redis配置

View File

@@ -118,7 +118,112 @@ public SaResult ss(String name, String pwd) {
答:直接在前端更改点击按钮时 Ajax 的请求地址即可
### 三、常见疑问
### 三、前后端分离架构下的整合方案
如果我们已有的系统是前后端分离模式我们显然不能为了接入SSO而改造系统的基础架构官方仓库的示例采用的是前后端一体方案要将其改造为前后台分离架构模式非常简单
以`sa-token-demo-sso2-client`为例:
##### 3.1、新建`H5Controller`开放接口
``` java
/**
* 前后台分离架构下集成SSO所需的代码
*/
@RestController
public class H5Controller {
// 当前是否登录
@RequestMapping("/isLogin")
public Object isLogin() {
return SaResult.data(StpUtil.isLogin());
}
// 返回SSO认证中心登录地址
@RequestMapping("/getSsoAuthUrl")
public SaResult getSsoAuthUrl(String clientLoginUrl) {
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
return SaResult.data(serverAuthUrl);
}
// 根据ticket进行登录
@RequestMapping("/doLoginByTicket")
public SaResult doLoginByTicket(String ticket) {
Object loginId = SaSsoUtil.checkTicket(ticket);
if(loginId != null) {
StpUtil.login(loginId);
return SaResult.data(StpUtil.getTokenValue());
}
return SaResult.error("无效ticket" + ticket);
}
// 全局异常拦截
@ExceptionHandler
public SaResult handlerException(Exception e) {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}
```
##### 3.2、增加跨域过滤器`CorsFilter.java`
源码详见:[CorsFilter.java](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso2-client/src/main/java/com/pj/h5/CorsFilter.java)
将其复制到项目中即可
##### 3.3、新建前端项目
任意文件夹新建前端项目:`sa-token-demo-sso2-client-h5`,在根目录添加测试文件:`index.html`
``` xml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sa-Token-SSO-Client端-测试页(前后端分离版)</title>
</head>
<body>
<h2>Sa-Token SSO-Client 应用端(前后端分离版)</h2>
<p>当前是否登录:<b class="is-login"></b></p>
<p>
<a href="javascript:location.href='sso-login.html?back=' + encodeURIComponent(location.href);">登录</a>
<a href="javascript:location.href=baseUrl + '/sso/logout?satoken=' + localStorage.satoken + '&back=' + encodeURIComponent(location.href);">注销</a>
</p>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// 后端接口地址
var baseUrl = "http://sa-sso-client1.com:9001";
// 查询当前会话是否登录
$.ajax({
url: baseUrl + '/isLogin',
type: "post",
dataType: 'json',
headers: {
"X-Requested-With": "XMLHttpRequest",
"satoken": localStorage.getItem("satoken")
},
success: function(res){
$('.is-login').html(res.data + '');
},
error: function(xhr, type, errorThrown){
return alert("异常:" + JSON.stringify(xhr));
}
});
</script>
</body>
</html>
```
##### 3.4、添加登录处理文件`sso-login.html`
源码详见:[sso-login.html](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso2-client-h5/sso-login.html)
将其复制到项目中即可,与`index.html`一样放在根目录下
##### 3.5、测试
先启动Server服务端与Client服务端再随便找个能预览html的工具打开前端项目比如[HBuilderX](https://www.dcloud.io/hbuilderx.html)),测试流程与一体版一致
### 四、常见疑问
##### 问在模式一与模式二中Client端 必须通过 Alone-Redis 插件来访问Redis吗

View File

@@ -116,7 +116,7 @@ public Object myinfo() {
}
```
访问测试:[http://sa-sso-client2.com:9001/sso/myinfo](http://sa-sso-client2.com:9001/sso/myinfo)
访问测试:[http://sa-sso-client1.com:9001/sso/myinfo](http://sa-sso-client1.com:9001/sso/myinfo)

View File

@@ -19,7 +19,7 @@
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon</artifactId>
<version>1.5.17</version>
<version>1.5.24</version>
</dependency>
<dependency>

View File

@@ -51,4 +51,9 @@ public class SaRequestForSolon implements SaRequest {
return ctx.method();
}
public Object forward(String path) {
ctx.forward(path);
return null;
}
}