认证与鉴权

1、Token的作用及原理

Token,即令牌,是服务器产生的,具有随机性和不可预测性,它主要有两个作用:

(1) 防止表单的重复提交:

1.防止页面提交表当重复提交的简单步骤:

​ (1,使用form 表单)

  1. 在服务器端生成 唯一的随机标识号,Token,同时在当前session中保存(真正的项目中要考虑存在缓存中,redis enchae 缓存)
  2. 将token发送至客户端,在form表单中来隐藏存储这个Token,表单提交时候连带着Token一起提交道服务器端
  3. 在服务器端进行验证提交过来的token是否是一致的,如果不一致、或前端为空或者后端为空,那么就是重复提交,如果相同清楚session中的重复的标识号

2、具体代码(jsp):

FromServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FormServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String token = TokenUtil.getInstance().makeToken();//创建令牌
System.out.println("在FormServlet中生成的token:"+token);
request.getSession().setAttribute("token", token); //在服务器使用session保存token(令牌)
request.getRequestDispatcher("/Form.jsp").forward(request, response);//跳转到form.jsp页面
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
}

Form.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>Form.jsp</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<form action="DoFormServlet" method="post">
<%--使用隐藏域存储生成的token--%>
<%--
<input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
--%>
<%--使用EL表达式取出存储在session中的token--%>
<input type="hidden" name="token" value="${token}" /> 用户名:<input
type="text" name="username"> <input type="submit" value="提交">
</form>
</body>
</html>

TokenUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import sun.misc.BASE64Encoder;

public class TokenUtil {


/*
*单例设计模式(保证类的对象在内存中只有一个)
*1、把类的构造函数私有
*2、自己创建一个类的对象
*3、对外提供一个公共的方法,返回类的对象
*/
private TokenUtil(){
}

private static final TokenUtil instance = new TokenUtil();

/**
* 返回类的对象
* @return
*/
public static TokenUtil getInstance(){
return instance;
}

/**
* 生成Token
* Token:Nv6RRuGEVvmGjB+jimI/gw==
* @return
*/
public String makeToken(){

String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
//数据指纹 128位长 16个字节 md5
try {
MessageDigest md = MessageDigest.getInstance("md5");
//对于给定数量的更新数据,digest 方法只能被调用一次。digest 方法被调用后,MessageDigest对象被重新设置成其初始状态。
byte md5[] = md.digest(token.getBytes());
//base64编码--任意二进制编码明文字符 adfsdfsdfsf
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(md5);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

DoFormServlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DoFormServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
boolean isRepeat = isRepeatSubmit(request);//判断用户是否是重复提交
if(isRepeat){
request.setAttribute("MSG", "请不要重复提交");
System.out.println("请不要重复提交");
request.getRequestDispatcher("/Msg.jsp").forward(request, response);
return;
}
request.getSession().removeAttribute("token");//移除session中的token
request.setAttribute("MSG","处理用户提交请求!!");
request.getRequestDispatcher("/Msg.jsp").forward(request, response);
System.out.println("处理用户提交请求!!");
}

/**
* 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
* @param request
* @return
* true 用户重复提交了表单
* false 用户没有重复提交表单
*/
private boolean isRepeatSubmit(HttpServletRequest request) {
String client_token = request.getParameter("token");
//1、如果用户提交的表单数据中没有token,则用户是重复提交了表单
if(client_token==null){
return true;
}
//取出存储在Session中的token
String server_token = (String) request.getSession().getAttribute("token");
//2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单
if(server_token==null){
return true;
}
//3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单
if(!client_token.equals(server_token)){
return true;
}
return false;
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}

Msg.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>result show</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>${MSG}</body>
</html>

(2) 用于身份认证(Cookie、Session和Token认证):

​ 前言: HTTP是一种无状态的协议,与服务其进行连接及断开,为了辨别这个请求是谁进行发起的,需要浏览器自己去解决,用户通过浏览器登录一个网站,在该浏览器器页面,不许要进行重新登录。而http是无状态的协议,那么网站后端是如何进行判断用户已经登录?

详情:(3条消息) Cookie、Session和Token认证_谢公子的博客-CSDN博客_cookie认证

(3条消息) Token ,Cookie和Session的区别–学习笔记_CharliChen ‘s Blog-CSDN博客

基于Token 认证和session 认证的比较 - 想飞_毛毛虫 - 博客园 (cnblogs.com)

两者的区别:

1.使用session 进行存储

服务端生成用户相关的 session 数据, 发给客户端 sesssion_id 存放到 cookie ,客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数据,

缺点: 服务端需要存储session ,会增加维护成本和减弱可扩展性

2. 使用token进行存储

image-20210705165439311

  • 客户端使用用户名跟密码请求登录
  • 服务端收到请求,去验证用户名与密码
  • 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
  • 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
  • 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  • 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

3、 access_token和refresh_token双令牌无感知登录

1.1.1 access_token

access_token 由后端颁发给前台,一般采用对称加密算法,可以反向解析出参数信息,如:用户ID,失效时间等
场景:
每次前端访问后端API接口,header中会带上此token,后端拦截器检验此token是否失效,当前请求用户ID等
JWT token: https://www.jianshu.com/p/b56fd5b24636

1.1.2 refresh_token

​ 它的作用就是避免让用户重复输入账号密码登录再次验证

1.1.3 结合使用:
                             一般我们可以将access_token的过期时间设置为2小时的,refresh_token的过期时间设置为1个月,然后用户第一次进来,用了一段时间access_token过期了,过期后前端携带refresh_token去获取新的access_token,返回的新的access_token依旧是2小时,那么除此之外,refresh_token自身再刷新一次,刷新一次后他还是1个月的过期时间(不累加),这就保证了用户在一个月内只要访问了应用,就可以享受无感知的体验.
1.1.4 安全性:
  1. access_token 泄露概率比较大,毕竟每次api请求都附带它,如果单独把 access_token 过期时间设置过长,一旦泄露,就相当于密码泄露
  2. refresh_token 泄露概率比较小,只有每次access_token 失效时才会使用它,所以二者组合可以很好的避免token泄露带来的安全风险,同时又能保证用户体验


4、JWT令牌:

1.jwt令牌是什么
  1. 1.1全称 JSON Web Token,是目前最流行的跨域身份验证解决方案
2.jwt数据结构:
  1. token分为三部分,header.payload.signatrue. 号分隔
  • header 头文件
  • payload 有效荷载
  • signatrue 签名文件
3.header信息:
  1. 如:签名使用的算法HS256,typ属性表示令牌的类型

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}
  • payload有效荷载
  1. jwt默认提供了以下7个属性设置,也可以自定义属性字段(类似Map中设置)

1
2
3
4
5
6
7
8
默认参数:
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
4.签名哈希:
  1. 签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。

1
2
3
4
5
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload)
var signature = HMACSHA256(encodedString, 'secret')
最终解果 : token = base64UrlEncode(header)
+ '.' + base64UrlEncode(payload)
+ '.' + signature
5、代码测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.Test;

