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 b79a2489..5f39d710 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 @@ -1,7 +1,7 @@ package cn.dev33.satoken.sso; /** - * Sa-Token-SSO模块相关常量 + * Sa-Token-SSO模块相关常量 * @author kong * */ @@ -22,7 +22,7 @@ public class SaSsoConsts { /** SSO-Server端:校验ticket 获取账号id */ public static String ssoCheckTicket = "/sso/checkTicket"; - /** SSO-Server端 (and Client端):单点注销 */ + /** SSO-Server端 (and Client端):单点注销地址 */ public static String ssoLogout = "/sso/logout"; /** SSO-Client端:登录地址 */ @@ -57,6 +57,9 @@ public class SaSsoConsts { /** Client端单点注销时-回调URL 参数名称 */ public static String ssoLogoutCall = "ssoLogoutCall"; + public static String name = "name"; + public static String pwd = "pwd"; + } /** Client端单点注销回调URL的Set集合,存储在Session中使用的key */ 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 3c1483b5..6c20e82f 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 @@ -19,59 +19,35 @@ import cn.dev33.satoken.util.SaResult; public class SaSsoHandle { /** - * 处理Server端请求 + * 处理所有Server端请求 * @return 处理结果 */ public static Object serverRequest() { // 获取变量 SaRequest req = SaHolder.getRequest(); - SaResponse res = SaHolder.getResponse(); - SaSsoConfig sso = SaManager.getConfig().getSso(); - StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; + SaSsoConfig cfg = SaManager.getConfig().getSso(); + + // ------------------ 路由分发 ------------------ - // ---------- SSO-Server端:单点登录授权地址 + // SSO-Server端:授权地址 if(req.isPath(Api.ssoAuth)) { - // ---------- 此处两种情况分开处理: - // 情况1:在SSO认证中心尚未登录,则先去登登录 - if(stpLogic.isLogin() == false) { - return sso.notLoginView.get(); - } - // 情况2:在SSO认证中心已经登录,开始构建授权重定向地址,下放ticket - String redirectUrl = SaSsoUtil.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(ParamName.redirect)); - return res.redirect(redirectUrl); + return ssoAuth(); } - // ---------- SSO-Server端:RestAPI 登录接口 + // SSO-Server端:RestAPI 登录接口 if(req.isPath(Api.ssoDoLogin)) { - return sso.doLoginHandle.apply(req.getParam("name"), req.getParam("pwd")); + return ssoDoLogin(); } - // ---------- SSO-Server端:校验ticket 获取账号id - if(req.isPath(Api.ssoCheckTicket) && sso.isHttp) { - String ticket = req.getParam(ParamName.ticket); - String sloCallback = req.getParam(ParamName.ssoLogoutCall); - - // 校验ticket,获取对应的账号id - Object loginId = SaSsoUtil.checkTicket(ticket); - - // 注册此客户端的单点注销回调URL - SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback); - - // 返回给Client端 - return loginId; + // SSO-Server端:校验ticket 获取账号id + if(req.isPath(Api.ssoCheckTicket) && cfg.isHttp) { + return ssoCheckTicket(); } - // ---------- SSO-Server端:单点注销 - if(req.isPath(Api.ssoLogout) && sso.isSlo) { - String loginId = req.getParam(ParamName.loginId); - String secretkey = req.getParam(ParamName.secretkey); - - // 遍历通知Client端注销会话 - SaSsoUtil.singleLogout(secretkey, loginId, url -> sso.sendHttp.apply(url)); - - // 完成 - return SaSsoConsts.OK; + // SSO-Server端:单点注销 + if(req.isPath(Api.ssoLogout) && cfg.isSlo) { + return ssoServerLogout(); } // 默认返回 @@ -79,102 +55,245 @@ public class SaSsoHandle { } /** - * 处理Client端请求 + * SSO-Server端:授权地址 + * @return 处理结果 + */ + public static Object ssoAuth() { + // 获取变量 + SaRequest req = SaHolder.getRequest(); + SaResponse res = SaHolder.getResponse(); + SaSsoConfig cfg = SaManager.getConfig().getSso(); + StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; + + // ---------- 此处两种情况分开处理: + // 情况1:在SSO认证中心尚未登录,则先去登登录 + if(stpLogic.isLogin() == false) { + return cfg.notLoginView.get(); + } + // 情况2:在SSO认证中心已经登录,开始构建授权重定向地址,下放ticket + String redirectUrl = SaSsoUtil.buildRedirectUrl(stpLogic.getLoginId(), req.getParam(ParamName.redirect)); + return res.redirect(redirectUrl); + } + + /** + * SSO-Server端:RestAPI 登录接口 + * @return 处理结果 + */ + public static Object ssoDoLogin() { + // 获取变量 + SaRequest req = SaHolder.getRequest(); + SaSsoConfig cfg = SaManager.getConfig().getSso(); + + // 处理 + return cfg.doLoginHandle.apply(req.getParam(ParamName.name), req.getParam(ParamName.pwd)); + } + + /** + * SSO-Server端:校验ticket 获取账号id + * @return 处理结果 + */ + public static Object ssoCheckTicket() { + // 获取变量 + SaRequest req = SaHolder.getRequest(); + + // 获取参数 + String ticket = req.getParam(ParamName.ticket); + String sloCallback = req.getParam(ParamName.ssoLogoutCall); + + // 校验ticket,获取对应的账号id + Object loginId = SaSsoUtil.checkTicket(ticket); + + // 注册此客户端的单点注销回调URL + SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback); + + // 返回给Client端 + return loginId; + } + + /** + * SSO-Server端:单点注销 + * @return 处理结果 + */ + public static Object ssoServerLogout() { + // 获取变量 + SaRequest req = SaHolder.getRequest(); + SaSsoConfig cfg = SaManager.getConfig().getSso(); + StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; + + // 获取参数 + String loginId = req.getParam(ParamName.loginId); + String secretkey = req.getParam(ParamName.secretkey); + + // 遍历通知Client端注销会话 + // SaSsoUtil.singleLogout(secretkey, loginId, url -> cfg.sendHttp.apply(url)); + // step.1 校验秘钥 + SaSsoUtil.checkSecretkey(secretkey); + + // step.2 遍历通知Client端注销会话 + SaSsoUtil.forEachSloUrl(loginId, url -> cfg.sendHttp.apply(url)); + + // step.3 Server端注销 + stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId)); + + // 完成 + return SaSsoConsts.OK; + } + + + /** + * 处理所有Client端请求 * @return 处理结果 */ public static Object clientRequest() { // 获取变量 SaRequest req = SaHolder.getRequest(); - SaResponse res = SaHolder.getResponse(); - SaSsoConfig sso = SaManager.getConfig().getSso(); - StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; + SaSsoConfig cfg = SaManager.getConfig().getSso(); + // ------------------ 路由分发 ------------------ + // ---------- SSO-Client端:登录地址 if(req.isPath(Api.ssoLogin)) { - String back = req.getParam(ParamName.back, "/"); - String ticket = req.getParam(ParamName.ticket); - - // 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回 - if(stpLogic.isLogin()) { - return res.redirect(back); - } - /* - * 此时有两种情况: - * 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心 - * 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录 - */ - if(ticket == null) { - String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back); - return res.redirect(serverAuthUrl); - } else { - // ------- 1、校验ticket,获取账号id - Object loginId = null; - if(sso.isHttp) { - // 方式1:使用http请求校验ticket - String ssoLogoutCall = null; - if(sso.isSlo) { - ssoLogoutCall = SaHolder.getRequest().getUrl().replace(Api.ssoLogin, Api.ssoLogoutCall); - } - String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall); - Object body = sso.sendHttp.apply(checkUrl); - loginId = (SaFoxUtil.isEmpty(body) ? null : body); - } else { - // 方式2:直连Redis校验ticket - loginId = SaSsoUtil.checkTicket(ticket); - } - // ------- 2、如果loginId有值,说明ticket有效,进行登录并重定向至back地址 - if(loginId != null ) { - stpLogic.login(loginId); - return res.redirect(back); - } else { - // 如果ticket无效: - return sso.ticketInvalidView.apply(ticket); - } - } + return ssoLogin(); } // ---------- SSO-Client端:单点注销 [模式二] - if(req.isPath(Api.ssoLogout) && sso.isSlo && sso.isHttp == false) { - stpLogic.logout(); - if(req.getParam(ParamName.back) == null) { - return SaResult.ok("单点注销成功"); - } else { - return res.redirect(req.getParam(ParamName.back, "/")); - } + if(req.isPath(Api.ssoLogout) && cfg.isSlo && cfg.isHttp == false) { + return ssoLogoutType2(); } // ---------- SSO-Client端:单点注销 [模式三] - if(req.isPath(Api.ssoLogout) && sso.isSlo && sso.isHttp) { - // 如果未登录,则无需注销 - if(stpLogic.isLogin() == false) { - return SaResult.ok(); - } - // 调用SSO-Server认证中心API,进行注销 - String url = SaSsoUtil.buildSloUrl(stpLogic.getLoginId()); - String body = String.valueOf(sso.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, "/")); - } - } - return SaResult.error("单点注销失败"); + if(req.isPath(Api.ssoLogout) && cfg.isSlo && cfg.isHttp) { + return ssoLogoutType3(); } // ---------- SSO-Client端:单点注销的回调 [模式三] - if(req.isPath(Api.ssoLogoutCall) && sso.isSlo && sso.isHttp) { - String loginId = req.getParam(ParamName.loginId); - String secretkey = req.getParam(ParamName.secretkey); - - SaSsoUtil.checkSecretkey(secretkey); - stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId)); - return SaSsoConsts.OK; + if(req.isPath(Api.ssoLogoutCall) && cfg.isSlo && cfg.isHttp) { + return ssoLogoutCall(); } // 默认返回 return SaSsoConsts.NOT_HANDLE; } + /** + * SSO-Client端:登录地址 + * @return 处理结果 + */ + public static Object ssoLogin() { + // 获取变量 + SaRequest req = SaHolder.getRequest(); + SaResponse res = SaHolder.getResponse(); + SaSsoConfig cfg = SaManager.getConfig().getSso(); + StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; + + // 获取参数 + String back = req.getParam(ParamName.back, "/"); + String ticket = req.getParam(ParamName.ticket); + + // 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回 + if(stpLogic.isLogin()) { + return res.redirect(back); + } + /* + * 此时有两种情况: + * 情况1:ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心 + * 情况2:ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录 + */ + if(ticket == null) { + String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back); + return res.redirect(serverAuthUrl); + } else { + // ------- 1、校验ticket,获取账号id + Object loginId = null; + if(cfg.isHttp) { + // 方式1:使用http请求校验ticket + String ssoLogoutCall = null; + if(cfg.isSlo) { + ssoLogoutCall = SaHolder.getRequest().getUrl().replace(Api.ssoLogin, Api.ssoLogoutCall); + } + String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall); + Object body = cfg.sendHttp.apply(checkUrl); + loginId = (SaFoxUtil.isEmpty(body) ? null : body); + } else { + // 方式2:直连Redis校验ticket + loginId = SaSsoUtil.checkTicket(ticket); + } + // ------- 2、如果loginId有值,说明ticket有效,进行登录并重定向至back地址 + if(loginId != null ) { + stpLogic.login(loginId); + return res.redirect(back); + } else { + // 如果ticket无效: + return cfg.ticketInvalidView.apply(ticket); + } + } + } + + /** + * SSO-Client端:单点注销 [模式二] + * @return 处理结果 + */ + public static Object ssoLogoutType2() { + // 获取变量 + SaRequest req = SaHolder.getRequest(); + SaResponse res = SaHolder.getResponse(); + StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; + + // 开始处理 + stpLogic.logout(); + if(req.getParam(ParamName.back) == null) { + return SaResult.ok("单点注销成功"); + } else { + return res.redirect(req.getParam(ParamName.back, "/")); + } + } + + /** + * SSO-Client端:单点注销 [模式三] + * @return 处理结果 + */ + public static Object ssoLogoutType3() { + // 获取变量 + SaRequest req = SaHolder.getRequest(); + SaResponse res = SaHolder.getResponse(); + SaSsoConfig cfg = SaManager.getConfig().getSso(); + StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; + + // 如果未登录,则无需注销 + if(stpLogic.isLogin() == false) { + return SaResult.ok(); + } + + // 调用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, "/")); + } + } + return SaResult.error("单点注销失败"); + } + + /** + * SSO-Client端:单点注销的回调 [模式三] + * @return 处理结果 + */ + public static Object ssoLogoutCall() { + // 获取变量 + SaRequest req = SaHolder.getRequest(); + StpLogic stpLogic = SaSsoUtil.saSsoTemplate.stpLogic; + + // 获取参数 + String loginId = req.getParam(ParamName.loginId); + String secretkey = req.getParam(ParamName.secretkey); + + SaSsoUtil.checkSecretkey(secretkey); + stpLogic.logoutByTokenValue(stpLogic.getTokenValueByLoginId(loginId)); + return SaSsoConsts.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 8857c0e6..50af22a3 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 @@ -8,77 +8,87 @@ import java.util.Set; import cn.dev33.satoken.SaManager; import cn.dev33.satoken.config.SaSsoConfig; import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.sso.SaSsoConsts.ParamName; import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.util.SaFoxUtil; /** - * Sa-Token-SSO 单点登录模块 + * Sa-Token-SSO 单点登录模块 * @author kong * */ public class SaSsoTemplate { + /** + * 单点登录模块使用的 StpLogic 对象 + */ public StpLogic stpLogic; public SaSsoTemplate(StpLogic stpLogic) { this.stpLogic = stpLogic; } + // ---------------------- Ticket 操作 ---------------------- + /** - * 创建一个 Ticket码 + * 根据 账号id 创建一个 Ticket码 * @param loginId 账号id - * @return 票据 + * @return Ticket码 */ public String createTicket(Object loginId) { - // 随机一个ticket + // 创建 Ticket String ticket = randomTicket(loginId); - // 保存入库 - long ticketTimeout = SaManager.getConfig().getSso().getTicketTimeout(); - SaManager.getSaTokenDao().set(splicingKeyTicketToId(ticket), String.valueOf(loginId), ticketTimeout); - SaManager.getSaTokenDao().set(splicingKeyIdToTicket(loginId), String.valueOf(ticket), ticketTimeout); + // 保存 Ticket + saveTicket(ticket, loginId); + saveTicketIndex(ticket, loginId); - // 返回 + // 返回 Ticket return ticket; } /** - * 删除一个 Ticket码 - * @param ticket Ticket码 + * 保存 Ticket + * @param ticket ticket码 + * @param loginId 账号id */ - public void deleteTicket(String ticket) { - Object loginId = getLoginId(ticket); - if(loginId != null) { - SaManager.getSaTokenDao().delete(splicingKeyTicketToId(ticket)); - SaManager.getSaTokenDao().delete(splicingKeyIdToTicket(loginId)); - } + public void saveTicket(String ticket, Object loginId) { + long ticketTimeout = SaManager.getConfig().getSso().getTicketTimeout(); + SaManager.getSaTokenDao().set(splicingTicketSaveKey(ticket), String.valueOf(loginId), ticketTimeout); } /** - * 构建URL:Server端向Client下放ticke的地址 + * 保存 Ticket 索引 + * @param ticket ticket码 * @param loginId 账号id - * @param redirect Client端提供的重定向地址 - * @return see note */ - public String buildRedirectUrl(Object loginId, String redirect) { - // 校验重定向地址 - checkRedirectUrl(redirect); - - // 删掉旧ticket - String oldTicket = SaManager.getSaTokenDao().get(splicingKeyIdToTicket(loginId)); - if(oldTicket != null) { - deleteTicket(oldTicket); - } - - // 获取新ticket - String ticket = createTicket(loginId); - - // 构建 授权重定向地址 - redirect = encodeBackParam(redirect); - String redirectUrl = SaFoxUtil.joinParam(redirect, ParamName.ticket, ticket); - return redirectUrl; + public void saveTicketIndex(String ticket, Object loginId) { + long ticketTimeout = SaManager.getConfig().getSso().getTicketTimeout(); + SaManager.getSaTokenDao().set(splicingTicketIndexKey(loginId), String.valueOf(ticket), ticketTimeout); } + /** + * 删除 Ticket + * @param ticket Ticket码 + */ + public void deleteTicket(String ticket) { + if(ticket == null) { + return; + } + SaManager.getSaTokenDao().delete(splicingTicketSaveKey(ticket)); + } + + /** + * 删除 Ticket索引 + * @param loginId 账号id + */ + public void deleteTicketIndex(Object loginId) { + if(loginId == null) { + return; + } + SaManager.getSaTokenDao().delete(splicingTicketIndexKey(loginId)); + } + /** * 根据 Ticket码 获取账号id,如果Ticket码无效则返回null * @param ticket Ticket码 @@ -88,7 +98,7 @@ public class SaSsoTemplate { if(SaFoxUtil.isEmpty(ticket)) { return null; } - return SaManager.getSaTokenDao().get(splicingKeyTicketToId(ticket)); + return SaManager.getSaTokenDao().get(splicingTicketSaveKey(ticket)); } /** @@ -101,9 +111,21 @@ public class SaSsoTemplate { public T getLoginId(String ticket, Class cs) { return SaFoxUtil.getValueByType(getLoginId(ticket), cs); } - + /** - * 校验ticket码,获取账号id,如果ticket可以有效,则立刻删除 + * 查询 指定账号id的 Ticket值 + * @param loginId 账号id + * @return Ticket值 + */ + public String getTicketValue(Object loginId) { + if(loginId == null) { + return null; + } + return SaManager.getSaTokenDao().get(splicingTicketIndexKey(loginId)); + } + + /** + * 校验ticket码,获取账号id,如果此ticket是有效的,则立即删除 * @param ticket Ticket码 * @return 账号id */ @@ -111,45 +133,31 @@ public class SaSsoTemplate { Object loginId = getLoginId(ticket); if(loginId != null) { deleteTicket(ticket); + deleteTicketIndex(loginId); } return loginId; } /** - * 校验重定向url合法性 - * @param url 下放ticket的url地址 + * 随机一个 Ticket码 + * @param loginId 账号id + * @return Ticket码 */ - public void checkRedirectUrl(String url) { - - // 1、是否是一个有效的url - if(SaFoxUtil.isUrl(url) == false) { - throw new SaTokenException("无效回调地址:" + url); - } - - // 2、截取掉?后面的部分 - int qIndex = url.indexOf("?"); - if(qIndex != -1) { - url = url.substring(0, qIndex); - } - - // 3、是否在[允许地址列表]之中 - String authUrl = SaManager.getConfig().getSso().getAllowUrl().replaceAll(" ", ""); - List authUrlList = Arrays.asList(authUrl.split(",")); - if(SaManager.getSaTokenAction().hasElement(authUrlList, url) == false) { - throw new SaTokenException("非法回调地址:" + url); - } - - // 验证通过 - return; + public String randomTicket(Object loginId) { + return SaFoxUtil.getRandomString(64); } + + // ---------------------- 构建URL ---------------------- + /** - * 构建URL:Server端 单点登录地址 + * 构建URL:Server端 单点登录地址 * @param clientLoginUrl Client端登录地址 * @param back 回调路径 - * @return [SSO-Server端-认证地址 ] + * @return [SSO-Server端-认证地址 ] */ public String buildServerAuthUrl(String clientLoginUrl, String back) { + // 服务端认证地址 String serverUrl = SaManager.getConfig().getSso().getAuthUrl(); @@ -165,6 +173,55 @@ public class SaSsoTemplate { return serverAuthUrl; } + /** + * 构建URL:Server端向Client下放ticke的地址 + * @param loginId 账号id + * @param redirect Client端提供的重定向地址 + * @return see note + */ + public String buildRedirectUrl(Object loginId, String redirect) { + + // 校验 重定向地址 是否合法 + checkRedirectUrl(redirect); + + // 删掉 旧Ticket + deleteTicket(getTicketValue(loginId)); + + // 创建 新Ticket + String ticket = createTicket(loginId); + + // 构建 授权重定向地址 (Server端 根据此地址向 Client端 下放Ticket) + return SaFoxUtil.joinParam(encodeBackParam(redirect), ParamName.ticket, ticket); + } + + /** + * 校验重定向url合法性 + * @param url 下放ticket的url地址 + */ + public void checkRedirectUrl(String url) { + + // 1、是否是一个有效的url + if(SaFoxUtil.isUrl(url) == false) { + throw new SaTokenException("无效redirect:" + url); + } + + // 2、截取掉?后面的部分 + int qIndex = url.indexOf("?"); + if(qIndex != -1) { + url = url.substring(0, qIndex); + } + + // 3、是否在[允许地址列表]之中 + String authUrl = SaManager.getConfig().getSso().getAllowUrl().replaceAll(" ", ""); + List authUrlList = Arrays.asList(authUrl.split(",")); + if(SaManager.getSaTokenAction().hasElement(authUrlList, url) == false) { + throw new SaTokenException("非法redirect:" + url); + } + + // 校验通过 √ + return; + } + /** * 对url中的back参数进行URL编码, 解决超链接重定向后参数丢失的bug * @param url url @@ -191,17 +248,8 @@ public class SaSsoTemplate { return url; } - /** - * 随机一个 Ticket码 - * @param loginId 账号id - * @return 票据 - */ - public String randomTicket(Object loginId) { - return SaFoxUtil.getRandomString(64); - } - - // ------------------- SSO 模式三 ------------------- + // ------------------- SSO 模式三相关 ------------------- /** * 校验secretkey秘钥是否有效 @@ -215,18 +263,23 @@ public class SaSsoTemplate { /** * 构建URL:校验ticket的URL + *

在模式三下,Client端拿到Ticket后根据此地址向Server端发送请求,获取账号id * @param ticket ticket码 * @param ssoLogoutCallUrl 单点注销时的回调URL * @return 构建完毕的URL */ public String buildCheckTicketUrl(String ticket, String ssoLogoutCallUrl) { + // 裸地址 String url = SaManager.getConfig().getSso().getCheckTicketUrl(); + // 拼接ticket参数 url = SaFoxUtil.joinParam(url, ParamName.ticket, ticket); + // 拼接单点注销时的回调URL if(ssoLogoutCallUrl != null) { url = SaFoxUtil.joinParam(url, ParamName.ssoLogoutCall, ssoLogoutCallUrl); } + // 返回 return url; } @@ -240,9 +293,10 @@ public class SaSsoTemplate { if(loginId == null || sloCallbackUrl == null || sloCallbackUrl.isEmpty()) { return; } - Set urlSet = stpLogic.getSessionByLoginId(loginId).get(SaSsoConsts.SLO_CALLBACK_SET_KEY, ()-> new HashSet()); + SaSession session = stpLogic.getSessionByLoginId(loginId); + Set urlSet = session.get(SaSsoConsts.SLO_CALLBACK_SET_KEY, ()-> new HashSet()); urlSet.add(sloCallbackUrl); - stpLogic.getSessionByLoginId(loginId).set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet); + session.set(SaSsoConsts.SLO_CALLBACK_SET_KEY, urlSet); } /** @@ -304,20 +358,23 @@ public class SaSsoTemplate { * @param ticket ticket值 * @return key */ - public String splicingKeyTicketToId(String ticket) { + public String splicingTicketSaveKey(String ticket) { return SaManager.getConfig().getTokenName() + ":ticket:" + ticket; } /** - * 拼接key:账号Id 反查 Ticket + * 拼接key:账号Id 反查 Ticket * @param id 账号id * @return key */ - public String splicingKeyIdToTicket(Object id) { + public String splicingTicketIndexKey(Object id) { return SaManager.getConfig().getTokenName() + ":id-ticket:" + id; } - + /** + * 单点注销回调函数 + * @author kong + */ @FunctionalInterface static interface CallSloUrlFunction{ /** @@ -327,5 +384,4 @@ public class SaSsoTemplate { public void run(String 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 870e456e..5978c964 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 @@ -4,7 +4,7 @@ import cn.dev33.satoken.sso.SaSsoTemplate.CallSloUrlFunction; import cn.dev33.satoken.stp.StpUtil; /** - * Sa-Token-SSO 单点登录工具类 + * Sa-Token-SSO 单点登录模块 工具类 * @author kong * */ @@ -15,33 +15,34 @@ public class SaSsoUtil { */ public static SaSsoTemplate saSsoTemplate = new SaSsoTemplate(StpUtil.stpLogic); + + // ---------------------- Ticket 操作 ---------------------- + /** - * 创建一个 Ticket票据 + * 根据 账号id 创建一个 Ticket码 * @param loginId 账号id - * @return 票据 + * @return Ticket码 */ public static String createTicket(Object loginId) { return saSsoTemplate.createTicket(loginId); } /** - * 删除一个 Ticket码 + * 删除 Ticket * @param ticket Ticket码 */ public static void deleteTicket(String ticket) { saSsoTemplate.deleteTicket(ticket); } - - /** - * 构建URL:Server端向Client下放ticke的地址 - * @param loginId 账号id - * @param redirect Client端提供的重定向地址 - * @return see note - */ - public static String buildRedirectUrl(Object loginId, String redirect) { - return saSsoTemplate.buildRedirectUrl(loginId, redirect); - } + /** + * 删除 Ticket索引 + * @param loginId 账号id + */ + public static void deleteTicketIndex(Object loginId) { + saSsoTemplate.deleteTicketIndex(loginId); + } + /** * 根据 Ticket码 获取账号id,如果Ticket码无效则返回null * @param ticket Ticket码 @@ -63,7 +64,7 @@ public class SaSsoUtil { } /** - * 校验ticket码,获取账号id,如果ticket可以有效,则立刻删除 + * 校验ticket码,获取账号id,如果此ticket是有效的,则立即删除 * @param ticket Ticket码 * @return 账号id */ @@ -71,13 +72,8 @@ public class SaSsoUtil { return saSsoTemplate.checkTicket(ticket); } - /** - * 校验重定向url合法性 - * @param url 下放ticket的url地址 - */ - public static void checkAuthUrl(String url) { - saSsoTemplate.checkRedirectUrl(url); - } + + // ---------------------- 构建URL ---------------------- /** * 构建URL:Server端 单点登录地址 @@ -89,6 +85,24 @@ public class SaSsoUtil { return saSsoTemplate.buildServerAuthUrl(clientLoginUrl, back); } + /** + * 构建URL:Server端向Client下放ticke的地址 + * @param loginId 账号id + * @param redirect Client端提供的重定向地址 + * @return see note + */ + public static String buildRedirectUrl(Object loginId, String redirect) { + return saSsoTemplate.buildRedirectUrl(loginId, redirect); + } + + /** + * 校验重定向url合法性 + * @param url 下放ticket的url地址 + */ + public static void checkAuthUrl(String url) { + saSsoTemplate.checkRedirectUrl(url); + } + // ------------------- SSO 模式三 ------------------- diff --git a/sa-token-doc/doc/index.html b/sa-token-doc/doc/index.html index f9bdc05e..169f2a6c 100644 --- a/sa-token-doc/doc/index.html +++ b/sa-token-doc/doc/index.html @@ -47,7 +47,8 @@ 首页 需求墙 - 推荐公众号 + + 生态 更新日志

diff --git a/sa-token-doc/doc/lib/index.css b/sa-token-doc/doc/lib/index.css index a18dfc31..1d07e407 100644 --- a/sa-token-doc/doc/lib/index.css +++ b/sa-token-doc/doc/lib/index.css @@ -7,10 +7,10 @@ body{font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu #main h2 {font-size: 1.6rem;} #main h3 {font-size: 1.25rem;} -.main-box .markdown-section{ padding: 30px 20px; max-width: 65%} +.main-box .markdown-section{ padding: 30px 20px; max-width: 75%; margin-left: 10%;} @media screen and (max-width: 800px) { .logo-box {display: none;} - .main-box .markdown-section{max-width: 1000px;} + .main-box .markdown-section{max-width: 1000px; margin-left: auto;} } /* @media screen and (min-width: 1700px) { .main-box .markdown-section{max-width: 70%;} diff --git a/sa-token-doc/doc/more/common-questions.md b/sa-token-doc/doc/more/common-questions.md index f83913b3..1a632a9f 100644 --- a/sa-token-doc/doc/more/common-questions.md +++ b/sa-token-doc/doc/more/common-questions.md @@ -77,14 +77,23 @@ ### 我想让用户修改密码后立即掉线重新登录,应该怎么做? 框架内置 [强制指定账号下线] 的APi,在执行修改密码逻辑之后调用此API即可: `StpUtil.logout()` + ### 整合Redis时先选择了默认jdk序列化,后又改成jackson序列化,程序开始报错,SerializationException? 两者的序列化算法不一致导致的反序列化失败,如果要更改序列化方式,则需要先将Redis中历史数据清除,再做更新 +### 代码鉴权、注解鉴权、路由拦截鉴权,我该如何选择? +这个问题没有标准答案,这里只能给你提供一些建议,从鉴权粒度的角度来看: +1. 路由拦截鉴权:粒度最粗,只能粗略的拦截一个模块进行权限认证 +2. 注解鉴权:粒度较细,可以详细到方法级,比较灵活 +3. 代码鉴权:粒度最细,不光可以控制到方法级,甚至可以if语句决定是否鉴权 + +So:从鉴权粒度的角度来看,需要针对一个模块鉴权的时候,就用路由拦截鉴权,需要控制到方法级的时候,就用注解鉴权,需要根据条件判断是否鉴权的时候,就用代码鉴权 + + ### 还是有不明白到的地方? -请在`github`提交`issues`,或者加入qq群交流(群链接在[首页](README?id=交流群)) +请在`gitee` 、 `github` 提交 `issues`,或者加入qq群交流(群链接在[首页](README?id=交流群)) ### 我能为这个框架贡献代码吗? -**可以**,请参照首页的提交pr步骤 ,[贡献代码](README?id=贡献代码) - +**可以**,如果有好的想法,请直接提交pr步骤 diff --git a/sa-token-doc/doc/more/link.md b/sa-token-doc/doc/more/link.md index e5bcf3b8..fea0851b 100644 --- a/sa-token-doc/doc/more/link.md +++ b/sa-token-doc/doc/more/link.md @@ -1,8 +1,8 @@ -# 友情链接 +# Sa-Token 相关项目 --- -#### 集成Sa-Token的开源项目: +### 使用 Sa-Token 的开源项目: [**[ sa-plus ]** 一个基于springboot架构的快速开发框架,内置代码生成器](https://gitee.com/click33/sa-plus) @@ -12,20 +12,24 @@ [**[ helio-starters ]** 基于JDK15 + Spring Boot 2.4 + Sa-Token + Mybatis-Plus的单体Boot版脚手架和微服务Cloud版脚手架,带有配套后台管理前端模板及代码生成器](https://gitee.com/uncarbon97/helio-starters) +[**[ Sa-Token-Study ]** 以demo示例的方式讲解 Sa-Token 源码涉及到的技术点,连载中……](https://gitee.com/click33/sa-token-study) + +如果您的开源项目使用了Sa-Token,欢迎提交...
-#### 推荐项目: -[[OkHttps] - 一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps) +### 推荐项目: -[[hasor] - 轻量级ioc/aop框架,采用"微内核+插件"的设计思想](https://gitee.com/zycgit/hasor) +[[ OkHttps ] - 一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps) -[[sa-admin] - 一个多窗口后台模板,流畅、易上手、提高生产力](https://gitee.com/ejlchina-zhxu/okhttps) +[[ hasor ] - 轻量级ioc/aop框架,采用"微内核+插件"的设计思想](https://gitee.com/zycgit/hasor) -[[vue-next-admin] - 一套为开发者快速开发准备的基于 vue2.x 越看越精彩的后台管理系统一站式平台模板](https://gitee.com/lyt-top/vue-admin-wonderful) +[[ sa-admin ] - 一个多窗口后台模板,流畅、易上手、提高生产力](https://gitee.com/ejlchina-zhxu/okhttps) -[[小诺快速开发平台] - 基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本](https://xiaonuo.vip/index#pricing) +[[ vue-next-admin ] - 一套为开发者快速开发准备的基于 vue2.x 越看越精彩的后台管理系统一站式平台模板](https://gitee.com/lyt-top/vue-next-admin) + +[[ 小诺快速开发平台 ] - 基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本](https://xiaonuo.vip/index#pricing)
虚位以待...