Redis概述及其在Session数据存储中的应用

发表时间: 2023-12-06 21:14
  1. 代码下载

本文章所有代码可从Github下载。地址:GitHub - YuRui1113/session-redis: Store session data to Redis and How to verify it.

  1. Redis简介

Redis 是一种开源(BSD 许可)内存中数据结构存储,用作数据库、缓存、消息代理和流引擎。 Redis 提供数据结构,例如字符串、哈希、列表、集合、带有范围查询的排序集、位图、超级日志、地理空间索引和流。 Redis 具有内置复制、Lua 脚本、LRU 驱逐、事务和不同级别的磁盘持久性,并通过 Redis Sentinel 和 Redis Cluster 自动分区提供高可用性。

您可以对这些类型进行原子操作,例如附加到字符串、增加哈希值、将一个元素插入列表,计算集合的交、并、差,或获取排序集中排名最高的成员。

为了实现最佳性能,Redis 使用内存数据集。根据您的使用案例,Redis可以通过定期将数据集转储到磁盘或将每个命令附加到基于磁盘的日志来持久保存数据。如果您只需要功能丰富的网络内存缓存,您还可以禁用持久性。

Redis 支持异步复制,具有快速非阻塞同步和自动重新连接以及网络分割时部分重新同步的功能。

Redis同样包括:

  • 事务
  • 发布/订阅
  • Lua脚本
  • 可限定TTL(生存时间)的键
  • LRU(Least Recently Used,即最近最久未使用)回收键
  • 自动故障恢复

您可以通过大多数编程语言使用 Redis。

Redis 采用 ANSI C 编写,适用于大多数POSIX系统,如Linux、*BSD 和 Mac OS X,无需外部依赖。Linux和OS X是Redis开发和测试最多的两个操作系统,我们建议使用Linux进行部署。Redis官方没有对Windows版本的支持,不建议在windows下使用Redis,所以官网没有 windows 版本可以下载。但是微软团队维护了开源的windows版本,只有 3.2 版本,可用于普通测试。本篇使用Windows上的Ubuntu虚拟机安装Redis。

  1. Windows 11上安装Ubuntu 22.04.3虚拟机

安装虚拟机可参考我之前的文章Windows11下使用Hyper-V创建Ubuntu 22.04.3虚拟机。按此文章步骤即可准备好一台Ubuntu虚拟机,并且和主机是可以互连互通的,这样使主机上的应用可以访问后续在Ubuntu上安装的Redis。

  1. 在Ubuntu虚拟机上安装Redis

在 Ubuntu 上打开命令行,并输入以下命令来安装Redis:

$ curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg$ echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list$ sudo apt-get update$ sudo apt-get install redis
  1. 配置Redis

安装完成后,修改Redis配置文件。 为此,请使用您选择的文本编辑器打开文件:

$ sudo vi /etc/redis/redis.conf
  • 绑定IP

绑定当前机器IP‘192.168.137.40’到Redis(注意当前机器IP可能为其它,可以使用命令ip a或ifconfig查看当前机器IP):

  • 禁用保护模式

在保护模式下,只能连接当前机器上的Redis。

  • 指定受监督指令

默认情况下,受监督指令设置为 no。 但是,要将 Redis 作为服务进行管理,请将受监督指令设置为 systemd(Ubuntu 的 init 系统)。

保存redis.conf然后退出。

  • 设置Redis服务开机自启动

使用如下命令来设置Redis开机自启动:

$ sudo systemctl enable redis-server.service

  • 启动Redis服务

每当您对 redis.conf 文件进行任何更改时,请重新加载或重新启动 Redis 服务。

$ sudo systemctl restart redis-server.service $ sudo systemctl status redis-server.service 

如果服务未启动,则检查 Redis 日志:
/var/log/redis/redis-server.log

$ sudo tail /var/log/redis/redis-server.log

另外,可以不通过服务启动 Redis 服务器,如下所示:

