首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >微服务安全之传统Session认证授权:从原理到实战的深度剖析

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

作者头像
果酱带你啃java
发布2026-04-14 14:25:26
发布2026-04-14 14:25:26
250
举报

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

1.1 什么是Session认证

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

1.2 Session认证的核心流程

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

1.3 授权机制与Session的结合

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

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

二、单体应用中的Session实现

2.1 技术选型与环境配置

pom.xml核心依赖

代码语言:javascript
复制
<?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配置

代码语言:javascript
复制
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 数据库设计

用户表

代码语言:javascript
复制
CREATE TABLE`sys_user` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`username`varchar(50) NOTNULLCOMMENT'用户名',
`password`varchar(100) NOTNULLCOMMENT'密码(加密)',
`nickname`varchar(50) DEFAULTNULLCOMMENT'昵称',
`email`varchar(100) DEFAULTNULLCOMMENT'邮箱',
`status`tinyintDEFAULT'1'COMMENT'状态(1:正常,0:禁用)',
`create_time` datetime DEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
`update_time` datetime DEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',
  PRIMARY KEY (`id`),
UNIQUEKEY`uk_username` (`username`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='系统用户表';

CREATETABLE`sys_role` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`role_name`varchar(50) NOTNULLCOMMENT'角色名称',
`role_code`varchar(50) NOTNULLCOMMENT'角色编码',
`description`varchar(200) DEFAULTNULLCOMMENT'描述',
`create_time` datetime DEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
`update_time` datetime DEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',
  PRIMARY KEY (`id`),
UNIQUEKEY`uk_role_code` (`role_code`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='角色表';

CREATETABLE`sys_user_role` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`user_id`bigintNOTNULLCOMMENT'用户ID',
`role_id`bigintNOTNULLCOMMENT'角色ID',
  PRIMARY KEY (`id`),
UNIQUEKEY`uk_user_role` (`user_id`,`role_id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='用户角色关联表';

CREATETABLE`sys_permission` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`perm_name`varchar(50) NOTNULLCOMMENT'权限名称',
`perm_code`varchar(50) NOTNULLCOMMENT'权限编码',
`url`varchar(200) DEFAULTNULLCOMMENT'资源路径',
`method`varchar(10) DEFAULTNULLCOMMENT'请求方法',
`create_time` datetime DEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
  PRIMARY KEY (`id`),
UNIQUEKEY`uk_perm_code` (`perm_code`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='权限表';

CREATETABLE`sys_role_permission` (
`id`bigintNOTNULL AUTO_INCREMENT COMMENT'主键ID',
`role_id`bigintNOTNULLCOMMENT'角色ID',
`perm_id`bigintNOTNULLCOMMENT'权限ID',
  PRIMARY KEY (`id`),
UNIQUEKEY`uk_role_perm` (`role_id`,`perm_id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='角色权限关联表';

2.3 核心代码实现

用户实体类

代码语言:javascript
复制
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")
publicclass 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

代码语言:javascript
复制
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("登录请求参数")
publicclass LoginDTO {
    @NotBlank(message = "用户名不能为空")
    @ApiModelProperty(value = "用户名", required = true)
    private String username;
    
    @NotBlank(message = "密码不能为空")
    @ApiModelProperty(value = "密码", required = true)
    private String password;
}

用户Mapper

代码语言:javascript
复制
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
 */
publicinterface 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

代码语言:javascript
复制
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
publicclass SysUserService extends ServiceImpl<SysUserMapper, SysUser> implements UserDetailsService {
    
    privatefinal SysUserMapper sysUserMapper;
    privatefinal PasswordEncoder passwordEncoder;
    
    /**
     * 根据用户名查询用户信息
     * @param username 用户名
     * @return 用户详情
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (!StringUtils.hasText(username)) {
            thrownew UsernameNotFoundException("用户名不能为空");
        }
        
        // 查询用户基本信息
        SysUser sysUser = sysUserMapper.selectByUsername(username);
        if (sysUser == null) {
            thrownew 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) {
            returnfalse;
        }
        
        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);
    }
}

认证控制器

代码语言:javascript
复制
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 = "认证接口")
publicclass AuthController {
    
    privatefinal 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);
    }
}

资源控制器

代码语言:javascript
复制
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 = "资源接口")
publicclass 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配置

代码语言:javascript
复制
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
publicclass SecurityConfig {
    
    /**
     * 密码编码器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        returnnew 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存储在应用内存,限制服务实例扩展

3.2 Session共享解决方案

3.2.1 基于Redis的Session共享

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

实现方案

代码语言:javascript
复制
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)
publicclass 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保护
代码语言:javascript
复制
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
publicclass 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 敏感操作二次验证
代码语言:javascript
复制
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 = "账户安全接口")
publicclass 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超时管理
代码语言:javascript
复制
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
publicclass 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缓存优化配置

代码语言:javascript
复制
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监控

代码语言:javascript
复制
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
publicclass MonitorController {
    
    privatefinal 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劫持、固定等攻击,确保系统安全性。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-01-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、传统Session认证授权的底层逻辑
    • 1.1 什么是Session认证
    • 1.2 Session认证的核心流程
    • 1.3 授权机制与Session的结合
    • 二、单体应用中的Session实现
      • 2.1 技术选型与环境配置
      • 2.2 数据库设计
      • 2.3 核心代码实现
    • 三、微服务架构下的Session共享问题
      • 3.1 微服务Session挑战
      • 3.2 Session共享解决方案
    • 四、Session安全加固
      • 4.1 Session安全风险
      • 4.2 安全加固措施
    • 五、性能优化与监控
      • 5.1 Session存储优化
      • 5.2 Session监控
    • 六、总结与展望
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档