微服务安全之传统Session认证授权:从原理到实战的深度剖析

本文涉及的产品
云原生网关 MSE Higress,422元/月
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 本文深入剖析传统Session认证授权机制,涵盖其原理、单体与微服务架构下的实现方案、安全风险及加固措施,并结合Spring Security与Redis实战代码,探讨在分布式环境中的优化与监控策略。

一、传统Session认证授权的底层逻辑

1.1 什么是Session认证

Session认证是基于HTTP无状态特性的解决方案,核心是服务端保存用户会话状态,客户端通过Cookie携带SessionID进行身份关联。其本质是服务端状态存储+客户端身份标识传递的组合模式。

image.gif 编辑

1.2 Session认证的核心流程

  1. 用户提交用户名密码到服务端
  2. 服务端验证通过后创建Session,存储用户信息
  3. 生成唯一SessionID,通过Cookie返回给客户端
  4. 后续请求客户端携带SessionID,服务端查询Session完成身份认证

1.3 授权机制与Session的结合

授权是在认证基础上对用户操作权限的控制,传统模式下:

  • 认证成功后,将用户权限信息存入Session
  • 每次请求时从Session获取权限信息进行校验
  • 通过Filter/Interceptor实现权限拦截

二、单体应用中的Session实现

2.1 技术选型与环境配置

pom.xml核心依赖

<?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>3.2.0</version>
       <relativePath/>
   </parent>

   <groupId>com.jam.demo</groupId>
   <artifactId>session-auth-demo</artifactId>
   <version>1.0.0</version>

   <properties>
       <maven.compiler.source>17</maven.compiler.source>
       <maven.compiler.target>17</maven.compiler.target>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <fastjson2.version>2.0.32</fastjson2.version>
       <mybatis-plus.version>3.5.5</mybatis-plus.version>
       <mysql.version>8.0.33</mysql.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-data-redis</artifactId>
       </dependency>
       
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>${mybatis-plus.version}</version>
       </dependency>
       
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>${mysql.version}</version>
           <scope>runtime</scope>
       </dependency>
       
       <dependency>
           <groupId>com.alibaba.fastjson2</groupId>
           <artifactId>fastjson2</artifactId>
           <version>${fastjson2.version}</version>
       </dependency>
       
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>1.18.30</version>
           <scope>provided</scope>
       </dependency>
       
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-validation</artifactId>
       </dependency>
       
       <dependency>
           <groupId>io.springfox</groupId>
           <artifactId>springfox-boot-starter</artifactId>
           <version>3.0.0</version>
       </dependency>
       
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-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>

application.yml配置

spring:
 datasource:
   url: jdbc:mysql://localhost:3306/session_auth?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
   username: root
   password: root
   driver-class-name: com.mysql.cj.jdbc.Driver
 redis:
   host: localhost
   port: 6379
   password:
   database: 0
 session:
   store-type: redis
   timeout: 1800
server:
 port: 8080
mybatis-plus:
 configuration:
   map-underscore-to-camel-case: true
   log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
 mapper-locations: classpath:mapper/*.xml
 type-aliases-package: com.jam.demo.entity

2.2 数据库设计

用户表

CREATE TABLE `sys_user` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `username` varchar(50) NOT NULL COMMENT '用户名',
 `password` varchar(100) NOT NULL COMMENT '密码(加密)',
 `nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
 `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
 `status` tinyint DEFAULT '1' COMMENT '状态(1:正常,0:禁用)',
 `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';

CREATE TABLE `sys_role` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `role_name` varchar(50) NOT NULL COMMENT '角色名称',
 `role_code` varchar(50) NOT NULL COMMENT '角色编码',
 `description` varchar(200) DEFAULT NULL COMMENT '描述',
 `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

CREATE TABLE `sys_user_role` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `user_id` bigint NOT NULL COMMENT '用户ID',
 `role_id` bigint NOT NULL COMMENT '角色ID',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_user_role` (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';

CREATE TABLE `sys_permission` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `perm_name` varchar(50) NOT NULL COMMENT '权限名称',
 `perm_code` varchar(50) NOT NULL COMMENT '权限编码',
 `url` varchar(200) DEFAULT NULL COMMENT '资源路径',
 `method` varchar(10) DEFAULT NULL COMMENT '请求方法',
 `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_perm_code` (`perm_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表';

CREATE TABLE `sys_role_permission` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `role_id` bigint NOT NULL COMMENT '角色ID',
 `perm_id` bigint NOT NULL COMMENT '权限ID',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_role_perm` (`role_id`,`perm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表';

2.3 核心代码实现

用户实体类

package com.jam.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;

/**
* 系统用户实体
* @author ken
*/