import java.util.Base64;
import java.util.Date;

public class JwtTest {


/**
* signature底层原理
* <p>
* var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload)<p>
* var signature = HMACSHA256(encodedString, 'secret')<p>
* 最终解果 : token = base64UrlEncode(header) + '.' + base64UrlEncode(payload) + '.' + signature<p>
* demo: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiIwMDciLCJzdWIiOiJzaHVzbSIsImV4cCI6MTU1NDYyMzc2MiwibmJmIjoxNTU0NzA2NTYyLCJpYXQiOjE1NTQ2MjAxNjIsImlzcyI6Imp1bml0In0.GHyxmrcXzxnAHN8ib2209I48wUpIU1Pyr_-sGDucoMM<p>
* <p>
* <p>
* payload 建议使用参数:<p>
* iat jwt的签发时间<p>
* exp jwt的过期时间,这个过期时间必须要大于签发时间<p>
* nbf 定义在什么时间之前,该jwt都是不可用的<p>
*/
@Test
public void testJwt() {
String secret = "loveqq";// 密钥

String token = Jwts.builder().claim("userId", "007")
.setSubject("shusm")
// exp 有效期设置 - 1h失效
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
// .setExpiration(new Date(System.currentTimeMillis())) // 立刻超时
// nbf 明天才生效
// .setNotBefore(new DateTime().plusDays(1).toDate())
.setIssuedAt(new Date())
.setIssuer("junit")
// 签名设置
.signWith(SignatureAlgorithm.HS256, secret)
.compact();

System.out.println("加密后access_token为:" + token);


// token分为三部分,header.payload.signatrue , 前2个部分可以直接用 base64 反向解析得出结果
String[] split = token.split("\\.");
String header = new String(Base64.getDecoder().decode(split[0].getBytes()));
System.out.println("header:" + header);
String payload = new String(Base64.getDecoder().decode(split[1].getBytes()));
System.out.println("payLoad:" + payload);

// token检验
Claims body = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();

System.out.println("------------JWT解析结果--------------");
body.forEach((k, v) -> {
System.out.println("K:" + k + "\t v:" + v);
});
}
}

二、基于JWT的token认证实现(验证登录)(spring):

基本思路

①用户首次登录,将输入的账号和密码提交给服务器;

②服务器对输入内容进行校验,若账号和密码匹配则验证通过,登录成功,并生成一个token值,将其保存到数据库,并返回给客户端;

③客户端拿到返回的token值将其保存在本地(如cookie/localStorage),作为公共参数,以后每次请求服务器时都携带该token(放在响应头里),提交给服务器进行校验;

④服务器接收到请求后,首先验证是否携带token,若携带则取出请求头里的token值与数据库存储的token进行匹配校验,若token值相同则登录成功,且当前正处于登录状态,此时正常返回数据,让app显示数据;若不存在或两个值不一致,则说明原来的登录已经失效,此时返回错误状态码,提示用户跳转至登录界面重新登录;

⑤注意:用户每进行一次登录,登录成功后服务器都会更新一个token新值返回给客户端;

img

简单实现:
引入java-jwt依赖
1
2
3
4
5
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.3.0</version>
</dependency>
签名工具 JwtUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtil {
/**
* 过期时间一天,
* TODO 正式运行时修改为15分钟
*/
private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000;
/**
* token私钥
*/
private static final String TOKEN_SECRET = "f26e587c28064d0e855e72c0a6a0e618";
/**
* 校验token是否正确
*
* @param token 密钥
* @return 是否正确
*/
public static boolean verify(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm)
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}

