重构SSO模块

This commit is contained in:
click33 2021-08-16 02:12:02 +08:00
parent ef1bdbd867
commit 35814bbb66
19 changed files with 143 additions and 46 deletions

View File

@ -45,7 +45,16 @@ public interface SaRequest {
*/
public default boolean isParam(String name, String value) {
String paramValue = getParam(name);
return paramValue != null && paramValue.equals(value);
return SaFoxUtil.isNotEmpty(paramValue) && paramValue.equals(value);
}
/**
* 检测请求是否提供了指定参数
* @param name 参数名称
* @return 是否提供
*/
public default boolean hasParam(String name) {
return SaFoxUtil.isNotEmpty(getParam(name));
}
/**
@ -106,7 +115,7 @@ public interface SaRequest {
}
/**
* 返回当前请求的urlhttp://xxx.com/
* 返回当前请求的url不带query参数http://xxx.com/
* @return see note
*/
public String getUrl();
@ -124,5 +133,14 @@ public interface SaRequest {
public default boolean isAjax() {
return getHeader("X-Requested-With") != null;
}
/**
* 转发请求
* @param url 转发地址
* @return 任意值
*/
public default Object forward(String path) {
throw new SaTokenException("No implementation");
}
}

View File

@ -68,7 +68,10 @@ public class SaSsoConsts {
/** 表示OK的返回结果 */
public static final String OK = "ok";
/** 表示自己 */
public static final String SELF = "self";
/** 表示请求没有得到任何有效处理 {msg: "not handle"} */
public static final String NOT_HANDLE = "{\"msg\": \"not handle\"}";
}

View File

@ -247,11 +247,9 @@ public class SaSsoHandle {
// 开始处理
stpLogic.logout();
if(req.getParam(ParamName.back) == null) {
return SaResult.ok("单点注销成功");
} else {
return res.redirect(req.getParam(ParamName.back, "/"));
}
// 返回
return ssoLogoutBack(req, res);
}
/**
@ -273,14 +271,12 @@ public class SaSsoHandle {
// 调用SSO-Server认证中心API进行注销
String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId());
String body = String.valueOf(cfg.sendHttp.apply(url));
if(SaSsoConsts.OK.equals(body)) {
if(req.getParam(ParamName.back) == null) {
return SaResult.ok("单点注销成功");
} else {
return res.redirect(req.getParam(ParamName.back, "/"));
}
if(SaSsoConsts.OK.equals(body) == false) {
return SaResult.error("单点注销失败");
}
return SaResult.error("单点注销失败");
// 返回
return ssoLogoutBack(req, res);
}
/**
@ -301,4 +297,28 @@ public class SaSsoHandle {
return SaSsoConsts.OK;
}
/**
* 封装单点注销成功后返回结果
* @param req SaRequest对象
* @param res SaResponse对象
* @return 返回结果
*/
public static Object ssoLogoutBack(SaRequest req, SaResponse res) {
/*
* 三种情况
* 1. 有back参数值为SELF -> 回退一级并刷新
* 2. 有back参数值为url -> 跳转back地址
* 3. 无back参数 -> 返回json数据
*/
String back = req.getParam(ParamName.back);
if(SaFoxUtil.isNotEmpty(back)) {
if(back.equals(SaSsoConsts.SELF)) {
return "<script>if(document.referrer != location.href){ location.replace(document.referrer || '/'); }</script>";
}
return res.redirect(back);
} else {
return SaResult.ok("单点注销成功");
}
}
}

View File