@Data
@TableName("sys_user")
public class SysUser {
   @TableId(type = IdType.AUTO)
   private Long id;
   private String username;
   private String password;
   private String nickname;
   private String email;
   private Integer status;
   private LocalDateTime createTime;
   private LocalDateTime updateTime;
}

用户DTO

package com.jam.demo.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;

/**
* 登录请求DTO
* @author ken
*/

@Data
@ApiModel("登录请求参数")
public class LoginDTO {
   @NotBlank(message = "用户名不能为空")
   @ApiModelProperty(value = "用户名", required = true)
   private String username;
   
   @NotBlank(message = "密码不能为空")
   @ApiModelProperty(value = "密码", required = true)
   private String password;
}

用户Mapper

package com.jam.demo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.SysUser;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;

/**
* 用户Mapper接口
* @author ken
*/

public interface SysUserMapper extends BaseMapper<SysUser> {
   
   /**
    * 根据用户名查询用户
    * @param username 用户名
    * @return 用户信息
    */

   @Select("SELECT * FROM sys_user WHERE username = #{username}")
   SysUser selectByUsername(@Param("username") String username);
   
   /**
    * 查询用户角色编码
    * @param userId 用户ID
    * @return 角色编码列表
    */

   @Select("SELECT r.role_code FROM sys_user_role ur " +
           "LEFT JOIN sys_role r ON ur.role_id = r.id " +
           "WHERE ur.user_id = #{userId}")
   List<String> selectRoleCodesByUserId(@Param("userId") Long userId);
   
   /**
    * 查询用户权限编码
    * @param userId 用户ID
    * @return 权限编码列表
    */

   @Select("SELECT p.perm_code FROM sys_user_role ur " +
           "LEFT JOIN sys_role_permission rp ON ur.role_id = rp.role_id " +
           "LEFT JOIN sys_permission p ON rp.perm_id = p.id " +
           "WHERE ur.user_id = #{userId}")
   List<String> selectPermCodesByUserId(@Param("userId") Long userId);
}

用户Service

package com.jam.demo.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jam.demo.entity.SysUser;
import com.jam.demo.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.stream.Collectors;

/**
* 用户服务实现类
* @author ken
*/

