Created
June 14, 2020 07:40
-
-
Save greenyleaf/b5a49f5f5459686a8daf69407efc1ff7 to your computer and use it in GitHub Desktop.
customized Apache Shiro, Remember Me Manager code, with spring-mybatis JDBC based persistent login, references some posts on Stackoverflow.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package top.sdrkyj.custom.shiro; | |
| import top.sdrkyj.custom.entity.Account; | |
| import top.sdrkyj.custom.entity.PersistentLogin; | |
| import top.sdrkyj.custom.service.PersistentLoginService; | |
| import org.apache.shiro.crypto.RandomNumberGenerator; | |
| import org.apache.shiro.crypto.SecureRandomNumberGenerator; | |
| import org.apache.shiro.crypto.hash.Sha512Hash; | |
| import org.apache.shiro.subject.PrincipalCollection; | |
| import org.apache.shiro.subject.SimplePrincipalCollection; | |
| import org.apache.shiro.subject.Subject; | |
| import org.apache.shiro.subject.SubjectContext; | |
| import org.apache.shiro.util.ByteSource; | |
| import org.apache.shiro.web.mgt.CookieRememberMeManager; | |
| import org.apache.shiro.web.servlet.Cookie; | |
| import org.apache.shiro.web.servlet.ShiroHttpServletRequest; | |
| import org.apache.shiro.web.servlet.SimpleCookie; | |
| import org.apache.shiro.web.subject.WebSubjectContext; | |
| import org.apache.shiro.web.util.RequestPairSource; | |
| import org.apache.shiro.web.util.WebUtils; | |
| import org.slf4j.Logger; | |
| import org.slf4j.LoggerFactory; | |
| import org.springframework.stereotype.Component; | |
| import javax.servlet.ServletRequest; | |
| import javax.servlet.http.HttpServletRequest; | |
| import javax.servlet.http.HttpServletResponse; | |
| import java.text.MessageFormat; | |
| import java.util.Base64; | |
| @Component("rememberMeManager") | |
| public class CustomRemeberMeManager extends CookieRememberMeManager { | |
| private final static Logger logger = LoggerFactory.getLogger(CustomRemeberMeManager.class); | |
| private final static RandomNumberGenerator rng = new SecureRandomNumberGenerator(); | |
| private final PersistentLoginService persistentLoginService; | |
| public CustomRemeberMeManager(PersistentLoginService persistentLoginService) { | |
| this.persistentLoginService = persistentLoginService; | |
| getCookie().setName("remember_me"); | |
| } | |
| @Override | |
| public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) { | |
| //修改, 根据cookie 值的token, 对应用户id, 查询用户信息。 | |
| logger.info("getRememberedPrincipals entered"); | |
| //PrincipalCollection principals = null; | |
| //try { | |
| String value = getRememberedCookieValue(subjectContext); | |
| //SHIRO-138 - only call convertBytesToPrincipals if bytes exist: | |
| if (value != null && value.length() > 0) { | |
| int pos = value.indexOf(':'); | |
| if (pos <= 0 || pos >= value.length() - 1) { | |
| onRememberedPrincipalFailure(new RuntimeException("无效的客户端持久化登录内容,不包含 ':' 分隔符。"), subjectContext); | |
| } | |
| String series = value.substring(0, pos); | |
| //... dao, read by cookie value ; | |
| Account byToken; | |
| PersistentLogin persistentLogin = persistentLoginService.findBySeries(series); | |
| if (persistentLogin != null) { | |
| //比较 token 解 base4 编码后的散列值 | |
| String clientToken = value.substring(pos + 1); | |
| byte[] clientTokenBytes = null; | |
| try { | |
| clientTokenBytes = Base64.getDecoder().decode(clientToken); | |
| } catch (IllegalArgumentException ex) { | |
| onRememberedPrincipalFailure(new RuntimeException("无效的客户端持久化登录内容,series 不是有效的 base64 编码。", ex), subjectContext); | |
| } | |
| if (!persistentLogin.getToken().equals(new Sha512Hash(clientTokenBytes).toBase64())) { | |
| onRememberedPrincipalFailure(new RuntimeException("无效的客户端持久化登录内容, 客户端 token 不匹配存储的的 token 。"), subjectContext); | |
| } | |
| byToken = persistentLogin.getAccount(); | |
| SimplePrincipalCollection spc = new SimplePrincipalCollection(); | |
| spc.add(byToken.getId(), "tokenRealm"); | |
| spc.add(byToken, "tokenRealm"); | |
| // 生成新的 token ,持久存储散列值;按照 series, 更新持久登录表。 | |
| //更新客户端 token | |
| ByteSource byteSource2 = rng.nextBytes(80); | |
| String token = byteSource2.toBase64(); | |
| if (persistentLoginService.update(series, new Sha512Hash(byteSource2.getBytes()).toBase64())) { | |
| //rememberSerializedIdentity(subject, byteSource.getBytes()); | |
| spc.add(series, "series"); | |
| //logger.debug("subjectContext instanceof RequestPairSource, {}", subjectContext instanceof RequestPairSource); | |
| //logger.debug("subjectContext.getClass(), {}", subjectContext.getClass()); | |
| rememberToken(subjectContext, MessageFormat.format("{0}:{1}", series, token)); | |
| } | |
| return spc; | |
| } | |
| } | |
| return null; | |
| } | |
| private boolean isIdentityRemoved(WebSubjectContext subjectContext) { | |
| ServletRequest request = subjectContext.resolveServletRequest(); | |
| if (request != null) { | |
| Boolean removed = (Boolean) request.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY); | |
| return removed != null && removed; | |
| } | |
| return false; | |
| } | |
| protected String getRememberedCookieValue(SubjectContext subjectContext) { | |
| if (!WebUtils.isHttp(subjectContext)) { | |
| String msg = "SubjectContext argument is not an HTTP-aware instance. This is required to obtain a " + | |
| "servlet request and response in order to retrieve the rememberMe cookie. Returning " + | |
| "immediately and ignoring rememberMe operation."; | |
| logger.debug(msg); | |
| return null; | |
| } | |
| WebSubjectContext wsc = (WebSubjectContext) subjectContext; | |
| if (isIdentityRemoved(wsc)) { | |
| return null; | |
| } | |
| HttpServletRequest request = WebUtils.getHttpRequest(wsc); | |
| HttpServletResponse response = WebUtils.getHttpResponse(wsc); | |
| String cookieValue = getCookie().readValue(request, response); | |
| // Browsers do not always remove cookies immediately (SHIRO-183) | |
| // ignore cookies that are scheduled for removal | |
| if (Cookie.DELETED_COOKIE_VALUE.equals(cookieValue)) return null; | |
| //may be null, new visitor | |
| return cookieValue; | |
| } | |
| @Override | |
| protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) { | |
| // 生成cookie token, 保存token和用户id的对应关系。增加对token的管理 | |
| String accountId = (String) accountPrincipals.getPrimaryPrincipal(); | |
| ByteSource byteSource1 = rng.nextBytes(32); | |
| ByteSource byteSource2 = rng.nextBytes(80); | |
| String series = byteSource1.toBase64(); | |
| String token = byteSource2.toBase64(); | |
| if (persistentLoginService.create(accountId, series, new Sha512Hash(byteSource2.getBytes()).toBase64())) { | |
| ((SimplePrincipalCollection) accountPrincipals).add(token, "series"); | |
| rememberToken(subject, MessageFormat.format("{0}:{1}", series, token)); | |
| } | |
| } | |
| protected void rememberToken(SubjectContext subjectContext, String token) { | |
| if (!WebUtils.isHttp(subjectContext)) { | |
| String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " + | |
| "request and response in order to set the rememberMe cookie. Returning immediately and " + | |
| "ignoring rememberMe operation."; | |
| logger.debug(msg); | |
| return; | |
| } | |
| rememberToken3((RequestPairSource) subjectContext, token); | |
| } | |
| protected void rememberToken(Subject subject, String token) { | |
| if (!WebUtils.isHttp(subject)) { | |
| String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " + | |
| "request and response in order to set the rememberMe cookie. Returning immediately and " + | |
| "ignoring rememberMe operation."; | |
| logger.debug(msg); | |
| return; | |
| } | |
| rememberToken3((RequestPairSource) subject, token); | |
| } | |
| protected void rememberToken3(RequestPairSource subject, String token) { | |
| if (!WebUtils.isHttp(subject)) { | |
| String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " + | |
| "request and response in order to set the rememberMe cookie. Returning immediately and " + | |
| "ignoring rememberMe operation."; | |
| logger.debug(msg); | |
| return; | |
| } | |
| HttpServletRequest request = WebUtils.getHttpRequest(subject); | |
| HttpServletResponse response = WebUtils.getHttpResponse(subject); | |
| //base 64 encode it and store as a cookie: | |
| Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies | |
| Cookie cookie = new SimpleCookie(template); | |
| cookie.setValue(token); | |
| cookie.saveTo(request, response); | |
| } | |
| @Override | |
| protected void forgetIdentity(Subject subject) { | |
| //退出之后,被调用; 或者自动登录后,在登录验证时勾选自动登录 | |
| logger.debug("forgetIdentity entered"); | |
| if (WebUtils.isHttp(subject)) { | |
| forgetIdentity3((RequestPairSource) subject); | |
| } | |
| } | |
| @Override | |
| public void forgetIdentity(SubjectContext subjectContext) { | |
| if (WebUtils.isHttp(subjectContext)) { | |
| forgetIdentity3((RequestPairSource) subjectContext); | |
| } | |
| } | |
| private void forgetIdentity3(RequestPairSource source) { | |
| //退出之后,被调用; 或者自动登录后,在登录验证时勾选自动登录 | |
| logger.debug("forgetIdentity entered"); | |
| if (WebUtils.isHttp(source)) { | |
| HttpServletRequest request = WebUtils.getHttpRequest(source); | |
| HttpServletResponse response = WebUtils.getHttpResponse(source); | |
| Cookie cookie = getCookie(); | |
| String value = cookie.readValue(request, response); | |
| //提取 series | |
| cookie.removeFrom(request, response); | |
| if (value != null) { | |
| int pos = value.indexOf(':'); | |
| if (pos > 0 && pos < value.length() - 1) { | |
| String series = value.substring(0, pos); | |
| logger.debug("persistent login, {}, {}", value, series); | |
| persistentLoginService.removeBySeries(series); | |
| } | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment