1.搭建测试环境

引入maven
pom.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 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 79 80 81 82 83 84 85 86 87 88 89 90 91
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.1</version> <relativePath/> </parent> <groupId>com.easysession</groupId> <artifactId>easysession</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <skipTests>true</skipTests> <springboot.version>2.6.1</springboot.version> <logback.version>1.2.10</logback.version> <jwt.version>3.2.0</jwt.version> <fastjson.version>1.2.66</fastjson.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </exclusion> <exclusion> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>${springboot.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>${logback.version}</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>${jwt.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.2.6.RELEASE</version> <configuration> <mainClass>com.reconciliation.RunApplication</mainClass> </configuration> </plugin> </plugins> </build> </project>
|
引入logback-spring.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?xml version="1.0" encoding="UTF-8" ?> <configuration scan="true" scanPeriod="10 minutes"> <appender name="stdot" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>%d{yyyy-MM-dd HH:mm:ss,GMT+8} [%p][%c][%M][%L]-> %m%n</pattern> </layout> </appender> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>easysession.log</file> <encoder> <charset>utf-8</charset> <pattern>%d{yyyy-MM-dd HH:mm:ss,GMT+8} [%p][%c][%M][%L]-> %m%n</pattern> </encoder> <append>false</append> <prudent>false</prudent> </appender> <root level="info"> <appender-ref ref="stdot"/> <appender-ref ref="file"/> </root> </configuration>
|
配置文件
application.properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| server.port=5050
server.servlet.session.timeout=PT60M
spring.mvc.throw-exception-if-no-handler-found=true spring.web.resources.add-mappings=false
spring.redis.database=0 spring.redis.host=127.0.0.1 spring.redis.port=6379
spring.redis.jedis.pool.max-active=20
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=0
spring.redis.timeout=2000
|
TestController
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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| package com.easysession.controller;
import com.easysession.jwt.JWTUtil; import com.easysession.redis.RedisUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.util.UUID;
@RestController public class TestController {
private static final String SESSION_KEY="session_key";
private static final String JWT_KEY ="jwt_key";
@Resource private RedisUtils redisUtils;
@Resource private JWTUtil<String> jwtUtil;
private boolean isEmpty(String value){ if(null==value||"".equals(value.trim())){ return true; } return false; }
@RequestMapping("saveSessionInfo") public String saveSessionInfo(HttpSession session,String msg){ if(isEmpty(msg)){ return "msg不能为空"; } session.setAttribute(SESSION_KEY,msg); return "保存session信息成功,sessionId:"+session.getId(); }
@RequestMapping("getSessionInfo") public String getSessionInfo(HttpSession session){ return "获取的session信息为:"+session.getAttribute(SESSION_KEY); }
@RequestMapping("saveByToken") public String saveByToken(String msg){ if(isEmpty(msg)){ return "msg不能为空"; } String token = UUID.randomUUID().toString(); redisUtils.set(token,msg); return "保存token信息成功,token:"+token; }
@RequestMapping("getByToken") public String getByToken(String token){ if(isEmpty(token)){ return "token不能为空"; } return "获取的token信息为:"+redisUtils.get(token); }
@RequestMapping("saveByTokenWithCookie") public String saveByTokenWithCookie(HttpServletResponse response,String msg){ if(isEmpty(msg)){ return "msg不能为空"; } String token = UUID.randomUUID().toString(); redisUtils.set(token,msg); Cookie cookie = new Cookie("token",token); response.addCookie(cookie); return "保存token信息成功,token:"+token; }
@RequestMapping("getByTokenWithCookie") public String getByTokenWithCookie(HttpServletRequest request){ Cookie[] cookies = request.getCookies(); if(cookies==null){ return "获取的token信息为:null"; } String token = null; for (Cookie cookie:cookies){ if("token".equals(cookie.getName())){ token = cookie.getValue(); break; } } if(isEmpty(token)){ return "token不能为空"; } return "获取的token信息为:"+redisUtils.get(token); }
@RequestMapping("saveByJwt") public String saveByJwt(String msg){ if(isEmpty(msg)){ return "msg不能为空"; } String token =jwtUtil.createToken(JWT_KEY,msg,10000); return "保存token信息成功,token:"+token; }
@RequestMapping("getByJwt") public String getByJwt(String token){ if(isEmpty(token)){ return "token不能为空"; } String msg =jwtUtil.getTokenData(JWT_KEY,token,String.class); return "获取jwt的信息是"+msg; } }
|
JsonUtils
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
| package com.easysession.jwt;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import java.util.List;
public class JsonUtils { private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);
public static String convertObj2Json(Object obj) { return JSON.toJSONString(obj); }
public static <T> T convertJson2Obj(String json, Class<T> classz) { try { return JSONObject.parseObject(json, classz); } catch (Exception e) { logger.error("convertJson2Obj异常,json:{}", json); throw new RuntimeException("数据转换异常"); } }
public static <T> List<T> convertJsonArray2List(String json, Class<T> classz) { try { return JSONArray.parseArray(json, classz); } catch (Exception e) { logger.error("convertJsonArray2List,json:{}", json, e); throw new RuntimeException("数据转换异常"); } } }
|
JWTUtil
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
| package com.easysession.jwt;
import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import org.springframework.stereotype.Component;
import java.util.Date;
@Component("jwtUtil") public class JWTUtil<T> {
private static final String SECRET = "test123456";
public String createToken(String key, T data, Integer expireSeconds) { String token = null; try { Date expiresAt = new Date(System.currentTimeMillis() + expireSeconds * 1000); token = JWT.create() .withClaim(key, JsonUtils.convertObj2Json(data)) .withExpiresAt(expiresAt) .sign(Algorithm.HMAC256(SECRET)); } catch (Exception e) { e.printStackTrace(); } return token; }
public <T> T getTokenData(String key, String token, Class<T> classz) { try { if (null == token || "".equals(token)) { return null; } JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build(); DecodedJWT jwt = verifier.verify(token); String jsonData = jwt.getClaim(key).asString(); return JsonUtils.convertJson2Obj(jsonData, classz); } catch (Exception e) { return null; } } }
|
RedisConfig
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
| package com.easysession.redis;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration("redisConfig") public class RedisConfig<V> {
@Bean public RedisTemplate<String, V> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, V> template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(RedisSerializer.string()); template.setValueSerializer(RedisSerializer.json()); template.setHashKeySerializer(RedisSerializer.string()); template.setHashValueSerializer(RedisSerializer.json()); template.afterPropertiesSet(); return template; } }
|
RedisUtils
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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| package com.easysession.redis;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils;
import javax.annotation.Resource; import java.util.Collection; import java.util.concurrent.TimeUnit;
@Component("redisUtils") public class RedisUtils<V> {
@Resource private RedisTemplate<String, V> redisTemplate;
private static final Logger logger = LoggerFactory.getLogger(RedisUtils.class);
public void delete(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key)); } } }
public V get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); }
public boolean set(String key, V value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { logger.error("设置redisKey:{},value:{}失败", key, value); return false; } }
public boolean setex(String key, V value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { logger.error("设置redisKey:{},value:{}失败", key, value); return false; } }
public long increment(String key, long delta, long time) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } long result = redisTemplate.opsForValue().increment(key, delta); if (result == 1) { expire(key, time); } return result; }
public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } }
|
这部分内容介绍了 Session、JWT 和 Token 的使用及区别,以下是详细的讲解:
1. Session
概念:
Session 是服务器用来存储用户信息的一种方式。当用户登录时,服务器会为每个用户创建一个 Session,并将用户的相关信息保存在服务器端。客户端通过 Session ID 来访问该信息。
工作原理:
- Session ID:客户端通过 Cookie 或 URL 参数将 Session ID 发送给服务器,服务器根据该 ID 查找并返回对应的 Session 信息。
- 存储方式:Session 通常存储在服务器的内存中(如 Redis)或者数据库中,避免了每次请求都需要传输大量的数据。
优缺点:
-
优点
:
- 安全性高,因为 Session 数据存储在服务器端。
- 不易受到客户端篡改。
-
缺点
:
- 每次访问服务器时需要携带 Session ID,可能会增加请求的负担。
- 在负载均衡和分布式架构中,Session 数据需要共享,可能涉及到复杂的存储解决方案。
2. JWT (JSON Web Token)
概念:
JWT 是一种用于客户端和服务器之间传递信息的紧凑、安全的 JSON 格式。它由三部分组成:
- Header(头部):包含了令牌的元数据,如签名算法。
- Payload(负载):包含了用户信息和其他数据。
- Signature(签名):用于验证令牌的有效性和防止篡改。
工作原理:
- 生成:服务器生成 JWT 后,客户端会收到该令牌,通常会将其存储在浏览器的 LocalStorage 或 Cookie 中。
- 使用:每次请求时,客户端将 JWT 放在 HTTP 请求的
Authorization 头中发送给服务器,服务器验证签名后,获取请求的用户信息。
- 无状态:JWT 是 无状态的,服务器不需要存储会话信息,减轻了服务器负担。
优缺点:
-
优点
:
- 无状态,减轻了服务器存储压力。
- 可以跨域使用,适合微服务架构。
-
缺点
:
- 一旦签名泄露,攻击者可以伪造 JWT,影响安全性。
- JWT 的有效期和签名一旦生成,就不能改变,若需要更新用户信息,需要重新生成新的 JWT。
3. Token
概念:
Token 是一个通用的认证方式,用于代替传统的基于 Session 的认证。与 Session 和 JWT 类似,Token 也是用于验证用户身份的一种方法,但与 Session 不同的是,它通常是短期有效的,且可以包含更多的信息。
工作原理:
- 生成 Token:客户端登录时,服务器通过 用户名 和 密码 验证用户身份,并生成 Token。
- 存储 Token:客户端将 Token 存储在 LocalStorage 或 Cookie 中,每次请求时将其发送到服务器进行验证。
- 验证 Token:服务器验证 Token 的有效性并解析出用户信息。
优缺点:
-
优点
:
- 简化了服务器管理,减少了对服务器端存储的依赖。
- 可以方便地实现跨域认证。
-
缺点
:
- 需要确保 Token 的传输过程是安全的(例如通过 HTTPS),否则容易被劫持。
- 一旦泄露,攻击者可以直接获取 Token,可能带来安全风险。
Session、JWT 和 Token 的比较
| 特性 |
Session |
JWT |
Token |
| 存储方式 |
服务器端存储(内存或数据库) |
客户端存储(如 LocalStorage 或 Cookie) |
客户端存储(如 LocalStorage 或 Cookie) |
| 认证方式 |
基于 Session ID |
基于 JWT Token |
基于 Token |
| 状态性 |
有状态,服务器需要存储 Session 数据 |
无状态,数据包含在 JWT 中 |
无状态,数据包含在 Token 中 |
| 跨域问题 |
需要共享 Session 数据(复杂的配置) |
可轻松实现跨域认证 |
同 JWT,支持跨域认证 |
| 安全性 |
需要保护 Session ID 和会话数据 |
需要保护 Token 和签名 |
与 JWT 相似,保护 Token 防止泄漏 |
总结:
- Session:适合需要状态管理和数据存储的场景,但在分布式系统中可能会增加管理复杂性。
- JWT:适合无状态的分布式认证,简化了服务器的负担,适用于微服务架构,但需要特别注意安全性。
- Token:是一个更通用的认证方案,功能与 JWT 类似,通常与 JWT 一起使用,但需要保护 Token 的有效性和安全性。
这些身份验证机制各有优缺点,选择适合的机制取决于应用的具体需求,如性能要求、分布式架构的复杂度、安全性等。
这段代码展示了一个 Spring Boot 项目中的 TestController 类,涉及到 Session、Token(包括通过 Redis 存储的普通 Token)和 JWT 的使用。下面我将详细讲解代码的作用和每个部分的功能:
1. TestController
TestController 类提供了一些接口,演示如何使用 Session、Token 和 JWT 来保存和获取用户信息。它包含以下几个核心方法:
2. 主要方法解释
saveSessionInfo
1 2 3 4 5 6 7 8
| @RequestMapping("saveSessionInfo") public String saveSessionInfo(HttpSession session, String msg) { if (isEmpty(msg)) { return "msg不能为空"; } session.setAttribute(SESSION_KEY, msg); return "保存session信息成功,sessionId:" + session.getId(); }
|
- 功能:此方法将用户提供的信息(
msg)存储在 Session 中。Session 是服务器端保存的用户会话数据。
- 用法:通过
session.setAttribute(SESSION_KEY, msg) 将 msg 保存到 Session 中。
- 返回值:返回成功信息和当前的
sessionId。
getSessionInfo
1 2 3 4
| @RequestMapping("getSessionInfo") public String getSessionInfo(HttpSession session) { return "获取的session信息为:" + session.getAttribute(SESSION_KEY); }
|
- 功能:此方法从 Session 中获取之前保存的
msg 信息。
- 用法:通过
session.getAttribute(SESSION_KEY) 获取存储在 Session 中的信息。
saveByToken
1 2 3 4 5 6 7 8 9
| @RequestMapping("saveByToken") public String saveByToken(String msg) { if (isEmpty(msg)) { return "msg不能为空"; } String token = UUID.randomUUID().toString(); redisUtils.set(token, msg); return "保存token信息成功,token:" + token; }
|
- 功能:将
msg 信息保存到 Redis 中,并生成一个随机的 Token(UUID),将其与 msg 一起存储。
- 用法:通过 Redis 工具类
redisUtils.set(token, msg) 将 Token 和消息一起存入 Redis。
- 返回值:返回成功信息和生成的 Token。
getByToken
1 2 3 4 5 6 7
| @RequestMapping("getByToken") public String getByToken(String token) { if (isEmpty(token)) { return "token不能为空"; } return "获取的token信息为:" + redisUtils.get(token); }
|
- 功能:通过传入 Token 从 Redis 中获取对应的消息
msg。
- 用法:通过
redisUtils.get(token) 获取保存在 Redis 中与 Token 关联的 msg。
- 返回值:返回获取到的信息。
saveByTokenWithCookie
1 2 3 4 5 6 7 8 9 10 11
| @RequestMapping("saveByTokenWithCookie") public String saveByTokenWithCookie(HttpServletResponse response, String msg) { if (isEmpty(msg)) { return "msg不能为空"; } String token = UUID.randomUUID().toString(); redisUtils.set(token, msg); Cookie cookie = new Cookie("token", token); response.addCookie(cookie); return "保存token信息成功,token:" + token; }
|
- 功能:与
saveByToken 类似,但这里将 Token 信息存入浏览器的 Cookie 中。这样可以在后续请求中通过 Cookie 自动携带 Token 信息。
- 用法:通过
response.addCookie(cookie) 将生成的 Token 保存到客户端的 Cookie 中。
getByTokenWithCookie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @RequestMapping("getByTokenWithCookie") public String getByTokenWithCookie(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies == null) { return "获取的token信息为:null"; } String token = null; for (Cookie cookie : cookies) { if ("token".equals(cookie.getName())) { token = cookie.getValue(); break; } } if (isEmpty(token)) { return "token不能为空"; } return "获取的token信息为:" + redisUtils.get(token); }
|
- 功能:从请求中获取 Cookie 中的 Token,然后使用 Redis 获取与该 Token 相关联的消息。
- 用法:通过
request.getCookies() 获取请求中的 Cookie,并获取 Token。
saveByJwt
1 2 3 4 5 6 7 8
| @RequestMapping("saveByJwt") public String saveByJwt(String msg) { if (isEmpty(msg)) { return "msg不能为空"; } String token = jwtUtil.createToken(JWT_KEY, msg, 10000); return "保存token信息成功,token:" + token; }
|
- 功能:生成一个 JWT 并将
msg 数据保存在 JWT 中,返回 JWT Token。
- 用法:通过
jwtUtil.createToken(JWT_KEY, msg, 10000) 创建一个 JWT,其中 10000 表示该 Token 的有效期(单位为秒)。
- 返回值:返回成功信息和生成的 JWT。
getByJwt
1 2 3 4 5 6 7 8
| @RequestMapping("getByJwt") public String getByJwt(String token) { if (isEmpty(token)) { return "token不能为空"; } String msg = jwtUtil.getTokenData(JWT_KEY, token, String.class); return "获取jwt的信息是" + msg; }
|
- 功能:验证并解析传入的 JWT,获取其中的数据(
msg)。
- 用法:通过
jwtUtil.getTokenData(JWT_KEY, token, String.class) 获取 JWT 中保存的数据。
3. 总结
- Session:用来存储用户会话信息,保存在服务器端,通过
sessionId 来标识每个用户会话。
- Token:通常用来进行无状态认证。通过生成一个唯一的字符串(如 UUID),并将其存储在 Redis 中。Token 可以通过 Cookie 或请求头传递。
- JWT:一种基于 Token 的认证方式,但它具有内嵌的签名,保证数据不被篡改,并支持跨域认证。JWT 的好处是无状态、跨平台,且支持一定的有效期。
在这段代码中,TestController 展示了如何使用 Session、Token 和 JWT 来管理用户的身份信息。RedisUtils 用于与 Redis 进行交互,JWTUtil 则是用来生成和验证 JWT Token 的工具类。