Spring Session with Redis

6,360 阅读4分钟

前言

之前通过 docker 在本地搭建了 Redis 环境、而公司内部 Session 方案有诸多定制,同时通过中间件也对 redis 的操作进行了大量封装。

为了一探究竟,本文通过构建一个简单的 web 应用,将 Session 管理与 Redis 的使用相结合从而加深理解。

spring love redis

术语

Session:web应用中用于表示客户端与服务器之间保持状态的解决方案。

Cookie:由web服务器生成发往客户端存储的数据、通常用于记录客户端登陆信息、方便服务端判断请求是否来源于相同的客户端。

HttpSession:因为 HTTP 协议无状态、为了在服务器端实现状态管理Java平台制定了 Session 标准接口规范了服务端状态管理的基本操作。

Web Server:通常也称为 web 容器如 apache tomcat,eclipse jetty,oracle weblogic。它们都实现了 HttpSession 接口、具体细节略有差异。

Spring Session:HttpSession 接口在 Spring Framework 中的实现。通过 Spring Session 将 Session 管理与特定的 web server 解藕、提供统一的 session 管理方案。

Spring Boot 集成 Spring Session

Spring Session 实现了 HttpSession 接口替换了 web server的默认实现,下面将在 demo 应用创建过程中说明其内部机制。

创建 Spring Boot web 应用

<?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.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.kongfutech</groupId>
    <artifactId>spring-session-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-session-demo</name>
    <description>Demo project for Spring Session with Redis</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

集成 Spring Session Data Redis

在 pom 文件中增加 spring-session-data-redis 依赖,由于 Spring Boot 已经集成了 Spring Session 所以这里不再单独引入。

   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>

在 application.properties 中配置以 Redis 作为 Spring Session 存储方式。除 Redis 外 Spring Session 目前还支持 JDBC、MongoDB 等其他存储方式。同时需要配置 redis 连接信息。

server.port=8081

# Session store type.
spring.session.store-type=redis

# Session timeout. If a duration suffix is not specified, seconds is used.
server.servlet.session.timeout=60s

# Redis server host.
spring.redis.host=localhost
# Login password of the redis server.
spring.redis.password=
# Redis server port.
spring.redis.port=6379
# Sessions flush mode.
spring.session.redis.flush-mode=on-save
# Namespace for keys used to store sessions.
spring.session.redis.namespace=springboot-session

增加上述配置之后 Spring Boot 会创建一个名为 springSessionRepositoryFilter 的 SessionRepositoryFilter 去替换 web server 中 HttpSesion 实现。这样就将 Session 的管理交给了 Spring Session。若将 spring.session.store-type 设置为空则表示在应用中禁用 Spring Session。

Spring Boot 会使用 Redis 连接配置自动创建 RedisConnectionFactory 负责与 Redis 集群的交互。

创建 REST Controller

package com.kongfutech.springsession.controller;

import javax.servlet.http.HttpSession;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author kongfutech
 * @version sessionDemo: SessionDemo.java, v 0.1 2019-06-24 19:23 kongfutech Exp $
 */
@RestController
public class SessionDemo {
    @GetMapping("/")
    String sessionId(HttpSession session) {
        return session.getId();
    }
}

配置 Spring Security

package com.kongfutech.springsession.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @author kongfutech
 * @version securityConfig: SecurityConfig.java, v 0.1 2019-06-24 19:26 kongfutech Exp $
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // Authentication : Admin --> Roles
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(
            org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()).withUser("admin").password("haha").roles("ADMIN");
    }

    // Authorization : Role -> Access
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic().and().authorizeRequests().antMatchers("/").hasRole("ADMIN").anyRequest()
            .authenticated();
    }
}

使用 postman 测试

未登陆情况下访问服务

未登陆

使用密码访问服务

使用密码登陆

通过 redis-cli 查看 Session 信息。

通过 redis-cli 删除所有 session 信息后再次访问会出现提示未授权

➜:~ kongfutech$ redis-cli keys "*" | xargs redis-cli del

未登陆

Spring MVC 集成 Spring Session

集成 Spring Session Data Redis

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>{spring-version}</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
        <version>{spring-session-version}}</version>
        <type>pom</type>
    </dependency>

    <dependency>
        <groupId>io.lettuce</groupId>
        <artifactId>lettuce-core</artifactId>
        <version>5.0.4.RELEASE</version>
    </dependency>

Spring boot 通过 spring.session.store-type=redis 这种配置方式实际等价于使用 @EnableRedisHttpSession 创建 filter 替换 Tomcat 等容器默认 Session 实现。

@Configuration
@EnableRedisHttpSession
public class HttpSessionConfig extends AbstractHttpSessionApplicationInitializer {
    // 配置 Redis 连接实现 Session 信息的存储
    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379));
    }
}

总结

  1. Spring boot 应用通过在 pom 文件增加相关依赖实现了 Spring Session 集成,同时在 application.properties 中启用或关闭 Redis 存储 Session。

  2. Spring Session 通过创建一个名为 springSessionRepositoryFilter 的 SessionRepositoryFilter 替换了 web server 默认的 HttpSession 实现。从而实现了 Session 管理与特定的 web server 的解藕以及 Session 存储方式的扩展。

  3. 非 Spring boot 应用集成方式略有不同、但其实现原理是一致的。

参考