diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java b/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java index 8eaf6671..9d3a8b7e 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java @@ -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 { } /** - * 返回当前请求的url,例:http://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"); + } } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoConsts.java b/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoConsts.java index 5f39d710..8f049dae 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoConsts.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoConsts.java @@ -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\"}"; - + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java b/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java index a45c2b92..ccb27877 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoHandle.java @@ -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 ""; + } + return res.redirect(back); + } else { + return SaResult.ok("单点注销成功"); + } + } + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoTemplate.java b/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoTemplate.java index d2236828..a30656ee 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoTemplate.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoTemplate.java @@ -212,8 +212,7 @@ public class SaSsoTemplate { } // 3、是否在[允许地址列表]之中 - String authUrl = SaManager.getConfig().getSso().getAllowUrl().replaceAll(" ", ""); - List authUrlList = Arrays.asList(authUrl.split(",")); + List 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 diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoUtil.java index e71e86e6..a48bf95b 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/sso/SaSsoUtil.java @@ -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(); + } + /** * 构建URL:Server端 账号资料查询地址 * @param loginId 账号id diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 7b9c89f9..87ed101b 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -64,6 +64,7 @@ public class StpLogic { */ public StpLogic setLoginType(String loginType){ this.loginType = loginType; + SaManager.putStpLogic(this); return this; } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java index 249404d4..7a7a0324 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java @@ -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数字拼接一个随机字符串 diff --git a/sa-token-demo/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java b/sa-token-demo/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java index b2e9064a..612de553 100644 --- a/sa-token-demo/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java +++ b/sa-token-demo/sa-token-demo-sso2-client/src/main/java/com/pj/sso/SsoClientController.java @@ -21,8 +21,7 @@ public class SsoClientController { String str = "

Sa-Token SSO-Client 应用端

" + "

当前会话是否登录:" + StpUtil.isLogin() + "

" + "

登录 " + - "注销

"; - // "注销

"; // 上面是[跳页面]方式,这个是[RestAPI]方式 区别在于是否加了back参数 + "注销

"; return str; } diff --git a/sa-token-demo/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java b/sa-token-demo/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java index 3bd9da9e..82e4c39a 100644 --- a/sa-token-demo/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java +++ b/sa-token-demo/sa-token-demo-sso3-client/src/main/java/com/pj/sso/SsoClientController.java @@ -26,7 +26,7 @@ public class SsoClientController { String str = "

Sa-Token SSO-Client 应用端

" + "

当前会话是否登录:" + StpUtil.isLogin() + "

" + "

登录" + - " 注销

"; + " 注销

"; return str; } diff --git a/sa-token-demo/sa-token-demo-sso3-client/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-sso3-client/src/main/resources/application.yml index 282d7db7..3f6194dd 100644 --- a/sa-token-demo/sa-token-demo-sso3-client/src/main/resources/application.yml +++ b/sa-token-demo/sa-token-demo-sso3-client/src/main/resources/application.yml @@ -4,12 +4,6 @@ server: # sa-token配置 sa-token: - # Token名称 - token-name: satoken - # Token有效期 - timeout: 2592000 - # Token风格 - token-style: uuid # SSO-相关配置 sso: # SSO-Server端 单点登录地址 diff --git a/sa-token-doc/doc/sso/sso-cd.md b/sa-token-doc/doc/sso/sso-cd.md index a65563a2..878cc3e8 100644 --- a/sa-token-doc/doc/sso/sso-cd.md +++ b/sa-token-doc/doc/sso/sso-cd.md @@ -14,7 +14,7 @@ ``` ##### 1.2、后端拦截重定向 -在后端注册全局过滤器(或拦截器),拦截需要登录后才能访问的页面资源,将未登录的访问重定向至登录接口 +在后端注册全局过滤器(或拦截器、或全局异常处理),拦截需要登录后才能访问的页面资源,将未登录的访问重定向至登录接口 ``` java /** * Sa-Token 配置类 diff --git a/sa-token-doc/doc/sso/sso-type2.md b/sa-token-doc/doc/sso/sso-type2.md index eaa6eae9..74522ab3 100644 --- a/sa-token-doc/doc/sso/sso-type2.md +++ b/sa-token-doc/doc/sso/sso-type2.md @@ -195,7 +195,7 @@ public class SsoClientController { String str = "

Sa-Token SSO-Client 应用端

" + "

当前会话是否登录:" + StpUtil.isLogin() + "

" + "

登录 " + - "注销

"; + "注销

"; return str; } diff --git a/sa-token-doc/doc/sso/sso-type3.md b/sa-token-doc/doc/sso/sso-type3.md index da518519..a97af5fd 100644 --- a/sa-token-doc/doc/sso/sso-type3.md +++ b/sa-token-doc/doc/sso/sso-type3.md @@ -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-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-type3-slo-index.png 's-w-sh') diff --git a/sa-token-plugin/sa-token-quick-login/src/main/java/cn/dev33/satoken/quick/SaQuickBean.java b/sa-token-plugin/sa-token-quick-login/src/main/java/cn/dev33/satoken/quick/SaQuickBean.java index 0b6017a6..6055e238 100644 --- a/sa-token-plugin/sa-token-quick-login/src/main/java/cn/dev33/satoken/quick/SaQuickBean.java +++ b/sa-token-plugin/sa-token-quick-login/src/main/java/cn/dev33/satoken/quick/SaQuickBean.java @@ -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(); }); } diff --git a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorHolder.java b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorHolder.java index 44c8a38d..634c2060 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorHolder.java +++ b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/context/SaReactorHolder.java @@ -16,6 +16,11 @@ public class SaReactorHolder { */ public static final Class CONTEXT_KEY = ServerWebExchange.class; + /** + * chain_key + */ + public static final String CHAIN_KEY = "WEB_FILTER_CHAIN_KEY"; + /** * 获取上下文对象 * @return see note diff --git a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java index ff241449..a743a382 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java +++ b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/filter/SaReactorFilter.java @@ -151,6 +151,10 @@ public class SaReactorFilter implements WebFilter { @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + + // 写入WebFilterChain对象 + exchange.getAttributes().put(SaReactorHolder.CHAIN_KEY, chain); + // ---------- 全局认证处理 try { // 写入全局上下文 (同步) diff --git a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java index be3dd606..24d7c79a 100644 --- a/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java +++ b/sa-token-starter/sa-token-reactor-spring-boot-starter/src/main/java/cn/dev33/satoken/reactor/model/SaRequestForReactor.java @@ -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); + } + } diff --git a/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java b/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java index 191a27ad..84e43f8e 100644 --- a/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java +++ b/sa-token-starter/sa-token-servlet/src/main/java/cn/dev33/satoken/servlet/model/SaRequestForServlet.java @@ -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); + } + } + } diff --git a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java index eef1ad7b..183e5ed8 100644 --- a/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java +++ b/sa-token-starter/sa-token-solon-plugin/src/main/java/cn/dev33/satoken/solon/model/SaRequestForSolon.java @@ -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(); } + }