上一篇提出, 經過修改 rememberMe 的編碼來實現 rememberMe的功能的設想, 過後我去嘗試實現了一番, 發現太麻煩, 仍是不要那麼作吧. 程序仍是要越簡單越好.java
那功能老是要實現的啊, 總不能由於麻煩, 就把功能給砍了吧. web
so, 換條路試試:spring
在先後端項目中, app和後臺之間的登陸, 並不能經過cookie來維持, 有一種常使用的技術: jwt, 這個技術, 其實就是經過在請求頭或者參數中加入一個參數 token (這個token是通過jwt加密的)的方式, 來實現登陸認證的.具體的原理並不討論. jwt加密的時候, 是能夠加入時間限制的. apache
在shiro裏面, 若是我將 rememberMe 也進行jwt加密, 而後再賦值回rememberMe , 給出去. 當我從請求頭中拿到rememberMe , 而後再解密成以前的數據, 不就能夠了麼.json
import ccdc.zykt.model.vo.UserExt; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.impl.DefaultClaims; import org.apache.commons.lang.time.DateUtils; import org.apache.shiro.codec.Base64; import java.util.Date; public class JWTUtil { private static final String KEY = Base64.encodeToString("jwt.key".getBytes()); public static String createJWT(String token) { Date now = new Date(); return Jwts.builder() .setSubject(token) .setIssuedAt(now) .setExpiration(DateUtils.addMinutes(now, 1)) .signWith(SignatureAlgorithm.HS512, KEY).compact(); } public static String createJWT(String token, int amount){ Date now = new Date(); return Jwts.builder().setSubject(token).setIssuedAt(now).setExpiration(DateUtils.addHours(now, amount)).signWith(SignatureAlgorithm.HS512, KEY).compact(); } public static boolean validate(String jwt){ try { Jwts.parser().setSigningKey(KEY).parse(jwt); return true; } catch (Throwable t) { return false; } } public static String validateJWT(String jwt) { try { Jwt parse = Jwts.parser().setSigningKey(KEY).parse(jwt); DefaultClaims body = (DefaultClaims) parse.getBody(); String phone = body.getSubject(); return phone; } catch (Throwable t) { return null; } } }
此處我將過時時間設置爲1分鐘, 在實際使用中, 能夠將這個參數變成可配置的, 到時候根據實際須要, 將時間改長一些就能夠了.後端
package ccdc.zykt.web.shiro.headtoken; import ccdc.zykt.web.util.JWTUtil; import com.google.common.base.Strings; import org.apache.commons.compress.utils.ByteUtils; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.codec.Base64; import org.apache.shiro.mgt.AbstractRememberMeManager; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.SubjectContext; import org.apache.shiro.web.servlet.ShiroHttpServletRequest; import org.apache.shiro.web.subject.WebSubjectContext; import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.StringUtils; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; /** * 將remember放到響應頭中去, 而後從請求頭中解析 * @author: elvin * @time: 2018-07-05 15:11 * @desc: **/ public class HeaderRememberMeManager extends AbstractRememberMeManager { private static final transient Logger log = LoggerFactory.getLogger(HeaderRememberMeManager.class); // header 中 固定使用的 key public static final String DEFAULT_REMEMBER_ME_HEADER_NAME = "remember-me"; @Override protected void rememberSerializedIdentity(Subject subject, byte[] serialized) { if (!WebUtils.isHttp(subject)) { if (log.isDebugEnabled()) { 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."; log.debug(msg); } } else { HttpServletResponse response = WebUtils.getHttpResponse(subject); String base64 = Base64.encodeToString(serialized); base64 = JWTUtil.createJWT(base64); // 設置 rememberMe 信息到 response header 中 response.setHeader(DEFAULT_REMEMBER_ME_HEADER_NAME, base64); } } private boolean isIdentityRemoved(WebSubjectContext subjectContext) { ServletRequest request = subjectContext.resolveServletRequest(); if (request == null) { return false; } else { Boolean removed = (Boolean) request.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY); return removed != null && removed; } } @Override protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) { if (!WebUtils.isHttp(subjectContext)) { if (log.isDebugEnabled()) { 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."; log.debug(msg); } return null; } else { WebSubjectContext wsc = (WebSubjectContext) subjectContext; if (this.isIdentityRemoved(wsc)) { return null; } else { HttpServletRequest request = WebUtils.getHttpRequest(wsc); // 在request header 中獲取 rememberMe信息 String base64 = request.getHeader(DEFAULT_REMEMBER_ME_HEADER_NAME); if ("deleteMe".equals(base64)) { return null; } else if (base64 != null) { base64 = JWTUtil.validateJWT(base64); if(Strings.isNullOrEmpty(base64)){ return null; } base64 = this.ensurePadding(base64); if (log.isTraceEnabled()) { log.trace("Acquired Base64 encoded identity [" + base64 + "]"); } byte[] decoded = Base64.decode(base64); if (log.isTraceEnabled()) { log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes."); } return decoded; } else { return null; } } } } private String ensurePadding(String base64) { int length = base64.length(); if (length % 4 != 0) { StringBuilder sb = new StringBuilder(base64); for (int i = 0; i < length % 4; ++i) { sb.append('='); } base64 = sb.toString(); } return base64; } @Override protected void forgetIdentity(Subject subject) { if (WebUtils.isHttp(subject)) { HttpServletRequest request = WebUtils.getHttpRequest(subject); HttpServletResponse response = WebUtils.getHttpResponse(subject); this.forgetIdentity(request, response); } } @Override public void forgetIdentity(SubjectContext subjectContext) { if (WebUtils.isHttp(subjectContext)) { HttpServletRequest request = WebUtils.getHttpRequest(subjectContext); HttpServletResponse response = WebUtils.getHttpResponse(subjectContext); this.forgetIdentity(request, response); } } private void forgetIdentity(HttpServletRequest request, HttpServletResponse response) { //設置刪除標示 response.setHeader(DEFAULT_REMEMBER_ME_HEADER_NAME, "deleteMe"); } }
rememberMe 在解析的時候, 就會將時間算進去, 若是超時了, 解析會返回false. 這樣, 就能夠爲rememberMe設置一個超時時間cookie
試驗證實, 仍是可行的.app
進行jwt處理以後, rememberMe字符串會比較長, 這裏提供一種思路:ide
1. 將過時時間轉換成 yyyy-MM-dd HH:mm:ss 格式的時間字符串, 進行 base64編碼, 會發現, 長度都是28位的. 假設定義爲變量 timeBase64post
2. 在 rememberSerializedIdentity 方法中, 對 timeBase64 進行截取, 分爲兩段, 拼接到 上面 base64字符串中去, 這中方式就至關因而進行了簡單的加密, 固然, 不進行此操做, 也徹底能夠, 直接拼接到 base64的開始處, 或者結尾處.
3. 在 getRememberedSerializedIdentity 方法中, 對base64字符串進行截取, 能夠獲得時間字符串, 以後對時間進行判斷, 就能知道, 是否過時.