$ sudo service redis-server start
  • 验证Redis版本

可以使用如下命令验证Redis版本:

$ redis-server --version

  • 配置防火墙

使用如下命令打开防火墙端口6379,6379是Redis默认使用端口。

$ sudo ufw allow 6379
  1. VS Code Redis插件

在 Windows 上,我们可以安装 Visual Studio Code 扩展来测试与 Redis 的连接以及访问Redis数据库。

安装后,您可以打开它并测试连接,如下所示:

以及访问Redis数据库

  1. 使用Redis CLI访问Redis
  • 连接Redis

您可以通过连接 Redis CLI 来测试 Redis 服务器是否正在运行:

$ redis-cli127.0.0.1:6379> pingPONG

可以指定不同的主机名或者IP,使用-h参数。还可以指定不同的端口,使用-p参数。

$ redis-cli -h redis15.localnet.org -p 6390 PINGPONG
  • 写入数据

SET key value [NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]

创建一个新的键值对:

SET framework angular
  • 读取数据

GET key

读取对应键的值:

GET framework
  • 更新数据

SET命令可用来覆盖同样键的数据:

SET framework react
  • 获取对应模式的键

KEYS pattern

可以用以下命令获取所有的键:

KEYS *
  • 非破坏性写入

Redis 为我们提供了一个名为 SETNX 的 SET 的非破坏性版本:

SETNX key value

当且仅当密钥尚不存在时,SETNX 会在内存中创建键值对。如果键已存在,Redis 会回复 0 表示存储键值对失败,回复 1 表示成功。Removing data

  • 删除数据

DEL key

使用DEL命令来删除数据:

DEL framework
  • 删除所有数据

FLUSHALL 命令可以从 Redis 中删除所有数据。

  • 键过期设置

使用 Redis 创建密钥时,我们可以指定该密钥应在内存中存储多长时间。使用 EXPIRE 命令,我们可以在密钥上设置超时,并在超时到期后自动删除密钥:

EXPIRE key seconds

以下命令将创建一个要在 30 秒后删除的键值对:

SET notification "Anomaly detected"EXPIRE notification 30

Redis 提供了 TTL 命令,该命令告诉我们密钥在过期和删除之前还剩下多少秒:

TTL key

从 Redis 2.8 开始,TTL 返回:

剩余超时(以秒为单位)。

-2 如果密钥不存在(尚未创建或已删除)。

-1 如果密钥存在但未设置到期时间。

使用 SET 与再次创建密钥相同,所以对于Redis来说,这还涉及重置当前分配给它的任何超时。

  1. 使用Redis CLI访问Redis复杂数据结构 - Lists

List是一系列有序元素。例如,1 2 4 5 6 90 19 3 是数字列表。在 Redis 中,需要注意的是,列表是作为链表实现的。这对性能有一些重要影响。将元素添加到列表的头部和尾部是快速的,但在列表中搜索元素的速度较慢,因为我们没有对元素的索引访问权限(就像我们在数组中所做的那样)。

  • RPUSH - 将一个或多个元素追加到列表尾。

RPUSH key element [element ...]

创建一个键为engineers的 List:

RPUSH engineers "Alice"// 1RPUSH engineers "Bob"// 2RPUSH engineers "Carmen"// 3

每次我们插入元素时,Redis 都会在插入后回复 List 的长度。

  • LRANGE - 根据指定的开始和停止索引返回 List 的子集

LRANGE key start stop

要查看完整的列表,我们可以使用一个巧妙的技巧:从 0 转到它前面的元素 -1。

LRANGE engineers 0 -1Redis 返回:1) "Alice"2) "Bob"3) "Carmen"

索引 -1 将始终表示 List 中的最后一个元素。

  • LPUSH - 将一个或多个元素添加到列表头

LPUSH key element [element ...]

将 Daniel 插入工程师列表的前面:

LPUSH engineers "Daniel"// 4