@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> implements UserDetailsService {
   
   private final SysUserMapper sysUserMapper;
   private final PasswordEncoder passwordEncoder;
   
   /**
    * 根据用户名查询用户信息
    * @param username 用户名
    * @return 用户详情
    */

   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
       if (!StringUtils.hasText(username)) {
           throw new UsernameNotFoundException("用户名不能为空");
       }
       
       // 查询用户基本信息
       SysUser sysUser = sysUserMapper.selectByUsername(username);
       if (sysUser == null) {
           throw new UsernameNotFoundException("用户不存在: " + username);
       }
       
       // 查询用户角色
       List<String> roleCodes = sysUserMapper.selectRoleCodesByUserId(sysUser.getId());
       // 查询用户权限
       List<String> permCodes = sysUserMapper.selectPermCodesByUserId(sysUser.getId());
       
       // 构建权限列表(角色+权限)
       List<SimpleGrantedAuthority> authorities = Lists.newArrayList();
       authorities.addAll(roleCodes.stream()
               .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
               .collect(Collectors.toList()));
       authorities.addAll(permCodes.stream()
               .map(perm -> new SimpleGrantedAuthority(perm))
               .collect(Collectors.toList()));
       
       return User.builder()
               .username(sysUser.getUsername())
               .password(sysUser.getPassword())
               .authorities(authorities)
               .accountExpired(false)
               .accountLocked(false)
               .credentialsExpired(false)
               .disabled(sysUser.getStatus() == 0)
               .build();
   }
   
   /**
    * 用户注册
    * @param username 用户名
    * @param password 密码
    * @param nickname 昵称
    * @return 注册结果
    */

   public boolean register(String username, String password, String nickname) {
       if (sysUserMapper.selectByUsername(username) != null) {
           return false;
       }
       
       SysUser user = new SysUser();
       user.setUsername(username);
       user.setPassword(passwordEncoder.encode(password));
       user.setNickname(StringUtils.hasText(nickname) ? nickname : username);
       user.setStatus(1);
       
       return save(user);
   }
}

认证控制器

package com.jam.demo.controller;

import com.jam.demo.dto.LoginDTO;
import com.jam.demo.service.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

/**
* 认证控制器
* @author ken
*/

@Slf4j
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@Api(tags = "认证接口")
public class AuthController {
   
   private final SysUserService sysUserService;
   
   /**
    * 用户登录
    * @param loginDTO 登录参数
    * @param session HTTP会话
    * @return 登录结果
    */

   @PostMapping("/login")
   @ApiOperation("用户登录")
   public ResponseEntity<?> login(@Validated @RequestBody LoginDTO loginDTO, HttpSession session) {
       // Spring Security会自动处理认证,这里直接返回成功信息
       Map<String, Object> result = Maps.newHashMap();
       result.put("code", 200);
       result.put("message", "登录成功");
       result.put("sessionId", session.getId());
       result.put("timeout", session.getMaxInactiveInterval());
       return ResponseEntity.ok(result);
   }
   
   /**
    * 用户注册
    * @param username 用户名
    * @param password 密码
    * @param nickname 昵称
    * @return 注册结果
    */

   @PostMapping("/register")
   @ApiOperation("用户注册")
   public ResponseEntity<?> register(
           @RequestParam String username,
           @RequestParam String password,
           @RequestParam(required = false) String nickname) {
       
       if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
           return ResponseEntity.badRequest().body("用户名和密码不能为空");
       }
       
       boolean success = sysUserService.register(username, password, nickname);
       if (success) {
           return ResponseEntity.ok("注册成功");
       } else {
           return ResponseEntity.badRequest().body("用户名已存在");
       }
   }
   
   /**
    * 用户登出
    * @param request 请求对象
    * @param response 响应对象
    * @return 登出结果
    */

   @GetMapping("/logout")
   @ApiOperation("用户登出")
   public ResponseEntity<?> logout(HttpServletRequest request, HttpServletResponse response) {
       Authentication auth = SecurityContextHolder.getContext().getAuthentication();
       if (auth != null) {
           new SecurityContextLogoutHandler().logout(request, response, auth);
       }
       return ResponseEntity.ok("登出成功");
   }
   
   /**
    * 获取当前用户信息
    * @param session HTTP会话
    * @return 用户信息
    */

   @GetMapping("/current-user")
   @ApiOperation("获取当前用户信息")
   public ResponseEntity<?> getCurrentUser(HttpSession session) {
       Authentication auth = SecurityContextHolder.getContext().getAuthentication();
       if (auth == null || auth.getPrincipal().equals("anonymousUser")) {
           return ResponseEntity.status(401).body("未登录");
       }
       
       Map<String, Object> userInfo = Maps.newHashMap();
       userInfo.put("username", auth.getName());
       userInfo.put("authorities", auth.getAuthorities());
       userInfo.put("sessionId", session.getId());
       
       return ResponseEntity.ok(userInfo);
   }
}

