前言:

  上一篇提出, 通过修改 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. C++标准库第二版笔记 3 和异常的理解 1

    C++标准库第二版笔记 3 和异常的理解 1 差错和异常(error and exception)的处理 标准异常类(exception class) 定义于 分为: 1.语言本身支持的异常 2.标准 ...

  2. Python Day 13 装饰器

    阅读目录   内容回顾 函数嵌套的定义 global.nonlocal关键字 闭包及闭包的运用场景 开放封闭原则 装饰器 一个函数被多次装饰 ##内容回顾 1.函数对象:函数名 => 存放的是函 ...

  3. 02.02.01 第1章 简介及基础操作(Power BI商业智能分析)

    02.02.01.01 powerbi简介 00:10:59 02.02.01.02 query数据导入 00:03:26 具体操作实例如下: 02.02.01.03导入access数据 00:05: ...

  4. javascript生成器

    next()方法 如果给next方法传参数, 那么这个参数将会作为上一次yield语句的返回值 ,这个特性在异步处理中是非常重要的, 因为在执行异步代码以后, 有时候需要上一个异步的结果, 作为下次异 ...

  5. 压力测试工具 Apache_jmeter软件配置+TCP示例说明

    该软件jmeter是Apache官方开源压力测试软件.  jmeter官网:http://jmeter.apache.org/  . 本文使用的版本是 3.0版本, 它需要jdk7及以上版本支持. 网 ...

  6. git撤销commit-hard

    场景: 不小心commit了一个不应该commit的修改,但是还没有push,想撤销那个commit 命令: a)git log b)git reset --hard commit_id 具体步骤如下 ...

  7. python time库

    https://www.cnblogs.com/tkqasn/p/6001134.html

  8. 谈谈websocket集群的解决方式

    上文我们已经利用websocket实现微信二维码支付的业务. 上述实现在单机环境中实现是没有什么问题的,无非就是客户端连接服务端,首先将连接的websocketsession存在一个map里面,当异步 ...

  9. python之路(七)-递归算法

    递归 特点 递归算法是一种直接或者间接地调用自身算法的过程.在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解. 递归算法解决问题的特点: (1) 递归就是在 ...

  10. 处理Word文档中所有修订

    打开现有文档进行编辑 若要打开现有文档,您可以将 Word类实例化,如以下 using 语句所示. 为此,您可以使用Open(String, Boolean) 方法打开具有指定 fileName 的字 ...