shiro + jwt 实现 请求头中的 rememberMe 时间限制功能
前言:
上一篇提出, 通过修改 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 时间限制功能的更多相关文章
- shiro 获取请求头中的 rememberMe
前言: 上一篇提到了, 将 sessionId 放到请求头中去, 那rememberMe是否也可以放到请求头中去呢. 其实不管是sessionId还是rememberMe, shiro都会默认往coo ...
- shiro 获取请求头中的 sessionId
前言: 在前后端项目中, 前端有可能会要求, 后台返回一个 sessionId 给他, 然后他在请求后台接口时, 把这个sessionId 带给后台, 后台拿到这个sessionId , 就能识别, ...
- 使用zuul实现验证自定义请求头中的token
路由:她会把外部所有对请求转发到具体的微服务实例上,是实现外部访问同一接口的基础 过滤: 就是权限的检查, 判断当前的请求是否有权限区访问那些服务集群 搭建后台网关: 导入eureka - clien ...
- Http 请求头中的 Proxy-Connection
平时用 Chrome 开发者工具抓包时,经常会见到 Proxy-Connection 这个请求头.之前一直没去了解什么情况下会产生它,也没去了解它有什么含义.最近看完<HTTP 权威指南> ...
- js 中ajax请求时设置 http请求头中的x-requestd-with= ajax
今天发现 AngularJS 框架的$http服务提供的$http.get() /$http.post()的ajax请求中没有带 x-requested-with字段. 这样的话,后端的php 就无法 ...
- HTTP 请求头中的 Remote_Addr,X-Forwarded-For,X-Real-IP
REMOTE_ADDR 表示发出请求的远程主机的 IP 地址,remote_addr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时,假设中间 ...
- Ajax 请求头中常见content-type
四种常见的 POST 提交数据方式 HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层规范.规范把 HTTP 请求分为三个部分:状态行.请求头.消息主体.协议规定 POST ...
- WebAPi获取请求头中对应键值
/// <summary> /// 依据键获取请求头中值数据 /// </summary> /// <param name="request"> ...
- HTTP 请求头中的 X-Forwarded-For(转)
原文:https://imququ.com/post/x-forwarded-for-header-in-http.html 我一直认为,对于从事 Web 前端开发的同学来说,HTTP 协议以及其他常 ...
随机推荐
- idea快捷键(后续更新)
自动补全当前行的标点符号 ctrl + shirt + 回车 跳到下一行 shirt +回车 复制一行 crtl + d 删除一行 ctrl + y 提示报错 alt + 回车 查看当前可以产什么参数 ...
- ajax用户是否存在
$(function(){ $("#username").blur(function(){ var usernameInput=$(this).val(); $.post( &qu ...
- mysql thread pool
转自:http://blog.csdn.net/wyzxg/article/details/8258033 mysql 线程处理流程图: Mysql支持单线程和多线程两种连接线程模式,如果单线程,则在 ...
- python之路(五)-文件操作
文件操作无非两个,即:读.写 python 2.x: 文件句柄 = file('文件路径', '模式') python3.x: 文件句柄 = open('文件路径', '模式') 打开文件的模式有: ...
- Ubuntu14.04下中文输入法拼音不正常问题 输入nihao会变成niha o
1. 打开输入法首选项,选择拼音模式,选择全拼 2. 在终端中输入ibus-daemon –drx
- hightopo自己用开源的方案重构一遍
经过一年多的学习吧前面路上的坑基本算踩过一遍了 所以下面计划吧hightopo网站上的demo用自己的方式重新写一遍
- 《python语言程序设计》_第二章笔记
#2.2_编写一个简单的程序 项目1: 设计:radius=20,求面积area? 程序: radius=20 #给变量radius复制area=radius*radius*3.14159 #编写ar ...
- Python基础理论 - 函数
函数是第一类对象:可以当做数据来传 1. 可以被引用 2. 可以作为函数参数 3. 可以作为函数返回值 4. 可以作为容器类型的元素 小例子: def func1(): print('func ...
- DelphiXE10.2.3——跨平台生成验证码图片
$("#img-code").bind( 'click', function () { $(this).attr('src','VerifyCode?t='+Math.random ...
- c# 使用资源文件
1.新建项目 2.新建资源文件 3. 代码中使用嵌入资源 using System;using System.Collections.Generic;using System.Text;using S ...