资源控制器

package com.jam.demo.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* 资源控制器
* @author ken
*/

@Slf4j
@RestController
@RequestMapping("/resources")
@Api(tags = "资源接口")
public class ResourceController {
   
   @GetMapping("/public")
   @ApiOperation("公共资源")
   public String publicResource() {
       return "这是公共资源,无需认证即可访问";
   }
   
   @GetMapping("/user")
   @ApiOperation("用户资源")
   @PreAuthorize("hasRole('USER')")
   public String userResource() {
       return "这是用户资源,需要USER角色";
   }
   
   @GetMapping("/admin")
   @ApiOperation("管理员资源")
   @PreAuthorize("hasRole('ADMIN')")
   public String adminResource() {
       return "这是管理员资源,需要ADMIN角色";
   }
   
   @GetMapping("/permission")
   @ApiOperation("特定权限资源")
   @PreAuthorize("hasAuthority('sys:user:view')")
   public String permissionResource() {
       return "这是需要特定权限的资源";
   }
}

Security配置

package com.jam.demo.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
* Spring Security配置
* @author ken
*/

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
   
   /**
    * 密码编码器
    */

   @Bean
   public PasswordEncoder passwordEncoder() {
       return new BCryptPasswordEncoder();
   }
   
   /**
    * 安全过滤链配置
    */

   @Bean
   public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
       http
           .csrf(csrf -> csrf.disable())
           .authorizeHttpRequests(auth -> auth
               .requestMatchers("/auth/login", "/auth/register", "/resources/public",
                                "/swagger-ui/**", "/v3/api-docs/**").permitAll()
               .anyRequest().authenticated()
           )
           .formLogin(form -> form
               .loginProcessingUrl("/auth/login")
               .successHandler((request, response, authentication) -> {
                   response.setContentType("application/json;charset=UTF-8");
                   response.getWriter().write("{\"code\":200,\"message\":\"登录成功\"}");
               })
               .failureHandler((request, response, exception) -> {
                   response.setContentType("application/json;charset=UTF-8");
                   response.getWriter().write("{\"code\":500,\"message\":\"" + exception.getMessage() + "\"}");
               })
           )
           .logout(logout -> logout
               .logoutUrl("/auth/logout")
               .logoutSuccessHandler((request, response, authentication) -> {
                   response.setContentType("application/json;charset=UTF-8");
                   response.getWriter().write("{\"code\":200,\"message\":\"登出成功\"}");
               })
           )
           .sessionManagement(session -> session
               .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
               .invalidSessionUrl("/auth/login?invalid=true")
               .maximumSessions(1)
               .expiredUrl("/auth/login?expired=true")
           );
       
       return http.build();
   }
}

三、微服务架构下的Session共享问题

3.1 微服务Session挑战

在微服务架构中,传统单机Session面临以下问题:

  • Session不共享:用户请求分发到不同服务实例导致认证失效
  • Session同步复杂:多实例间Session同步成本高
  • 跨服务认证:不同微服务间需要统一的身份认证
  • 扩展性差:Session存储在应用内存,限制服务实例扩展

image.png

3.2 Session共享解决方案

3.2.1 基于Redis的Session共享

原理:将Session存储在Redis中,所有服务实例共享Redis中的Session数据

实现方案

package com.jam.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;

/**
* Redis Session配置
* @author ken
*/

@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class RedisSessionConfig {
   
   /**
    * Cookie序列化配置
    */

   @Bean
   public CookieSerializer cookieSerializer() {
       DefaultCookieSerializer serializer = new DefaultCookieSerializer();
       serializer.setCookieName("SESSIONID");
       serializer.setCookiePath("/");
       serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
       serializer.setUseHttpOnlyCookie(true);
       serializer.setSameSite("Lax");
       serializer.setUseSecureCookie(false); // 生产环境建议设为true
       return serializer;
   }
}