现在有四名工程师。让我们验证顺序是否正确:

LRANGE engineers 0 -1Redis返回:1) "Daniel"2) "Alice"3) "Bob"4) "Carmen"
  • 添加多个元素

在RPUSH 和 LPUSH 的命令格式中看到,我们可以在每个命令中插入多个元素。

根据我们现有的工程师列表,运行以下命令:

RPUSH engineers "Eve" "Francis" "Gary"// 7

验证元素添加到列表中:

同样可以使用LPUSH命令:

LPUSH engineers "Hugo" "Ivan" "Jess"// 10

验证:

LRANGE 0 -1

Redis返回:

  • LLEN – 返回列表长度

LLEN key

使用如下命令测试engineers列表长度:

LLEN engineers

Redis返回 (integer) 10。

  • LPOP - 删除并返回 List 的第一个元素

LPOP key [count]

使用此命令从列表engineers中删除第一个元素 "Jess":

LPOP engineers

Redis返回 "Jess"。

  • RPOP - 删除并返回 List 的最后一个元素

RPOP key [count]

删除最后一个元素 "Gary":

RPOP engineers

Redis返回 "Gary".

  1. 使用Redis CLI访问Redis复杂数据结构 - Sets

在 Redis 中,Set 类似于 List,只是它不为其元素保留任何特定顺序,并且每个元素必须是唯一的。

  • SADD - 将一个或多个成员添加到集合中

SADD key member [member ...]

创建键为languages的set:

SADD languages "english"// 1SADD languages "spanish"// 1SADD languages "french"// 1

在每次添加成员时,Redis 将返回使用 SADD 命令添加的成员数,而不是 Set 的大小。下面命令将添加多个元素:

SADD languages "chinese" "japanese" "german"// 3

添加已存在元素:

SADD languages "english"// 0
  • SREM – 删除一个或多个元素

SREM key member [member ...]

可以同时删除一个或多个元素:

SREM languages "english" "french"// 2SREM languages "german"// 0

SREM返回删除元素个数。

  • SISMEMBER - 确定成员是否属于Set

SISMEMBER key member

如果成员是 Set的一部分,则此命令返回 1;否则,它将返回 0:

SISMEMBER languages "spanish"// 1SISMEMBER languages "german"// 0
  • SMEMBERS – 返回Set所有元素

SMEMBERS key

下面命令返回键为languages的set里所有元素:

SMEMBERS languages

Redis 返回:

由于 Set 是无序的,因此 Redis 可以在每次调用时以任何顺序自由返回元素。他们无法保证元素的顺序。

  • SUNION – 合并多个Set

SUNION key [key ...]

SUNION 的每个参数都代表一个 Set,我们可以将其合并到一个更大的 Set 中。请务必注意,任何重复的成员都将列出一次。

创建一个键为ancient-languages的Set:

SADD ancient-languages "greek"SADD ancient-languages "latin"SADD ancient-languages "chinese"SMEMBERS ancient-languages

使用下面命令合并Set:

SUNION languages ancient-languages

Redis返回:

  1. 使用Redis CLI访问Redis复杂数据结构 - Hashes

在 Redis 中,Hash 是一种数据结构,用于将字符串键与字段值对进行映射。因此,哈希可用于表示对象。它们的键是哈希的名称,值表示字段名称字段值条目的序列。我们可以这样描述计算机对象:

computer name "MacBook Pro" year 2015 disk 512 ram 16

对象的属性定义为对象名称 computer 之后的“属性名称”和“属性值”序列。

  • HSET - 创建或修改Hash中字段的值

HSET key field value [field value ...]

如果字段已存在,则修改字段值。

下面命令创建键为computer的hash:

HSET computer name "MacBook Pro"// 1HSET computer year 2015// 1HSET computer disk 512// 1HSET computer ram 16// 1

对于每个 HSET 命令,Redis 都会回复一个整数,如下所示:

1 如果字段是哈希中的新字段并且设置了值。

0 如果哈希中已存在字段并且值已更新。

修改字段值:

HSET computer year 2018// 0
  • HGET – 获取字段值

HGET key field

获取year字段值:

HGET computer year

Redis返回 "2018"。

  • HGETALL – 返回所有字段和值

HGETALL key

下面命令返回键为computer的Hash所有字段和值:

返回:

  • HMSET – 设置多个字段值

HMSET key field value [field value ...]

下面命令创建键为tablet的hash:

HMSET tablet name "iPad" year 2016 disk 64 ram 4
  • HMGET – 获取多个指定字段值

HMGET key field [field ...]

下面命令返回disk和ram字段值:

HMGET tablet disk ram

Redis返回:

  1. 使用Redis CLI访问Redis复杂数据结构 - Sorted Sets

在 Redis 1.2 中引入的有序集合本质上是一个集合:它包含唯一的、不重复的字符串成员。但是,虽然 Set 的成员没有排序(Redis 可以在每次调用 Set 时以任何顺序自由返回元素),但 Sorted Set 的每个成员都链接到一个称为 score 的浮点值,Redis 使用该值来确定 Sorted Set 成员的顺序。由于排序集的每个元素都映射到一个值,因此它还具有类似于 Hash 的体系结构。

与有序集合交互的一些命令类似于我们用于普通集合的命令:替换 Set 命令中的 S 并将其替换为 Z。例如,SADD => ZADD。但是,也有两者独有的命令。

  • ZADD - 将一个或多个成员添加到排序集合中,或更新其score

ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]

创建一个键为tickets的有序集:

ZADD tickets 100 HELP204// 1ZADD tickets 90 HELP004// 1ZADD tickets 180 HELP330// 1

ZADD返回添加元素数量。

  • ZRANGE – 返回指定索引区间的元素

ZRANGE key start stop [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]

使用0到-1索引区间可获取所有元素:

ZRANGE tickets 0 -1Redis返回:1) "HELP004"2) "HELP204"3) "HELP330"

可以使用WITHSCORES参数来同时返回score:

ZRANGE tickets 0 -1 WITHSCORESRedis返回:1) "HELP004"2) "90"3) "HELP204"4) "100"5) "HELP330"6) "180"

可以看到元素是按score升序返回的.

  1. 使用Java Redis客户端 – Jedis

Redis 在其官方网站上列出了最知名的客户端库。Jedis有多种替代品,但目前只有两种推荐的明星产品:lettuce和Redisson。

这两个客户端确实有一些独特的功能,比如线程安全、透明的重新连接处理和异步 API,这些都是 Jedis 所缺乏的。

然而,Jedis 很小,比其他两个快得多。此外,它是 Spring Framework 开发人员选择的客户端库,并且拥有这三个库中最大的社区。

下面我们通过创建Junit test用例来展示如何使用Jedis API。

  • Maven依赖

为使用Jedis,我们只需在pom.xml中添加如下依赖:

<dependency>    <groupId>redis.clients</groupId>    <artifactId>jedis</artifactId>    <version>5.1.0</version></dependency>
  • 访问Redis数据结构 - Strings
@Test	public void testJedisOperateString() {		String key = "events/student/taylor";		String value = "1,3,5,7";		jedis.set(key, value);		String cachedResponse = jedis.get(key);		assertEquals(value, cachedResponse);	}
  • 访问Redis数据结构 - Lists
@Test	public void testJedisOperateList() {		String key = "queue#tasks";		String value = "firstTask";		jedis.lpush(key, "firstTask");		jedis.lpush(key, "secondTask");		String task = jedis.rpop(key);		assertEquals(value, task);	}
  • 访问Redis数据结构 - Sets