/**
* 生成签名,15min后过期
*
* @param username 用户名
* @return 加密的token
*/
public static String sign(String username,String userId) {
try {
@Autowired
private IUserService userService;

/**
* 登陆接口
*
* @return token
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public ApiResponse login(@RequestBody Map<String, String> map) {
String loginName = map.get("loginName");
String password = map.get("password");
//身份验证是否成功
boolean isSuccess = userService.checkUser(loginName, password);
if (isSuccess) {
User user = userService.getUserByLoginName(loginName);
if (user != null) {
//返回token
String token = JwtUtil.sign(user.getName(), user.getId());
if (token != null) {
return ApiResponseUtil.getApiResponse(token);
}
}
}
//返回登陆失败消息
return ApiResponseUtil.getApiResponse(ApiResponseEnum.LOGIN_FAIL);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
配置拦截器TokenInterceptor.java

import com.alibaba.fastjson.JSONObject;
import com.joe.entity.ApiResponse;
import com.joe.enums.ApiResponseEnum;
import com.joe.util.ApiResponseUtil;
import com.joe.util.JwtUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

public class TokenInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setCharacterEncoding("utf-8");
String token = request.getHeader("access_token");
//token不存在
if (null != token) {
//验证token是否正确
boolean result = JwtUtil.verify(token);
if (result) {
return true;
}
}
ApiResponse apiResponse = ApiResponseUtil.getApiResponse(ApiResponseEnum.AUTH_ERROR);
responseMessage(response,response.getWriter(),apiResponse);
return false;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}

/**
* 返回信息给客户端
*
* @param response
* @param out
* @param apiResponse
*/
private void responseMessage(HttpServletResponse response, PrintWriter out, ApiResponse apiResponse) {
response.setContentType("application/json; charset=utf-8");
out.print(JSONObject.toJSONString(apiResponse));
out.flush();
out.close();
}

}
spring-mvc.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--此文件负责整个mvc中的配置-->
<!--启用spring的一些annotation -->
<context:annotation-config/>
<!-- 配置注解驱动 可以将request参数与绑定到controller参数上 -->
<mvc:annotation-driven/>

<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/login/"/>
<bean class="com.joe.interceptor.TokenInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>

<!-- 自动扫描装配 -->
<context:component-scan base-package="com.joe"/>
</beans>
转载

(3条消息) Token的作用及原理_0945v1-CSDN博客

(3条消息) Java实现基于token认证_程序员阿坤的博客-CSDN博客

小程序登录基本思路

转载:

​ 1、(3条消息) 面试官问我:如何设计 QQ、微信等第三方账号登陆 ?还要我说出数据库表设计!…_GitHubDaily-CSDN博客

​ 2、微信小程序-getUserInfo回调的实例详解_JavaScript_脚本之家 (jb51.net)

​ 3、(3条消息) 微信小程序前段+java后端实现登陆功能(超详细)_最菜的黑客的博客-CSDN博客_java后端实现微信小程序登录

​ 4、(3条消息) 微信小程序登录的后端Java详细实现_Lu、ck的博客-CSDN博客_微信小程序的后端是如何实现的

小程序实现3rd_session登录:

img

1、小程序客户端代码表现:

(3条消息) 微信小程序之微信登陆 —— 微信小程序教程系列(20)_michael的博客-CSDN博客_微信小程序登陆

2、小程序Java端:

​    按照微信的建议此时需要生成一个不重复值作为openId的唯一性标识。这里采用的是java的uuid。然后把这个uuid值作为key,把openid以及后面会用到的session_key作为value,存进redis。并且把uuid值返回给小程序。这样小程序就可以直接拿uuid值跟服务端交互。

    也许会有人问,如果有人得到uuid值其实跟得到openid没什么区别啊,都相当于是会员的唯一性标志。

    所以这里要对这个uuid值进行一个处理。首先存入redis时要有时效性。session_key在微信服务器有效期是30天,建议服务端缓存session_key不超过30天。当小程序传过来的uuid值过期时,认为这是过期的uuid,则重新走wx.login步骤。

    为了方便redis中不仅会寸uuid与openid的对应关系。还会再存一条openid对应uuid的记录,目的是为了下一次重新wx.login步骤时根据openid找到之前老的uuid,如果存在的话就删掉,然后查询一条新的uuid值,并且把openid对应的这条记录也更新掉。这样redis服务器中就不会有多余的脏数据,减轻服务器的负担。

采用jfinal框架
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  public Ret userInfo(String code) throws WxErrorException, SQLException {
WxMaService wxMaService = new WxMaServiceImpl();
WxMaDefaultConfigImpl wxMaDefaultConfigImpl = new WxMaDefaultConfigImpl();
wxMaDefaultConfigImpl.setAppid(WxaConfig.appid); //小程序appId
wxMaDefaultConfigImpl.setSecret(WxaConfig.secret); //小程序secret
wxMaService.setWxMaConfig(wxMaDefaultConfigImpl);

WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(code);
WxMaSecCheckService session1 = wxMaService.getSecCheckService();
UserThirdAuth userThirdAuth = getUserIdByWx(session.getOpenid());
if (userThirdAuth == null) {
return Ret.by("msg", "用户没有权限").set("ok", "1");
}
JSONObject deptCode = staffService.findByUserId(userThirdAuth.getUserId());
if (deptCode == null) {
return Ret.by("msg", "用户没有对应的权限").set("ok", "1");
}
String openId = session.getOpenid();

// 得到sessionkey之后进行缓存,key值采用不会重复的uuid
String rsessionKey = UUID.randomUUID().toString();

LOG.debug("rsession = " + rsessionKey);
// 首先根据openId,取出来之前存的openId对应的sessionKey的值。
String oldSessionKey = CacheKit.get("logical", openId);
LOG.debug("oldSessionKey = " + oldSessionKey);
if (oldSessionKey != null && "".equals(oldSessionKey)) {
// 删除之前openId对应的sessionKey
CacheKit.remove("logical", oldSessionKey);
LOG.debug("老的缓存删除之后" + CacheKit.get("logical",oldSessionKey).toString());
}
// 开始缓存新的sessionKey: key --> uuid, value --> wexSession
JSONObject wexSession = new JSONObject();
wexSession.put("openid", openId);
wexSession.put("sessionKey", session.getSessionKey());

CacheKit.put("logical",rsessionKey,wexSession);
// 开始缓存 openid 和 uuid 对应关系
CacheKit.put("logical",openId,rsessionKey);
LOG.debug("缓存之后"+CacheKit.get("logical",rsessionKey).toString() );
return Ret.create().set("user", deptCode).set("sessionKey",rsessionKey);

}

转载:

java实现微信小程序登录态维护 - 知乎 (zhihu.com)

微信小程序登录状态java后台解密 - xxlfly - 博客园 (cnblogs.com)

解决jwt失效

img

服务器不存jwt会出一些问题的,jwt在签发的时候带有权限和账户状态这些动态信息,服务器不能全相信这些信息。比如管理员已经把张三的某个权限下掉了或者把账号冻结了,但是张三客户端jwt中还是有这个权限和状态,还是可以访问已经没有权限的资源。如果在使用jwt的情况下每次请求还去找数据库或者缓存做鉴权,jwt就失去意义了,既然状态还是存在服务器上要查询,为什么不用加密用户名而用jwt呢。

解决一般都是在jwt生成后在redis中存一份,有效期设置为jwt的有效期,当用户的状态发生变动时删除redis中对应用户的jwt。这样每次做认证和鉴权时只要确认redis中储存了客户端发送来jwt(reids的haskey使用布隆过滤器,性能非常好),如果存在可以直接信任这个jwt。如果在认证和鉴权时发现redis中没有这个jwt,说明账号的状态发生了变化,强制要求客户端重新获取jwt即可。