3.2.2 Session粘滞(IP Hash)

负载均衡器根据用户IP地址计算哈希,确保同一用户请求始终分发到同一服务实例。优点是实现简单,缺点是不利于服务扩缩容,单点故障影响用户体验。

3.2.3 Token认证替代方案

JWT(JSON Web Token)作为无状态认证方案,客户端存储Token,服务端无需保存会话状态,天然适合微服务架构。

四、Session安全加固

4.1 Session安全风险

  • Session劫持:通过窃取SessionID冒充用户身份
  • Session固定:攻击者诱导用户使用固定SessionID
  • CSRF攻击:利用用户已认证的Session发起恶意请求
  • Session超时设置不当:Session有效期过长增加被盗风险

4.2 安全加固措施

4.2.1 SessionID保护

package com.jam.demo.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
* Session安全过滤器
* @author ken
*/

@Slf4j
@Component
public class SessionSecurityFilter implements Filter {
   
   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
           throws IOException, ServletException {
       
       HttpServletRequest req = (HttpServletRequest) request;
       HttpServletResponse res = (HttpServletResponse) response;
       
       // 设置Cookie的HttpOnly和Secure属性
       res.setHeader("Set-Cookie", "SESSIONID=" + req.getSession().getId() +
               "; HttpOnly; Secure; SameSite=Lax; Path=/");
       
       // 验证请求来源
       String referer = req.getHeader("Referer");
       String host = req.getServerName();
       if (referer != null && !referer.contains(host)) {
           log.warn("可疑的请求来源: {}", referer);
           // 可以根据需要进行拦截处理
       }
       
       // 检查Session是否包含用户信息
       HttpSession session = req.getSession(false);
       if (session != null && session.getAttribute("USER_INFO") == null) {
           log.warn("Session中用户信息缺失,可能存在Session劫持风险");
       }
       
       chain.doFilter(request, response);
   }
}

4.2.2 敏感操作二次验证

package com.jam.demo.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;

/**
* 账户安全控制器
* @author ken
*/

@Slf4j
@RestController
@RequestMapping("/account")
@RequiredArgsConstructor
@Api(tags = "账户安全接口")
public class AccountSecurityController {
   
   /**
    * 修改密码(需要二次验证)
    */

   @PostMapping("/change-password")
   @ApiOperation("修改密码")
   public String changePassword(
           @RequestParam String newPassword,
           @RequestParam String verifyCode,
           HttpSession session)
{
       
       // 验证验证码
       String savedCode = (String) session.getAttribute("VERIFY_CODE");
       if (savedCode == null || !savedCode.equals(verifyCode)) {
           return "验证码错误";
       }
       
       // 执行密码修改逻辑
       log.info("密码修改成功,更新Session信息");
       
       // 修改密码后使当前Session失效,强制重新登录
       session.invalidate();
       
       return "密码修改成功,请重新登录";
   }
}

4.2.3 Session超时管理

package com.jam.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.SessionRepositoryFilter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
* Session超时过滤器
* @author ken
*/

@Configuration
public class SessionTimeoutFilter implements Filter {
   
   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
           throws IOException, ServletException {
       
       HttpServletRequest req = (HttpServletRequest) request;
       HttpServletResponse res = (HttpServletResponse) response;
       
       // 检查Session是否即将过期
       HttpSession session = req.getSession(false);
       if (session != null) {
           int maxInactive = session.getMaxInactiveInterval();
           long lastAccessed = session.getLastAccessedTime();
           long current = System.currentTimeMillis();
           
           // 如果剩余时间小于60秒,返回警告信息
           if ((current - lastAccessed) / 1000 > maxInactive - 60) {
               res.setHeader("X-Session-Timeout-Warning", "true");
           }
       }
       
       chain.doFilter(request, response);
   }
   
   @Override
   public void init(FilterConfig filterConfig) throws ServletException {
       Filter.super.init(filterConfig);
   }
   