@Test	public void testJedisOperateSet() {		String key = "nicknames";		jedis.sadd(key, "nickname#1");		jedis.sadd(key, "nickname#2");		jedis.sadd(key, "nickname#1");		Set<String> nicknames = jedis.smembers(key);		assertEquals(2, nicknames.size());		boolean exists = jedis.sismember("nicknames", "nickname#1");		assertTrue(exists);	}

nicknames元素个数为2,因为重复的元素nickname#1会被忽略。

  • 访问Redis数据结构 - Hashes
@Test	public void testJedisOperateHash() {		jedis.hset("user#1", "name", "Peter");		jedis.hset("user#1", "job", "politician");		String name = jedis.hget("user#1", "name");		assertEquals("Peter", name);		Map<String, String> fields = jedis.hgetAll("user#1");		String job = fields.get("job");		assertEquals("politician", job);	}
  • 访问Redis数据结构 - Sorted Sets
@Test	public void testJedisOperateSortedSet() {		String key = "ranking";		String value = "firstTask";		Map<String, Double> scores = new HashMap<>();		scores.put("PlayerOne", 3000.0);		scores.put("PlayerTwo", 1500.0);		scores.put("PlayerThree", 8200.0);		scores.entrySet().forEach(playerScore -> {			jedis.zadd(key, playerScore.getValue(), playerScore.getKey());		});		String player = jedis.zrevrange(key, 0, 1).iterator().next();		assertEquals("PlayerThree", player);		long rank = jedis.zrevrank(key, "PlayerOne");		assertEquals(1, rank);	}
  1. 使用Redis存储Spring Session

Spring Session 的目标是将会话管理从服务器中存储的 HTTP 会话限制中解放出来。

该解决方案可以轻松地在云中的服务之间共享会话数据,而无需绑定到单个容器(如 Tomcat)。此外,它还支持在同一浏览器中发送多个会话,并在标头中发送会话。

在本文中,我们将使用 Spring Session 来管理 Web 应用中的身份验证信息。虽然 Spring Session 可以使用 JDBC、Gemfire 或 MongoDB 持久化数据,但本篇将使用Redis作为存储Session的解决方案。

  1. 开发环境

当前项目使用以下开发环境:

  • JDK 17
  • IDE:VS Code(版本1.83.1),并安装以下插件:
Extension Pack for JavaSpring Boot Extension Pack
  1. 创建项目
  • 打开VS Code,按CTRL + SHIFT + P打开命令栏,选择Spring Initializr: Create a Maven Project…开始创建项目
  • 指定Spring Boot版本:3.2.0
  • 指定项目语言:Java
  • 输入group id:com.taylor
  • 输入artifact:session-redis
  • 指定包类型:jar
  • 指定java版本:17
  • 选择所需依赖:
LombokSpring Data RedisSpring SecuritySpring Web

按回车键后,系统提示选择保存项目文件夹,指定文件后按回车键创建项目完成。

按CTRL + SHIFT + P打开命令栏,选择Maven: Add a dependency,搜索并添加下列依赖:

        <dependency>            <groupId>redis.clients</groupId>            <artifactId>jedis</artifactId>            <version>5.1.0</version>        </dependency>        <dependency>            <groupId>org.springframework.session</groupId>            <artifactId>spring-session-data-redis</artifactId>            <version>3.2.0</version>        </dependency>
  1. 项目配置

spring.session.store-type设置为redis将会存储Session到Redis,项目配置如下:

server:  port: 8080spring: data:    redis:      host: 192.168.137.40      port: 6379  session:    store-type: redis    redis:      flush-mode: on_save      namespace: spring:session
  1. 配置Spring Security

首先创建一个安全配置类。

@Configuration

@EnableWebSecurity

public class SecurityConfig {

}

  • 定义密码编码器
		@Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }
  • 使用 Spring Boot CLI 对密码进行编码

为避免暴露纯文本密码可以使用 Spring Boot CLI 对密码进行编码。命令如下:

spring encodepassword password

Spring Boot CLI将产生类似如下编码后的密码:

{bcrypt}a$lDUTwo3pS6bt7Iwv4oVmPuM3hfYNKdddurzv4xBpvzk31RS7LfS72

通常密码格式={编码器id}编码后的密码, 这里我们已经指定密码编码器为BCryptPasswordEncoder, 所以可以移除id:‘{bcrypt}’,直接使用编码后的密码。

  • 创建存储在内存中的测试用户

创建用户名为’admin’和所在权限组为:"ADMIN", "USER"。

				UserDetails admin = User.withUsername("admin")                .password("a$lDUTwo3pS6bt7Iwv4oVmPuM3hfYNKdddurzv4xBpvzk31RS7LfS72")                .roles("ADMIN", "USER")                .build();        return new InMemoryUserDetailsManager(user, admin);
  • 配置Spring安全

主要实现下列Spring安全配置:

只允许具有角色“ADMIN”的登录用户才能访问URL包含“api/v1/session”的API

使用basic认证

确保认证TOKEN存储到Session

		@Bean    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {        http.authorizeHttpRequests(                authorize -> authorize                        .requestMatchers("api/v1/session/**").hasRole("ADMIN")                        .anyRequest()                        .authenticated())                // HTTP Basic are stateless and won't store authentication token in session                // Below code will make authentication token to be stored in Redis                .httpBasic((basic) -> basic                        .addObjectPostProcessor(new ObjectPostProcessor<BasicAuthenticationFilter>() {                            @Override                            public <O extends BasicAuthenticationFilter> O postProcess(O filter) {                                filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());                                return filter;                            }                        }))                // Avoid 401 issue for HTTP post and put method                .csrf(csrf -> csrf.disable());        return http.build();    }
  1. 创建Web API

这里创建一个SessionController,包括三个API:

  • 验证登录用户访问API - /hello
  • 验证使用代码存储数据到Session的API - PUT /data/{key}
  • 验证使用代码获取Session数据的API – GET /data/{key}

代码如下:

package com.taylor.sessionredis.controllers;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PutMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpSession;@RestController@RequestMapping("api/v1/session")public class SessionController {    @GetMapping("/hello")    public String helloWorld(HttpServletRequest request) {        return "Hello, World!";    }    @GetMapping("/data/{key}")    public String getSession(HttpSession session, @PathVariable String key) {        Object sessionValue = session.getAttribute(key);        System.out.println("[get]session id:" + session.getId());        if (sessionValue != null) {            return sessionValue.toString();        }        return "Session doesn't exist for key: " + key;    }    @PutMapping("/data/{key}")    public ResponseEntity<String> setSession(HttpServletRequest request, @PathVariable String key,            @RequestBody String value) {        request.getSession().setAttribute(key, value);        System.out.println("[set]session id:" + request.getSession().getId());        return ResponseEntity.ok("OK");    }}
  1. 创建Junit test测试类

Junit test测试类创建代码如下:

package com.taylor.sessionredis;import static org.junit.jupiter.api.Assertions.assertEquals;import static org.junit.jupiter.api.Assertions.assertTrue;import java.util.HashMap;import java.util.Map;import java.util.Set;import org.junit.jupiter.api.BeforeEach;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.boot.test.web.client.TestRestTemplate;import org.springframework.http.HttpEntity;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpMethod;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import redis.clients.jedis.Jedis;@SpringBootTestclass SessionRedisApplicationTests {	private Jedis jedis;	private TestRestTemplate httpClient;	private TestRestTemplate httpClientWithAuth;	private static final String REDIS_HOST = "192.168.137.40";	private static final int REDIS_PORT = 6379;	private static final String URL_TEST_SESSION = "http://localhost:8080/api/v1/session";	private static final String URL_TEST_SESSION_HELLO = URL_TEST_SESSION + "/hello";	private static final String URL_TEST_SESSION_DATA = URL_TEST_SESSION + "/data";	private static final String EXPECTED_API_RESULT_HELLO = "Hello, World!";	private static final String EXPECTED_API_RESULT_OK = "OK";	@BeforeEach	public void init() {		httpClient = new TestRestTemplate(TestRestTemplate.HttpClientOption.ENABLE_COOKIES);		httpClientWithAuth = new TestRestTemplate("admin", "password",				TestRestTemplate.HttpClientOption.ENABLE_COOKIES);		jedis = new Jedis(REDIS_HOST, REDIS_PORT);		jedis.flushAll();	}	}
  1. 测试用户认证信息存储到Redis

这里我们使用Junit来测试,需要验证下列测试步骤:

  • 测试 Redis 开始时无任何数据
  • 测试用户认证和授权成功,能够访问/hello API
  • 测试 Redis 在会话中存储的身份验证数据
  • 测试匿名访问返回HTTP 401
  • 测试带有Session信息作为Header的匿名访问能够正常访问/hello API
  • 验证Redis清空所有数据后再访问返回HTTP 401

Junit test代码如下:

@Test	public void testRedisControlsSession() {		// Test Redis is empty at the beginning		Set<String> redisKeys = jedis.keys("*");		assertEquals(0, redisKeys.size());		// Test authorization		ResponseEntity<String> result = httpClientWithAuth.getForEntity(URL_TEST_SESSION_HELLO, String.class);		assertEquals(EXPECTED_API_RESULT_HELLO, result.getBody()); // Login worked		// Test authentication data stored in session by Redis		redisKeys = jedis.keys("*");		assertTrue(redisKeys.size() > 0); // Redis is populated with session data		// Get session info for next request		String sessionCookie = result.getHeaders().get("Set-Cookie").get(0).split(";")[0];		HttpHeaders headers = new HttpHeaders();		headers.add("Cookie", sessionCookie);		HttpEntity<String> httpEntitySession = new HttpEntity<>(headers);		// Test accessing anonymous		result = httpClient.getForEntity(URL_TEST_SESSION_HELLO, String.class);		assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode());		// Test accessing anonymous but with authentication session data		result = httpClient.exchange(URL_TEST_SESSION_HELLO,				HttpMethod.GET,				httpEntitySession,				String.class);		assertEquals(EXPECTED_API_RESULT_HELLO, result.getBody());		// clear all keys in Redis		jedis.flushAll();		// Test accessing denied after sessions are removed in Redis		result = httpClient.exchange(URL_TEST_SESSION_HELLO,				HttpMethod.GET,				httpEntitySession,				String.class);		assertEquals(HttpStatus.UNAUTHORIZED, result.getStatusCode());	}
  1. 测试使用Java代码存储Session数据

同样使用Junit来测试,需要验证下列测试步骤:

  • 调用存储数据到Session API
  • 调用获取存储到Redis的Session数据
  • 验证获取数据和之前存储的数据一致

Junit test代码如下:

@Test	public void testSetAndGetSession() {		String sessionKey = "session-key";		String sessionValue = "session-value";		// Test manually storing data in session by Redis		ResponseEntity<String> result = httpClientWithAuth.exchange(URL_TEST_SESSION_DATA + "/" + sessionKey,				HttpMethod.PUT,				new HttpEntity<>(sessionValue),				String.class);		assertEquals(EXPECTED_API_RESULT_OK, result.getBody());		String sessionCookie = result.getHeaders().get("Set-Cookie").get(0).split(";")[0];		HttpHeaders headers = new HttpHeaders();		headers.add("Cookie", sessionCookie);		HttpEntity<String> httpEntity = new HttpEntity<>(headers);		// Test retrieving data from session		result = httpClientWithAuth.exchange(URL_TEST_SESSION_DATA + "/" + sessionKey,				HttpMethod.GET,				// Must include previous session info, or else server side can't know its				// session object				httpEntity,				String.class);		assertEquals(sessionValue, result.getBody());	}

谢谢观看!