@ -212,8 +212,7 @@ public class SaSsoTemplate {
}
// 3是否在[允许地址列表]之中
String authUrl = SaManager.getConfig().getSso().getAllowUrl().replaceAll(" ", "");
List<String> authUrlList = Arrays.asList(authUrl.split(","));
List<String> authUrlList = Arrays.asList(getAllowUrl().replaceAll(" ", "").split(","));
if(SaManager.getSaTokenAction().hasElement(authUrlList, url) == false) {
throw new SaTokenException("非法redirect" + url);
}
@ -222,6 +221,15 @@ public class SaSsoTemplate {
return;
}
/**
* 获取所有允许的授权回调地址多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
* @return see note
*/
public String getAllowUrl() {
// 默认从配置文件中返回
return SaManager.getConfig().getSso().getAllowUrl();
}
/**
* 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug
* @param url url

View File

@ -99,10 +99,18 @@ public class SaSsoUtil {
* 校验重定向url合法性
* @param url 下放ticket的url地址
*/
public static void checkAuthUrl(String url) {
public static void checkRedirectUrl(String url) {
saSsoTemplate.checkRedirectUrl(url);
}
/**
* 获取所有允许的授权回调地址多个用逗号隔开 (不在此列表中的URL将禁止下放ticket)
* @return see note
*/
public static String getAllowUrl() {
return saSsoTemplate.getAllowUrl();
}
/**
* 构建URLServer端 账号资料查询地址
* @param loginId 账号id

View File

@ -64,6 +64,7 @@ public class StpLogic {
*/
public StpLogic setLoginType(String loginType){
this.loginType = loginType;
SaManager.putStpLogic(this);
return this;
}

View File

@ -61,6 +61,15 @@ public class SaFoxUtil {
public static boolean isEmpty(Object str) {
return str == null || "".equals(str);
}
/**
* 指定元素是否不为 (null或者空字符串)
* @param str 指定元素
* @return 是否为null或者空字符串
*/
public static boolean isNotEmpty(Object str) {
return isEmpty(str) == false;
}
/**
* 以当前时间戳和随机int数字拼接一个随机字符串

View File

@ -21,8 +21,7 @@ public class SsoClientController {
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a> " +
"<a href=\"javascript:location.href='/sso/logout?back=' + encodeURIComponent(location.href);\">注销</a></p>";
// "<a href='/sso/logout' target='_blank'>注销</a></p>"; // 上面是[跳页面]方式这个是[RestAPI]方式 区别在于是否加了back参数
"<a href='/sso/logout?back=self'>注销</a></p>";
return str;
}

View File

@ -26,7 +26,7 @@ public class SsoClientController {
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
" <a href='/sso/logout' target='_blank'>注销</a></p>";
" <a href='/sso/logout?back=self'>注销</a></p>";
return str;
}

View File

@ -4,12 +4,6 @@ server:
# sa-token配置
sa-token:
# Token名称
token-name: satoken
# Token有效期
timeout: 2592000
# Token风格
token-style: uuid
# SSO-相关配置
sso:
# SSO-Server端 单点登录地址

View File

@ -14,7 +14,7 @@
```
##### 1.2、后端拦截重定向
在后端注册全局过滤器(或拦截器),拦截需要登录后才能访问的页面资源,将未登录的访问重定向至登录接口
在后端注册全局过滤器(或拦截器、或全局异常处理),拦截需要登录后才能访问的页面资源,将未登录的访问重定向至登录接口
``` java
/**
* Sa-Token 配置类

View File

@ -195,7 +195,7 @@ public class SsoClientController {
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a> " +
"<a href='/sso/logout' target='_blank'>注销</a></p>";
"<a href='/sso/logout?back=self'>注销</a></p>";
return str;
}

View File

@ -182,7 +182,7 @@ sa-token:
点击 **`[注销]`** 按钮,即可单点注销成功
![sso-type3-slo.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-slo.png 's-w-sh')
<!-- ![sso-type3-slo.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-slo.png 's-w-sh') -->
![sso-type3-slo-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-slo-index.png 's-w-sh')

View File

@ -1,8 +1,5 @@
package cn.dev33.satoken.quick;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
@ -11,11 +8,11 @@ import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.quick.config.SaQuickConfig;
import cn.dev33.satoken.quick.web.SaQuickController;
import cn.dev33.satoken.spring.SpringMVCUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
@ -65,26 +62,16 @@ public class SaQuickBean implements WebMvcConfigurer {
.addExclude("/favicon.ico", "/saLogin", "/doLogin", "/sa-res/**").
// 认证函数: 每次请求执行
setAuth(r -> {
// System.out.println("---------- 进入sa-token全局认证 -----------");
// 未登录时直接转发到login.html页面
if (SaQuickManager.getConfig().getAuth() && StpUtil.isLogin() == false) {
try {
HttpServletRequest request = SpringMVCUtil.getRequest();
HttpServletResponse response = SpringMVCUtil.getResponse();
request.getRequestDispatcher("/saLogin").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
SaHolder.getRequest().forward("/saLogin");
// 抛出异常不再继续执行
throw NotLoginException.newInstance(StpUtil.getLoginType(), "");
}
}).
// 异常处理函数每次认证函数发生异常时执行此函数
setError(e -> {
// System.out.println("---------- 进入sa-token异常处理 -----------");
return e.getMessage();
});
}

View File

@ -16,6 +16,11 @@ public class SaReactorHolder {
*/
public static final Class<ServerWebExchange> CONTEXT_KEY = ServerWebExchange.class;
/**
* chain_key
*/
public static final String CHAIN_KEY = "WEB_FILTER_CHAIN_KEY";
/**
* 获取上下文对象
* @return see note

View File

@ -151,6 +151,10 @@ public class SaReactorFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// 写入WebFilterChain对象
exchange.getAttributes().put(SaReactorHolder.CHAIN_KEY, chain);
// ---------- 全局认证处理
try {
// 写入全局上下文 (同步)

View File

@ -3,8 +3,12 @@ package cn.dev33.satoken.reactor.model;
import org.springframework.http.HttpCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
/**
* Request for Reactor
@ -85,4 +89,19 @@ public class SaRequestForReactor implements SaRequest {
public String getMethod() {
return request.getMethodValue();
}
/**
* 转发请求
*/
@Override
public Object forward(String path) {
ServerWebExchange exchange = SaReactorSyncHolder.getContent();
WebFilterChain chain = exchange.getAttribute(SaReactorHolder.CHAIN_KEY);
ServerHttpRequest newRequest = request.mutate().path(path).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
}
}

View File

@ -1,9 +1,15 @@
package cn.dev33.satoken.servlet.model;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.exception.SaTokenException;
/**
* Request for Servlet
@ -89,4 +95,18 @@ public class SaRequestForServlet implements SaRequest {
return request.getMethod();
}
/**
* 转发请求
*/
@Override
public Object forward(String path) {
try {
HttpServletResponse response = (HttpServletResponse)SaManager.getSaTokenContext().getResponse().getSource();
request.getRequestDispatcher(path).forward(request, response);
return null;
} catch (ServletException | IOException e) {
throw new SaTokenException(e);
}
}
}

View File

@ -1,8 +1,9 @@
package cn.dev33.satoken.solon.model;
import cn.dev33.satoken.context.model.SaRequest;
import org.noear.solon.core.handle.Context;
import cn.dev33.satoken.context.model.SaRequest;
/**
* @author noear
* @since 1.4
@ -49,4 +50,5 @@ public class SaRequestForSolon implements SaRequest {
public String getMethod() {
return ctx.method();
}
}