diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginModel.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginModel.java index 7f22cefc8d3ebd8f7d36016ba3a7c3b265b1ff70..f58666c977e0c14db83fa44ec06ae28b75cad669 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginModel.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/SaLoginModel.java @@ -2,6 +2,7 @@ package cn.dev33.satoken.stp; import cn.dev33.satoken.SaTokenManager; import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.util.SaTokenConsts; /** @@ -23,9 +24,9 @@ public class SaLoginModel { public Long timeout; /** - * 是否为临时Cookie(临时Cookie会在浏览器关闭时自动删除) + * 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在) */ - public Boolean isTempCookie; + public Boolean isLastingCookie; /** @@ -61,21 +62,36 @@ public class SaLoginModel { } /** - * @return isTempCookie + * @return isLastingCookie */ - public Boolean getIsTempCookie() { - return isTempCookie; + public Boolean getIsLastingCookie() { + return isLastingCookie; } /** - * @param isTempCookie 要设置的 isTempCookie + * @param isLastingCookie 要设置的 isLastingCookie * @return 对象自身 */ - public SaLoginModel setIsTempCookie(Boolean isTempCookie) { - this.isTempCookie = isTempCookie; + public SaLoginModel setIsLastingCookie(Boolean isLastingCookie) { + this.isLastingCookie = isLastingCookie; return this; } + + /** + * @return cookie时长 + */ + public int getCookieTimeout() { + if(isLastingCookie == false) { + return -1; + } + if(timeout == SaTokenDao.NEVER_EXPIRE) { + return Integer.MAX_VALUE; + } + return (int)(long)timeout; + } + + /** * 构建对象,初始化默认值 * @return 对象自身 @@ -96,8 +112,8 @@ public class SaLoginModel { if(timeout == null) { timeout = config.getTimeout(); } - if(isTempCookie == null) { - isTempCookie = false; + if(isLastingCookie == null) { + isLastingCookie = true; } return this; } 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 2e616dfe7b24406cc4ea063001c06af9ef0085e7..d7486d300291a3fcefa5d3c20b3112008a65579b 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 @@ -161,10 +161,10 @@ public class StpLogic { /** * 在当前会话上登录id, 并指定登录设备 * @param loginId 登录id,建议的类型:(long | int | String) - * @param isTempCookie 是否为临时Cookie + * @param isLastingCookie 是否为持久Cookie */ - public void setLoginId(Object loginId, boolean isTempCookie) { - setLoginId(loginId, new SaLoginModel().setIsTempCookie(isTempCookie)); + public void setLoginId(Object loginId, boolean isLastingCookie) { + setLoginId(loginId, new SaLoginModel().setIsLastingCookie(isLastingCookie)); } /** @@ -234,9 +234,8 @@ public class StpLogic { // 注入Cookie if(config.getIsReadCookie() == true){ HttpServletResponse response = SaTokenManager.getSaTokenServlet().getResponse(); - int cookieTimeout = loginModel.getIsTempCookie() ? -1 : (int)(long)loginModel.getTimeout(); SaTokenManager.getSaTokenCookie().addCookie(response, getTokenName(), tokenValue, - "/", config.getCookieDomain(), cookieTimeout); + "/", config.getCookieDomain(), loginModel.getCookieTimeout()); } } @@ -584,8 +583,9 @@ public class StpLogic { setLastActivityToNow(tokenValue); // cookie注入 if(getConfig().getIsReadCookie() == true){ + int cookieTimeout = (int)(getConfig().getTimeout() == SaTokenDao.NEVER_EXPIRE ? Integer.MAX_VALUE : getConfig().getTimeout()); SaTokenManager.getSaTokenCookie().addCookie(SaTokenManager.getSaTokenServlet().getResponse(), getTokenName(), tokenValue, - "/", getConfig().getCookieDomain(), (int)getConfig().getTimeout()); + "/", getConfig().getCookieDomain(), cookieTimeout); } } } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java index b46b7ec57a75b1299cd82c75e10e9041feb06561..64388bde9ed648e9f452b174d1d5c5f79c5b1863 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java @@ -75,10 +75,10 @@ public class StpUtil { /** * 在当前会话上登录id, 并指定登录设备 * @param loginId 登录id,建议的类型:(long | int | String) - * @param isTempCookie 是否为临时Cookie + * @param isLastingCookie 是否为持久Cookie */ - public void setLoginId(Object loginId, boolean isTempCookie) { - stpLogic.setLoginId(loginId, isTempCookie); + public static void setLoginId(Object loginId, boolean isLastingCookie) { + stpLogic.setLoginId(loginId, isLastingCookie); } /** diff --git a/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java b/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java index f56ef2942903dc7de735af6b6f0615a9c904c399..ed89978f548a5ac4d1067b90aa300710028571d2 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java @@ -14,7 +14,6 @@ import com.pj.util.AjaxJson; import cn.dev33.satoken.exception.NotLoginException; import cn.dev33.satoken.exception.NotPermissionException; import cn.dev33.satoken.exception.NotRoleException; -import cn.dev33.satoken.stp.StpUtil; /** * 全局异常处理 @@ -25,7 +24,7 @@ public class GlobalException { // 在当前类每个方法进入之前触发的操作 @ModelAttribute public void get(HttpServletRequest request) throws IOException { - StpUtil.checkPermission("user:add"); + } diff --git a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java index 6427852515bc1210c3df826863f4bbf4c781fc2b..fe537f8f0a7e51d66335f0dbcb99b8ffd507e6d9 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java @@ -246,9 +246,10 @@ public class TestController { // StpUtil.getTokenSession().logout(); // StpUtil.logoutByLoginId(10001); // StpUtil.setLoginId(10001); -// StpUtil.setLoginId(10001, new SaLoginModel().setIsTempCookie(true)); +// StpUtil.setLoginId(10001, false); // StpUtil.getLoginId(); - +// StpUtil.setLoginId(10001); +// StpUtil.getTokenSession(); return AjaxJson.getSuccess("访问成功"); } diff --git a/sa-token-doc/doc/use/remember-me.md b/sa-token-doc/doc/use/remember-me.md index 6d2940a645d9f7cc87fee0af537258107018c053..540501fae3c6322aef72854c44735051364bff4a 100644 --- a/sa-token-doc/doc/use/remember-me.md +++ b/sa-token-doc/doc/use/remember-me.md @@ -10,9 +10,52 @@ ### 在sa-token中实现记住我功能 -sa-token的登录授权,默认就是`记住我`模式,为了实现`非记住我`模式, 你需要做一些适配 +sa-token的登录授权,**默认就是`[记住我]`模式**,为了实现`[非记住我]`模式, 你需要在登录时如下设置: -要 +``` java +// 设置登录账号id为10001,第二个参数指定是否为[记住我],当此值为false后,关闭浏览器后再次打开需要重新登录 +StpUtil.setLoginId(10001, false); +``` + +那么,sa-token实现`[记住我]`的具体原理是? + + +### 实现原理 +Cookie作为浏览器提供的默认会话跟踪机制,其生命周期有两种形式,分别是: +- 临时Cookie:有效期为本次会话,只要关闭浏览器窗口,Cookie就会消失 +- 永久Cookie:有效期为一个具体的时间,在时间未到期之前,即使用户关闭了浏览器Cookie也不会消失 + +利用Cookie的此特性,我们便可以轻松实现 [记住我] 模式: +- 勾选[记住我]按钮时:调用`StpUtil.setLoginId(10001, true)`,在浏览器写入一个`永久Cookie`保存token,此时用户即使重启浏览器token依然有效 +- 不勾选[记住我]按钮时:调用`StpUtil.setLoginId(10001, false)`,在浏览器写入一个`临时Cookie`保存token,此时用户在重启浏览器后token便会消失,导致会话失效 + + +### 前后台分离模式下如何实现[记住我]? + +此时机智的你😏很快发现一个问题,Cookie虽好,却无法在前后端分离环境下使用,那是不是代表上述方案在APP、小程序等环境中无效? + +准确的讲,答案是肯定的,任何基于Cookie的认证方案在前后台分离环境下都会失效(原因在于这些客户端默认没有实现Cookie功能),不过好在,这些客户端一般都提供了替代方案, +唯一遗憾的是,此场景中token的生命周期需要我们在前端手动控制 + +以经典跨端框架 [uni-app](https://uniapp.dcloud.io/) 为例,我们可以使用如下方式达到同样的效果: +``` js +// 使用本地存储保存token,达到 [永久Cookie] 的效果 +uni.setStorageSync("satoken", "xxxx-xxxx-xxxx-xxxx-xxx"); + +// 使用globalData保存token,达到 [临时Cookie] 的效果 +getApp().globalData.satoken = "xxxx-xxxx-xxxx-xxxx-xxx"; +``` + +如果你决定在PC浏览器环境下进行前后台分离模式开发,那么更加简单: +``` js +// 使用 localStorage 保存token,达到 [永久Cookie] 的效果 +localStorage.setItem("satoken", "xxxx-xxxx-xxxx-xxxx-xxx"); + +// 使用 sessionStorage 保存token,达到 [临时Cookie] 的效果 +sessionStorage.setItem("satoken", "xxxx-xxxx-xxxx-xxxx-xxx"); +``` + +Remember me, it's too easy!