   @Override
   public void destroy() {
       Filter.super.destroy();
   }
}

五、性能优化与监控

5.1 Session存储优化

Redis缓存优化配置

spring:
 redis:
   lettuce:
     pool:
       max-active: 8
       max-idle: 8
       min-idle: 0
       max-wait: -1ms
   timeout: 2000ms
 session:
   redis:
     flush-mode: on_save
     namespace: spring:session

5.2 Session监控

package com.jam.demo.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
* Session监控控制器
* @author ken
*/

@Slf4j
@RestController
@RequestMapping("/monitor")
@RequiredArgsConstructor
public class MonitorController {
   
   private final RedisTemplate<String, Object> redisTemplate;
   
   /**
    * 获取Session统计信息
    */

   @GetMapping("/session-stats")
   public Map<String, Object> getSessionStats(HttpServletRequest request) {
       Map<String, Object> stats = Maps.newHashMap();
       
       // 获取当前活跃Session数(通过Redis前缀匹配)
       Collection<String> keys = redisTemplate.keys("spring:session:sessions:*");
       stats.put("active_sessions", keys != null ? keys.size() : 0);
       
       // 当前请求的Session信息
       stats.put("current_session_id", request.getSession().getId());
       stats.put("current_session_timeout", request.getSession().getMaxInactiveInterval());
       
       // 服务器信息
       stats.put("server_port", request.getServerPort());
       stats.put("context_path", request.getContextPath());
       
       return stats;
   }
}

六、总结与展望

传统Session认证授权机制在单体应用中成熟可靠,但在微服务架构下面临共享和扩展挑战。通过Redis等分布式存储实现Session共享是过渡方案,而JWT等无状态认证更适合云原生微服务架构。

在实际应用中,应根据系统规模和架构特点选择合适的认证方案:

  • 小型单体应用:传统Session认证足够满足需求
  • 中大型分布式系统:分布式Session或JWT认证
  • 云原生微服务:OAuth2.0/OIDC+JWT的认证授权体系

无论选择哪种方案,都必须重视安全加固,防范Session劫持、固定等攻击,确保系统安全性。

目录
相关文章
|
5天前
|
云安全 人工智能 安全
AI被攻击怎么办?
阿里云提供 AI 全栈安全能力,其中对网络攻击的主动识别、智能阻断与快速响应构成其核心防线,依托原生安全防护为客户筑牢免疫屏障。
|
15天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
9天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
605 214
|
存储 人工智能 监控
从代码生成到自主决策:打造一个Coding驱动的“自我编程”Agent
本文介绍了一种基于LLM的“自我编程”Agent系统,通过代码驱动实现复杂逻辑。该Agent以Python为执行引擎,结合Py4j实现Java与Python交互,支持多工具调用、记忆分层与上下文工程,具备感知、认知、表达、自我评估等能力模块,目标是打造可进化的“1.5线”智能助手。
847 61
|
7天前
|
人工智能 移动开发 自然语言处理
2025最新HTML静态网页制作工具推荐:10款免费在线生成器小白也能5分钟上手
晓猛团队精选2025年10款真正免费、无需编程的在线HTML建站工具,涵盖AI生成、拖拽编辑、设计稿转代码等多种类型,均支持浏览器直接使用、快速出图与文件导出,特别适合零基础用户快速搭建个人网站、落地页或企业官网。
1250 157
|
4天前
|
编解码 Linux 数据安全/隐私保护
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
教程分享免费视频压缩软件,免费视频压缩,视频压缩免费,附压缩方法及学习教程
241 138
|
7天前
|
存储 安全 固态存储
四款WIN PE工具,都可以实现U盘安装教程
Windows PE是基于NT内核的轻量系统,用于系统安装、分区管理及故障修复。本文推荐多款PE制作工具,支持U盘启动,兼容UEFI/Legacy模式,具备备份还原、驱动识别等功能,操作简便,适合新旧电脑维护使用。
521 109