前言:

  上一篇提出, 通过修改 rememberMe 的编码来实现 rememberMe的功能的设想, 事后我去尝试实现了一番, 发现太麻烦, 还是不要那么做吧. 程序还是要越简单越好.

  那功能总是要实现的啊, 总不能因为麻烦, 就把功能给砍了吧.

  so, 换条路试试:

  在前后端项目中, app和后台之间的登录, 并不能通过cookie来维持, 有一种常使用的技术: jwt, 这个技术, 其实就是通过在请求头或者参数中加入一个参数 token (这个token是经过jwt加密的)的方式, 来实现登录认证的.具体的原理并不讨论. jwt加密的时候, 是可以加入时间限制的.

  在shiro里面, 如果我将 rememberMe 也进行jwt加密, 然后再赋值回rememberMe , 给出去. 当我从请求头中拿到rememberMe , 然后再解密成之前的数据, 不就可以了么.

实现:

一. jwt帮助类

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分钟, 在实际使用中, 可以将这个参数变成可配置的,  到时候根据实际需要, 将时间改长一些就可以了.

二. 改写HeaderRememberMeManager类

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设置一个超时时间

结果展示:

1. 登录之后, 使用postmen 尝试访问

2. 耐心等待1分钟, 然后再去访问这个接口试试

试验证明, 还是可行的.

进行jwt处理之后, rememberMe字符串会比较长, 这里提供一种思路:

1. 将过期时间转换成  yyyy-MM-dd HH:mm:ss 格式的时间字符串, 进行 base64编码, 会发现, 长度都是28位的. 假设定义为变量 timeBase64

2. 在 rememberSerializedIdentity 方法中, 对 timeBase64 进行截取, 分为两段, 拼接到 上面 base64字符串中去, 这中方式就相当于是进行了简单的加密, 当然, 不进行此操作, 也完全可以, 直接拼接到 base64的开始处, 或者结尾处.

3. 在  getRememberedSerializedIdentity 方法中, 对base64字符串进行截取, 可以得到时间字符串, 之后对时间进行判断, 就能知道, 是否过期.

shiro + jwt 实现 请求头中的 rememberMe 时间限制功能的更多相关文章

  1. shiro 获取请求头中的 rememberMe

    前言: 上一篇提到了, 将 sessionId 放到请求头中去, 那rememberMe是否也可以放到请求头中去呢. 其实不管是sessionId还是rememberMe, shiro都会默认往coo ...

  2. shiro 获取请求头中的 sessionId

    前言: 在前后端项目中, 前端有可能会要求, 后台返回一个 sessionId 给他, 然后他在请求后台接口时, 把这个sessionId 带给后台, 后台拿到这个sessionId , 就能识别, ...

  3. 使用zuul实现验证自定义请求头中的token

    路由:她会把外部所有对请求转发到具体的微服务实例上,是实现外部访问同一接口的基础 过滤: 就是权限的检查, 判断当前的请求是否有权限区访问那些服务集群 搭建后台网关: 导入eureka - clien ...

  4. Http 请求头中的 Proxy-Connection

    平时用 Chrome 开发者工具抓包时,经常会见到 Proxy-Connection 这个请求头.之前一直没去了解什么情况下会产生它,也没去了解它有什么含义.最近看完<HTTP 权威指南> ...

  5. js 中ajax请求时设置 http请求头中的x-requestd-with= ajax

    今天发现 AngularJS 框架的$http服务提供的$http.get() /$http.post()的ajax请求中没有带 x-requested-with字段. 这样的话,后端的php 就无法 ...

  6. HTTP 请求头中的 Remote_Addr,X-Forwarded-For,X-Real-IP

    REMOTE_ADDR 表示发出请求的远程主机的 IP 地址,remote_addr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时,假设中间 ...

  7. Ajax 请求头中常见content-type

    四种常见的 POST 提交数据方式 HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层规范.规范把 HTTP 请求分为三个部分:状态行.请求头.消息主体.协议规定 POST ...

  8. WebAPi获取请求头中对应键值

    /// <summary> /// 依据键获取请求头中值数据 /// </summary> /// <param name="request"> ...

  9. HTTP 请求头中的 X-Forwarded-For(转)

    原文:https://imququ.com/post/x-forwarded-for-header-in-http.html 我一直认为,对于从事 Web 前端开发的同学来说,HTTP 协议以及其他常 ...

随机推荐

  1. Codeforces Round #539 (Div. 2) C Sasha and a Bit of Relax

    题中意思显而易见,即求满足al⊕al+1⊕…⊕amid=amid+1⊕amid+2⊕…⊕ar且l到r的区间长为偶数的这样的数对(l,r)的个数. 若al⊕al+1⊕…⊕amid=amid+1⊕amid ...

  2. 利用IO和File类实现拷贝文件目录问题

    /* 复制文件夹 参数 File src,File dest */ public static void copy(File src,File dest){ if (src.isDirectory() ...

  3. WEB服务器搭建(Apache+Tomcat+eclipse)

    1.下载xampp安装,选择Apache+MySQL+Tomcat 官方下载链接:https://www.apachefriends.org/zh_cn/download.html 2.下载安装jav ...

  4. python PyInstaller 库

    https://www.cnblogs.com/gopythoner/p/6337543.html https://www.cnblogs.com/duan-qs/p/6548875.html htt ...

  5. [转]dd命令、cp命令详解+dd命令、cp命令对比 ---delong

    出处:http://blog.csdn.net/sun_app/article/details/18263299 1.dd命令详解 1)中文man手册dd的解释 NAME       dd - 转换和 ...

  6. 使用tcpdump探测TCP/IP三次握手

    读计算机应该就同说过TCP/IP三次握手,但是都没有去验证过,今天心血来潮,去验证了一下,于是乎写下了这篇博客,可能写的可能有问题,还请多多指教 包括我学习,还有从很多资料来看资料,第三次握手,应该会 ...

  7. numpy 与 matplotlib 的应用

    numpy 与 matplotlib 的应用 一.库函数介绍 1. numpy库 NumPy(Numeric Python)提供了一个N维的数组类型ndarray,Numpy底层使用C语言编写,内部解 ...

  8. GC算法基础

    寻找垃圾对象的算法:1. 引用计数(无法处理循环引用) 2. 根寻法(被广泛引用在gc算法中) 清理垃圾的算法: 1. 标记复制  2. 标记清理  3. 标记整理 分代算法的好处: 1. 分代处理, ...

  9. 10-Mock模拟接口返回数据

    1.安装mock 方法一:pip安装 命令行直接输入:pip install mock 方法二:官网下载mock安装包安装 下载安装包后,解压,命令行进入解压目录,执行python setup.py ...

  10. Android基础知识学习

    IPC  (Inter-Process Communication) 意思是: 进程间的通信,是指两个进程之间进行数据交换的过程. Android中如何开启多进程呢? 只需要给四大组件(Activit ...