mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-10-07 23:24:24 +08:00
refactor: 略微优化 sso 相关 demo
This commit is contained in:
@@ -201,7 +201,7 @@ public interface SaRequest {
|
|||||||
* @return /
|
* @return /
|
||||||
*/
|
*/
|
||||||
default boolean isAjax() {
|
default boolean isAjax() {
|
||||||
return getHeader("X-Requested-With") != null;
|
return "XMLHttpRequest".equalsIgnoreCase(getHeader("X-Requested-With")) || isParam("_ajax", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -41,7 +41,9 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
|
|||||||
|
|
||||||
// 预定的状态码
|
// 预定的状态码
|
||||||
public static final int CODE_SUCCESS = 200;
|
public static final int CODE_SUCCESS = 200;
|
||||||
public static final int CODE_ERROR = 500;
|
public static final int CODE_ERROR = 500;
|
||||||
|
public static final int CODE_NOT_PERMISSION = 403;
|
||||||
|
public static final int CODE_NOT_LOGIN = 401;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建
|
* 构建
|
||||||
@@ -213,6 +215,18 @@ public class SaResult extends LinkedHashMap<String, Object> implements Serializa
|
|||||||
return new SaResult(CODE_ERROR, msg, null);
|
return new SaResult(CODE_ERROR, msg, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建未登录
|
||||||
|
public static SaResult notLogin() {
|
||||||
|
return new SaResult(CODE_NOT_LOGIN, "not login", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建无权限
|
||||||
|
public static SaResult notPermission() {
|
||||||
|
return new SaResult(CODE_NOT_PERMISSION, "not permission", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 构建指定状态码
|
// 构建指定状态码
|
||||||
public static SaResult get(int code, String msg, Object data) {
|
public static SaResult get(int code, String msg, Object data) {
|
||||||
return new SaResult(code, msg, data);
|
return new SaResult(code, msg, data);
|
||||||
|
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020-2099 sa-token.cc
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package cn.dev33.satoken.util;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.fun.SaFunction;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码语法糖封装
|
||||||
|
*
|
||||||
|
* @author click33
|
||||||
|
* @since 1.43.0
|
||||||
|
*/
|
||||||
|
public class SaSugar {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行一个 Lambda 表达式,返回这个 Lambda 表达式的结果值,
|
||||||
|
* <br> 方便组织代码,例如:
|
||||||
|
* <pre>
|
||||||
|
int value = Sugar.get(() -> {
|
||||||
|
int a = 1;
|
||||||
|
int b = 2;
|
||||||
|
return a + b;
|
||||||
|
});
|
||||||
|
</pre>
|
||||||
|
* @param <R> 返回值类型
|
||||||
|
* @param lambda lambda 表达式
|
||||||
|
* @return lambda 的执行结果
|
||||||
|
*/
|
||||||
|
public static <R> R get(Supplier<R> lambda) {
|
||||||
|
return lambda.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行一个 Lambda 表达式
|
||||||
|
* <br> 方便组织代码,例如:
|
||||||
|
* <pre>
|
||||||
|
Sugar.exe(() -> {
|
||||||
|
int a = 1;
|
||||||
|
int b = 2;
|
||||||
|
return a + b;
|
||||||
|
});
|
||||||
|
</pre>
|
||||||
|
* @param lambda lambda 表达式
|
||||||
|
*/
|
||||||
|
public static void exe(SaFunction lambda) {
|
||||||
|
lambda.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
// 服务器接口主机地址
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@@ -8,31 +8,18 @@
|
|||||||
<h2>Sa-Token SSO-Client 应用端(前后端分离版-原生h5)</h2>
|
<h2>Sa-Token SSO-Client 应用端(前后端分离版-原生h5)</h2>
|
||||||
<p>当前是否登录:<b class="is-login"></b></p>
|
<p>当前是否登录:<b class="is-login"></b></p>
|
||||||
<p>
|
<p>
|
||||||
<a href="javascript:location.href='sso-login.html?back=' + encodeURIComponent(location.href);">登录</a>
|
<a class="login-btn">登录</a>
|
||||||
<a href="javascript:location.href=baseUrl + '/sso/logout?satoken=' + localStorage.satoken + '&back=' + encodeURIComponent(location.href);">注销</a>
|
<a class="logout-btn">注销</a>
|
||||||
</p>
|
</p>
|
||||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
<script src="common.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
// 后端接口地址
|
document.querySelector('.login-btn').href = 'sso-login.html?back=' + encodeURIComponent(location.href);
|
||||||
var baseUrl = "http://sa-sso-client1.com:9002";
|
document.querySelector('.logout-btn').href = baseUrl + '/sso/logout?satoken=' + localStorage.satoken + '&back=' + encodeURIComponent(location.href);
|
||||||
|
|
||||||
// 查询当前会话是否登录
|
ajax('/sso/isLogin', {}, function(res){
|
||||||
$.ajax({
|
setHtml('.is-login', res.data);
|
||||||
url: baseUrl + '/sso/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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
@@ -11,83 +11,35 @@
|
|||||||
<div class="login-box">
|
<div class="login-box">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
<script src="common.js"></script>
|
||||||
<script>window.jQuery || alert('当前页面CDN服务商已宕机,请将所有js包更换为本地依赖')</script>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
// 后端接口地址
|
|
||||||
var baseUrl = "http://sa-sso-client1.com:9002";
|
|
||||||
|
|
||||||
var back = getParam('back', '/');
|
var back = getParam('back', '/');
|
||||||
var ticket = getParam('ticket');
|
var ticket = getParam('ticket');
|
||||||
$(function() {
|
|
||||||
|
window.onload = function(){
|
||||||
if(ticket) {
|
if(ticket) {
|
||||||
doLoginByTicket(ticket);
|
doLoginByTicket(ticket);
|
||||||
} else {
|
} else {
|
||||||
goSsoAuthUrl();
|
goSsoAuthUrl();
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
// 重定向至认证中心
|
// 重定向至认证中心
|
||||||
function goSsoAuthUrl() {
|
function goSsoAuthUrl() {
|
||||||
sa.ajax('/sso/getSsoAuthUrl', {clientLoginUrl: location.href}, function(res) {
|
ajax('/sso/getSsoAuthUrl', {clientLoginUrl: location.href}, function(res) {
|
||||||
console.log(res);
|
|
||||||
location.href = res.data;
|
location.href = res.data;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据ticket值登录
|
// 根据ticket值登录
|
||||||
function doLoginByTicket(ticket) {
|
function doLoginByTicket(ticket) {
|
||||||
sa.ajax('/sso/doLoginByTicket', {ticket: ticket}, function(res) {
|
ajax('/sso/doLoginByTicket', {ticket: ticket}, function(res) {
|
||||||
console.log(res);
|
localStorage.setItem('satoken', res.data);
|
||||||
if(res.code == 200) {
|
location.href = decodeURIComponent(back);
|
||||||
localStorage.setItem('satoken', res.data);
|
|
||||||
location.href = decodeURIComponent(back);
|
|
||||||
} else {
|
|
||||||
alert(res.msg);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -4,6 +4,7 @@ import cn.dev33.satoken.sso.model.SaCheckTicketResult;
|
|||||||
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
|
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
|
||||||
import cn.dev33.satoken.sso.template.SaSsoUtil;
|
import cn.dev33.satoken.sso.template.SaSsoUtil;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||||
import cn.dev33.satoken.util.SaResult;
|
import cn.dev33.satoken.util.SaResult;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -35,7 +36,10 @@ public class H5Controller {
|
|||||||
@RequestMapping("/sso/doLoginByTicket")
|
@RequestMapping("/sso/doLoginByTicket")
|
||||||
public SaResult doLoginByTicket(String ticket) {
|
public SaResult doLoginByTicket(String ticket) {
|
||||||
SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket, "/sso/doLoginByTicket");
|
SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket, "/sso/doLoginByTicket");
|
||||||
StpUtil.login(ctr.loginId, ctr.remainSessionTimeout);
|
StpUtil.login(ctr.loginId, new SaLoginParameter()
|
||||||
|
.setTimeout(ctr.remainTokenTimeout)
|
||||||
|
.setDeviceId(ctr.deviceId)
|
||||||
|
);
|
||||||
return SaResult.data(StpUtil.getTokenValue());
|
return SaResult.data(StpUtil.getTokenValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
package com.pj.h5;
|
package com.pj.h5;
|
||||||
|
|
||||||
import cn.dev33.satoken.context.SaHolder;
|
import cn.dev33.satoken.fun.strategy.SaCorsHandleFunction;
|
||||||
import cn.dev33.satoken.filter.SaServletFilter;
|
|
||||||
import cn.dev33.satoken.router.SaHttpMethod;
|
import cn.dev33.satoken.router.SaHttpMethod;
|
||||||
import cn.dev33.satoken.router.SaRouter;
|
import cn.dev33.satoken.router.SaRouter;
|
||||||
import cn.dev33.satoken.util.SaResult;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Sa-Token 权限认证] 配置类 (解决跨域问题)
|
* [Sa-Token 权限认证] 配置类 (解决跨域问题)
|
||||||
@@ -15,50 +12,29 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
* @author click33
|
* @author click33
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
public class SaTokenConfigure {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册 [Sa-Token 全局过滤器]
|
* CORS 跨域处理策略
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public SaServletFilter getSaServletFilter() {
|
public SaCorsHandleFunction corsHandle() {
|
||||||
return new SaServletFilter()
|
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", "*");
|
||||||
|
|
||||||
// 指定 [拦截路由] 与 [放行路由]
|
// 如果是预检请求,则立即返回到前端
|
||||||
.addInclude("/**").addExclude("/favicon.ico")
|
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||||
|
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
||||||
// 认证函数: 每次请求执行
|
.back();
|
||||||
.setAuth(obj -> {
|
};
|
||||||
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
|
|
||||||
// ...
|
|
||||||
})
|
|
||||||
|
|
||||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
|
||||||
.setError(e -> {
|
|
||||||
return SaResult.error(e.getMessage());
|
|
||||||
})
|
|
||||||
|
|
||||||
// 前置函数:在每次认证函数之前执行
|
|
||||||
.setBeforeAuth(obj -> {
|
|
||||||
SaHolder.getResponse()
|
|
||||||
|
|
||||||
// ---------- 设置跨域响应头 ----------
|
|
||||||
// 允许指定域访问跨域资源
|
|
||||||
.setHeader("Access-Control-Allow-Origin", "*")
|
|
||||||
// 允许所有请求方式
|
|
||||||
.setHeader("Access-Control-Allow-Methods", "*")
|
|
||||||
// 允许的header参数
|
|
||||||
.setHeader("Access-Control-Allow-Headers", "*")
|
|
||||||
// 有效时间
|
|
||||||
.setHeader("Access-Control-Max-Age", "3600")
|
|
||||||
;
|
|
||||||
|
|
||||||
// 如果是预检请求,则立即返回到前端
|
|
||||||
SaRouter.match(SaHttpMethod.OPTIONS)
|
|
||||||
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
|
||||||
.back();
|
|
||||||
})
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,53 @@
|
|||||||
|
package com.pj.h5;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.sso.model.SaCheckTicketResult;
|
||||||
|
import cn.dev33.satoken.sso.processor.SaSsoClientProcessor;
|
||||||
|
import cn.dev33.satoken.sso.template.SaSsoUtil;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||||
|
import cn.dev33.satoken.util.SaResult;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 前后台分离架构下集成SSO所需的代码 (SSO-Client端)
|
||||||
|
* <p>(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)</p>
|
||||||
|
* @author click33
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
public class H5Controller {
|
||||||
|
|
||||||
|
// 当前是否登录
|
||||||
|
@RequestMapping("/sso/isLogin")
|
||||||
|
public Object isLogin() {
|
||||||
|
return SaResult.data(StpUtil.isLogin());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回SSO认证中心登录地址
|
||||||
|
@RequestMapping("/sso/getSsoAuthUrl")
|
||||||
|
public SaResult getSsoAuthUrl(String clientLoginUrl) {
|
||||||
|
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
|
||||||
|
return SaResult.data(serverAuthUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ticket进行登录
|
||||||
|
@RequestMapping("/sso/doLoginByTicket")
|
||||||
|
public SaResult doLoginByTicket(String ticket) {
|
||||||
|
SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket, "/sso/doLoginByTicket");
|
||||||
|
StpUtil.login(ctr.loginId, new SaLoginParameter()
|
||||||
|
.setTimeout(ctr.remainTokenTimeout)
|
||||||
|
.setDeviceId(ctr.deviceId)
|
||||||
|
);
|
||||||
|
return SaResult.data(StpUtil.getTokenValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局异常拦截
|
||||||
|
@ExceptionHandler
|
||||||
|
public SaResult handlerException(Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return SaResult.error(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
package com.pj.h5;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.fun.strategy.SaCorsHandleFunction;
|
||||||
|
import cn.dev33.satoken.router.SaHttpMethod;
|
||||||
|
import cn.dev33.satoken.router.SaRouter;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Sa-Token 权限认证] 配置类 (解决跨域问题)
|
||||||
|
*
|
||||||
|
* @author click33
|
||||||
|
*/
|
||||||
|
@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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -47,6 +47,9 @@ public class ApiName {
|
|||||||
/** SSO-Client端:单点注销地址 */
|
/** SSO-Client端:单点注销地址 */
|
||||||
public String ssoLogout = "/sso/logout";
|
public String ssoLogout = "/sso/logout";
|
||||||
|
|
||||||
|
/** SSO-Client端:判断当前是否登录地址 */
|
||||||
|
public String ssoIsLogin = "/sso/isLogin";
|
||||||
|
|
||||||
/** SSO-Client端:单点注销的回调 */
|
/** SSO-Client端:单点注销的回调 */
|
||||||
public String ssoLogoutCall = "/sso/logoutCall";
|
public String ssoLogoutCall = "/sso/logoutCall";
|
||||||
|
|
||||||
@@ -67,6 +70,7 @@ public class ApiName {
|
|||||||
this.ssoSignout = prefix + this.ssoSignout;
|
this.ssoSignout = prefix + this.ssoSignout;
|
||||||
this.ssoLogin = prefix + this.ssoLogin;
|
this.ssoLogin = prefix + this.ssoLogin;
|
||||||
this.ssoLogout = prefix + this.ssoLogout;
|
this.ssoLogout = prefix + this.ssoLogout;
|
||||||
|
this.ssoIsLogin = prefix + this.ssoIsLogin;
|
||||||
this.ssoPushC = prefix + this.ssoPushC;
|
this.ssoPushC = prefix + this.ssoPushC;
|
||||||
this.ssoLogoutCall = prefix + this.ssoLogoutCall;
|
this.ssoLogoutCall = prefix + this.ssoLogoutCall;
|
||||||
return this;
|
return this;
|
||||||
@@ -87,6 +91,7 @@ public class ApiName {
|
|||||||
this.ssoSignout = this.ssoSignout.replaceFirst(oldPrefix, prefix);
|
this.ssoSignout = this.ssoSignout.replaceFirst(oldPrefix, prefix);
|
||||||
this.ssoLogin = this.ssoLogin.replaceFirst(oldPrefix, prefix);
|
this.ssoLogin = this.ssoLogin.replaceFirst(oldPrefix, prefix);
|
||||||
this.ssoLogout = this.ssoLogout.replaceFirst(oldPrefix, prefix);
|
this.ssoLogout = this.ssoLogout.replaceFirst(oldPrefix, prefix);
|
||||||
|
this.ssoIsLogin = this.ssoIsLogin.replaceFirst(oldPrefix, prefix);
|
||||||
this.ssoPushC = this.ssoPushC.replaceFirst(oldPrefix, prefix);
|
this.ssoPushC = this.ssoPushC.replaceFirst(oldPrefix, prefix);
|
||||||
this.ssoLogoutCall = this.ssoLogoutCall.replaceFirst(oldPrefix, prefix);
|
this.ssoLogoutCall = this.ssoLogoutCall.replaceFirst(oldPrefix, prefix);
|
||||||
return this;
|
return this;
|
||||||
|
@@ -41,6 +41,9 @@ public class ParamName {
|
|||||||
/** client参数名称 */
|
/** client参数名称 */
|
||||||
public String client = "client";
|
public String client = "client";
|
||||||
|
|
||||||
|
/** tokenName 参数 */
|
||||||
|
public String tokenName = "tokenName";
|
||||||
|
|
||||||
/** tokenValue 参数 */
|
/** tokenValue 参数 */
|
||||||
public String tokenValue = "tokenValue";
|
public String tokenValue = "tokenValue";
|
||||||
|
|
||||||
@@ -72,4 +75,10 @@ public class ParamName {
|
|||||||
/** singleDeviceIdLogout 参数 */
|
/** singleDeviceIdLogout 参数 */
|
||||||
public String singleDeviceIdLogout = "singleDeviceIdLogout";
|
public String singleDeviceIdLogout = "singleDeviceIdLogout";
|
||||||
|
|
||||||
|
public String isLogin = "isLogin";
|
||||||
|
public String authUrl = "authUrl";
|
||||||
|
public String redirectUrl = "redirectUrl";
|
||||||
|
public String currSsoLoginUrl = "currSsoLoginUrl";
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -111,16 +111,17 @@ public class SaSsoClientProcessor {
|
|||||||
String back = req.getParam(paramName.back, "/");
|
String back = req.getParam(paramName.back, "/");
|
||||||
String ticket = req.getParam(paramName.ticket);
|
String ticket = req.getParam(paramName.ticket);
|
||||||
|
|
||||||
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
|
|
||||||
if(stpLogic.isLogin()) {
|
|
||||||
return res.redirect(back);
|
|
||||||
}
|
|
||||||
/*
|
/*
|
||||||
* 此时有两种情况:
|
* 此时有两种情况:
|
||||||
* 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
* 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||||
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
* 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
||||||
*/
|
*/
|
||||||
if(ticket == null) {
|
if(ticket == null) {
|
||||||
|
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
|
||||||
|
if(stpLogic.isLogin()) {
|
||||||
|
return res.redirect(back);
|
||||||
|
}
|
||||||
|
|
||||||
// 获取当前项目的 sso 登录地址
|
// 获取当前项目的 sso 登录地址
|
||||||
// 全局配置了就是用全局的,否则使用当前请求的地址
|
// 全局配置了就是用全局的,否则使用当前请求的地址
|
||||||
String currSsoLoginUrl;
|
String currSsoLoginUrl;
|
||||||
|
@@ -31,6 +31,7 @@ import cn.dev33.satoken.stp.StpLogic;
|
|||||||
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
|
import cn.dev33.satoken.stp.parameter.SaLogoutParameter;
|
||||||
import cn.dev33.satoken.util.SaFoxUtil;
|
import cn.dev33.satoken.util.SaFoxUtil;
|
||||||
import cn.dev33.satoken.util.SaResult;
|
import cn.dev33.satoken.util.SaResult;
|
||||||
|
import cn.dev33.satoken.util.SaSugar;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -113,43 +114,34 @@ public class SaSsoServerProcessor {
|
|||||||
String redirect = req.getParam(paramName.redirect);
|
String redirect = req.getParam(paramName.redirect);
|
||||||
String client = req.getParam(paramName.client);
|
String client = req.getParam(paramName.client);
|
||||||
|
|
||||||
// 方式1:直接重定向回Client端 (mode=simple)
|
// 若 redirect 为空,则选择 homeRoute,若 homeRoute 也为空,则抛出异常
|
||||||
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
|
if(SaFoxUtil.isEmpty(redirect)) {
|
||||||
|
if(SaFoxUtil.isEmpty(cfg.getHomeRoute())) {
|
||||||
// 若 redirect 为空,则选择 homeRoute,若 homeRoute 也为空,则抛出异常
|
throw new SaSsoException("未指定 redirect 参数,也未配置 homeRoute 路由,无法完成重定向操作").setCode(SaSsoErrorCode.CODE_30014);
|
||||||
if(SaFoxUtil.isEmpty(redirect)) {
|
|
||||||
if(SaFoxUtil.isEmpty(cfg.getHomeRoute())) {
|
|
||||||
throw new SaSsoException("未指定 redirect 参数,也未配置 homeRoute 路由,无法完成重定向操作").setCode(SaSsoErrorCode.CODE_30014);
|
|
||||||
}
|
|
||||||
return res.redirect(cfg.getHomeRoute());
|
|
||||||
}
|
}
|
||||||
ssoServerTemplate.checkRedirectUrl(client, redirect);
|
return res.redirect(cfg.getHomeRoute());
|
||||||
return res.redirect(redirect);
|
|
||||||
} else {
|
|
||||||
// 方式2:带着 ticket 参数重定向回Client端 (mode=ticket)
|
|
||||||
|
|
||||||
// 校验提供的client是否为非法字符
|
|
||||||
// if(SaSsoConsts.CLIENT_WILDCARD.equals(client)) {
|
|
||||||
// throw new SaSsoException("无效 client 标识:" + client).setCode(SaSsoErrorCode.CODE_30013);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 若 redirect 为空,则选择 homeRoute,若 homeRoute 也为空,则抛出异常
|
|
||||||
if(SaFoxUtil.isEmpty(redirect)) {
|
|
||||||
if(SaFoxUtil.isEmpty(cfg.getHomeRoute())) {
|
|
||||||
throw new SaSsoException("未指定 redirect 参数,也未配置 homeRoute 路由,无法完成重定向操作").setCode(SaSsoErrorCode.CODE_30014);
|
|
||||||
}
|
|
||||||
return res.redirect(cfg.getHomeRoute());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建并跳转
|
|
||||||
String redirectUrl = ssoServerTemplate.buildRedirectUrl(client, redirect, stpLogic.getLoginId(), stpLogic.getTokenValue());
|
|
||||||
// 构建成功,说明 redirect 地址合法,此时需要更新一下该账号的Session有效期
|
|
||||||
if(cfg.getAutoRenewTimeout()) {
|
|
||||||
stpLogic.renewTimeout(stpLogic.getConfigOrGlobal().getTimeout());
|
|
||||||
}
|
|
||||||
// 跳转
|
|
||||||
return res.redirect(redirectUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String redirectUrl = SaSugar.get(() -> {
|
||||||
|
// 方式1:直接重定向回Client端 (mode=simple)
|
||||||
|
if(mode.equals(SaSsoConsts.MODE_SIMPLE)) {
|
||||||
|
ssoServerTemplate.checkRedirectUrl(client, redirect);
|
||||||
|
return redirect;
|
||||||
|
} else {
|
||||||
|
// 方式2:带着 ticket 参数重定向回Client端 (mode=ticket)
|
||||||
|
|
||||||
|
// 构建并跳转
|
||||||
|
String _redirectUrl = ssoServerTemplate.buildRedirectUrl(client, redirect, stpLogic.getLoginId(), stpLogic.getTokenValue());
|
||||||
|
// 构建成功,说明 redirect 地址合法,此时需要更新一下该账号的Session有效期
|
||||||
|
if(cfg.getAutoRenewTimeout()) {
|
||||||
|
stpLogic.renewTimeout(stpLogic.getConfigOrGlobal().getTimeout());
|
||||||
|
}
|
||||||
|
return _redirectUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 跳转
|
||||||
|
return res.redirect(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -99,7 +99,7 @@ public class SaSsoClientTemplate extends SaSsoTemplate {
|
|||||||
* 部分 Servlet 版本 request.getRequestURL() 返回的 url 带有 query 参数,形如:http://domain.com?id=1,
|
* 部分 Servlet 版本 request.getRequestURL() 返回的 url 带有 query 参数,形如:http://domain.com?id=1,
|
||||||
* 如果不加判断会造成最终生成的 serverAuthUrl 带有双 back 参数 ,这个 if 判断正是为了解决此问题
|
* 如果不加判断会造成最终生成的 serverAuthUrl 带有双 back 参数 ,这个 if 判断正是为了解决此问题
|
||||||
*/
|
*/
|
||||||
if( ! clientLoginUrl.contains(paramName.back + "=" + back) ) {
|
if( ! clientLoginUrl.contains(paramName.back + "=") ) {
|
||||||
clientLoginUrl = SaFoxUtil.joinParam(clientLoginUrl, paramName.back, back);
|
clientLoginUrl = SaFoxUtil.joinParam(clientLoginUrl, paramName.back, back);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user