位置:首页 > Java > 基于Spring Security实现自动登录的完整指南

基于Spring Security实现自动登录的完整指南

时间:2026-08-27  |  作者:火苗实验室  |  阅读:0

目录

  1. 一、自动登录原理
  2. 二、创建持久化表结构
  3. 三、添加 Maven 依赖
  4. 四、配置数据源
  5. 五、Spring Security 配置类
  6. 六、自定义 UserDetailsService

前言

本文详细解析了基于Spring Security实现自动登录的完整流程。从核心原理出发,阐述服务端生成令牌并存储于Cookie与数据库的机制。接着指导创建persistent_logins表、引入Maven依赖及配置数据源。重点讲解Spring Security配置类中Remember-Me功能的启用,以及自定义UserDetailsService加载用户信息。最后结合Thymeleaf登录页面与Controller控制器,确保复选框参数匹配,实现安全且便捷的自动登录体验。

基于Spring Security实现自动登录的完整指南 的核心流程信息图
基于Spring Security实现自动登用简体中文信息图概括基于Spring Security实现自动登的核心流程、关键规则与实践要点。

一、自动登录原理

自动登录的核心机制在于首次认证成功后,服务端会生成一个令牌(token),并将其分别存储于浏览器 Cookie 和数据库持久层中。当用户再次访问系统时,浏览器会自动携带该 Cookie 发送至服务端。服务端接收到请求后,提取其中的 token 并在数据库中查找匹配记录。若数据库中存在该 token 且未过期,则系统判定用户身份有效,从而完成自动登录流程;反之,若未找到对应记录或 token 失效,则视为认证失败,需重新登录。

一、自动登录原理 对应的技术说明图
一、自动登录原理概括一、自动登录原理的核心概念、关键要点与实践提示。

二、创建持久化表结构

Spring Security 框架内置了 JdbcTokenRepositoryImpl 组件,专门用于持久化存储 remember-me 相关的 token 数据。为了实现这一功能,开发者必须在数据库中创建一张对应的 persistent_logins 表,用于保存令牌值、用户名及过期时间等关键信息,确保服务端能够准确查询和验证用户身份。

二、创建持久化表结构 对应的技术说明图
二、创建持久化表结构概括二、创建持久化表结构的核心概念、关键要点与实践提示。

三、添加 Maven 依赖

在项目的构建配置中,必须确保 pom.xml 文件内正确引入了 Spring Security 核心模块以及 Spring Boot Starter Web 依赖。这些依赖是构建 Web 应用并启用安全认证功能的基础,缺少任一依赖都可能导致自动登录功能无法正常运行或出现类加载错误。

四、配置数据源

为了使 Spring Security 能够访问持久化表,需要在 application.yml 配置文件中明确指定数据库连接参数。这包括数据库 URL、用户名、密码以及驱动类名等信息,确保应用能够成功建立与数据库的连接,进而执行 token 的读写操作。

五、Spring Security 配置类

这是实现自动登录的核心环节。开发者需要编写一个配置类,在其中启用 remember-me 功能,并明确指定 token 的持久化策略。通过配置类,可以定义 token 的有效期、密钥以及是否使用数据库存储等细节,从而控制自动登录的行为模式。

六、自定义 UserDetailsService

该接口用于从数据库加载用户详细信息。在 remember-me 自动登录过程中,系统需要根据 token 中的用户名查询对应的用户数据。因此,必须实现自定义的 UserDetailsService,确保能够从持久层准确获取用户凭证和权限信息,以完成身份验证。

七、登录页面(Thymeleaf 模板)

src/main/resources/templates/login.html 目录下创建登录页面模板。需要注意的是,页面中代表“记住我”功能的复选框(checkbox)的 name 属性值,必须与 Spring Security 配置中指定的 rememberMeParameter 参数完全一致,否则服务端无法正确识别用户的自动登录意愿。

八、Controller 控制器

编写 Controller 类,用于处理登录页面和首页的请求映射。该控制器负责接收用户的登录请求,并将用户重定向至首页或错误页面,确保整个登录流程的路由逻辑清晰且正确。

九、首页模板

src/main/resources/templates/home.html 目录下创建首页模板,用于展示登录成功后的用户信息。该页面通常包含欢迎语、用户名以及退出登录的链接,为用户提供基本的交互界面。

十、验证自动登录

  1. 启动项目,访问 http://localhost:8080/login
  2. 输入用户名 admin,密码 123456,勾选"记住我"复选框,点击登录。
  3. 登录成功后,浏览器会收到名为 remember-me-cookie 的 cookie。
  4. 关闭浏览器,重新打开并访问 http://localhost:8080/home,无需再次登录即可进入首页。
  5. 查看数据库 persistent_logins 表,会看到一条包含用户名、series 和 token 的记录。

十一、核心原理总结

  • 首次登录:用户勾选"记住我"提交登录 → RememberMeAuthenticationFilter 拦截成功认证 → 生成 series + token 存入数据库,同时将 series 写入 cookie 返回浏览器。
  • 后续访问:浏览器携带 cookie 访问 → RememberMeAuthenticationFilter 从 cookie 中提取 series → 去数据库查询匹配的 series 和 token → 验证通过后自动创建认证对象,用户无需输入密码。
  • 安全机制:每次自动登录成功后,服务端会更新 token 值(防止 token 被窃取后重复使用)。如果数据库中的 token 与 cookie 中的 token 不匹配,说明 token 可能被盗用,系统会清除该用户的所有 remember-me 记录。

常见疑问解答

Q1:token 过期了也能查到数据,岂不是不安全?

Q2:唯一序列号(series)的作用是什么?浏览器不是携带 token 进行访问吗?

A:浏览器 cookie 中存储的是 series + token 的组合,而非单纯的 token。series 作为数据库 persistent_logins 表的主键,用于唯一标识一次登录会话。服务端根据 cookie 中的 series 去数据库查找对应的记录,再比对 token 是否匹配。这样设计的好处是:同一个用户可以拥有多个 series(多次登录产生多个会话),每个会话独立管理;当某个 series 对应的 token 被盗用时,只需删除该 series 的记录,不影响其他正常会话。

Q3:token 在数据库中会自动更新吗?客户端(浏览器)怎么更新?

A:是的,token 在数据库中会自动更新,但这个过程是由 Spring Security 框架自动完成的,不需要开发者手动编写更新逻辑。具体流程如下:

  • 服务端处理:当用户携带有效的 remember-me cookie 发起请求时,Spring Security 的 TokenBasedRememberMeServices 会验证 token 的有效性。如果验证通过,框架会自动生成一个新的 token 值,并更新数据库 persistent_logins 表中对应 series 的记录。
  • 客户端更新:在服务端更新数据库的同时,框架会向客户端浏览器写入一个新的 cookie。这个新 cookie 包含了新生成的 token 和原有的 series。浏览器会自动覆盖旧的 cookie 值,从而保持客户端与服务端数据的同步。

这种机制确保了即使 token 被窃取,攻击者使用的也是旧 token,而服务端已将其标记为失效。一旦检测到 token 不匹配,系统会立即删除该 series 的所有记录,强制用户重新登录,从而最大限度地降低安全风险。

注意:虽然自动更新机制提高了安全性,但建议开发者合理设置 tokenValiditySeconds 参数,以平衡用户体验与安全性。过长的过期时间可能增加 token 被长期利用的风险。

服务端与客户端的 Token 同步机制

在自动登录流程中,服务端与客户端的交互并非简单的读取,而是包含了一个动态更新的闭环过程。当用户通过 remember-me cookie 成功实现自动登录时,系统并不会一直使用最初生成的那个 token。相反,PersistentTokenBasedRememberMeServices 会主动调用 PersistentTokenRepository.updateToken() 方法,生成一个全新的随机 token 值。随后,系统会将这个新生成的 token 更新到数据库 persistent_logins 表的对应字段中,而该记录中的 series 字段保持不变。这一机制确保了每次自动登录都伴随着数据的刷新。

  • 客户端(浏览器)自动更新 cookie:在服务端完成数据库更新后,响应头 Set-Cookie 会将新的 series + token 组合写入浏览器的 remember-me-cookie 中。浏览器接收到该响应后,会自动覆盖本地存储的旧 cookie。整个过程对用户完全透明,用户无需手动干预即可完成 token 的迭代。

    简单来说,每次自动登录都是一次“一换一”的更新操作:服务端更新数据库中的 token,同时通过 HTTP 响应让浏览器更新 cookie 中的 token,两者始终保持严格一致。这种机制极大地提升了安全性,即使旧的 token 被恶意窃取,在合法用户下一次自动登录时,该旧 token 也会因数据库中的值已变更而立即失效。

    Cookie 名称的自定义配置

    Q4:remember-me-cookie 必须这样命名吗?设置成其他的命名可以吗?

    A:完全可以自定义。在 Spring Security 的配置文件中,.rememberMeCookieName("remember-me-cookie") 这一行代码就是用来指定 cookie 名称的关键配置。你可以将其修改为任意符合规范的 cookie 名称,例如:

    .rememberMeCookieName("my-app-remember-token")   // 自定义 cookie 名称

    需要注意的是,修改 cookie 名称后,退出登录时的清除逻辑也必须同步调整。具体而言,deleteCookies() 配置中的名称也要同步修改为新的名称,否则在用户执行退出操作时,系统将无法正确清除该 cookie,导致状态残留:

    .logout(logout -> logout
        .logoutSuccessUrl("/loginlogout")
        .deleteCookies("my-app-remember-token")  // 与 rememberMeCookieName 保持一致
        .permitAll()
    )

    此外,如果前端 JavaScript 代码需要读取或操作这个 cookie,也必须使用自定义后的名称进行匹配。总之,remember-me-cookie 仅仅是 Spring Security 框架提供的默认名称,你可以根据项目的具体规范随意命名,只要保证在配置文件中所有引用该名称的地方保持一致即可。

    Q5:series 字段存储在哪?浏览器是如何把它发送到服务端的?

    A:series 字段存储在以下两个位置:

    • 数据库:series 是 persistent_logins 表的主键,首次登录勾选"记住我"时由服务端生成并写入该表。
    • 浏览器 cookie:series 与 token 拼接成 series:token 格式,存储在名为 remember-me-cookie 的 cookie 中(cookie 名称可自定义)。

    浏览器发送 series 到服务端的过程完全自动:

    1. 用户首次登录勾选"记住我"后,服务端通过响应头 Set-Cookieseries:token 写入浏览器 cookie。
    2. 后续浏览器访问同一站点的任何请求,都会自动携带该 cookie(受同源策略保护)。
    3. 服务端的 RememberMeAuthenticationFilter 拦截请求,从 cookie 中提取 series:token,解析出 series 后去数据库 persistent_logins 表查询对应记录,再比对 token 是否匹配。

    整个过程对开发者完全透明,无需手动编写 cookie 读写代码。Spring Security 的 PersistentTokenBasedRememberMeServices 负责 cookie 的解析和 token 的比对工作。

    A:cookie 中存储的格式为 series:token,中间用英文冒号(:)作为分隔符。例如一个实际的 cookie 值长这样:

    Cookie 名称: remember-me

    Cookie 值: series + ":" + token (Base64编码)

    示例: MmFjM2Q0ZTU2Nzg5MGFiYw==:ZGVmNDU2Nzg5MGFiY2RlZg==

    其中:

    • series 部分(如 MmFjM2Q0ZTU2Nzg5MGFiYw==)是随机生成的唯一标识符,用于在数据库中定位用户会话。
    • token 部分(如 ZGVmNDU2Nzg5MGFiY2RlZg==)是包含用户详细信息(如用户名、权限等)的加密字符串,用于验证用户身份。
    • Base64 编码确保了数据在 HTTP 头中传输时的安全性和兼容性,避免了特殊字符导致的解析错误。

    当浏览器再次访问服务器时,它会自动将包含 series 和 token 的 cookie 附加到请求头中。服务器端的 RememberMeAuthenticationFilter 组件会提取这些值,并根据 series 在 persistent_logins 表中查找对应的记录。如果找到记录且 token 匹配,则自动登录成功;否则,登录失效,需要用户重新登录。

    这种机制不仅提高了用户体验,还增强了系统的安全性,因为即使 cookie 被截获,攻击者也需要同时拥有有效的 series 和 token 才能冒充用户。此外,Spring Security 提供的 PersistentTokenBasedRememberMeServices 组件简化了这一过程,使得开发者无需手动处理复杂的 cookie 逻辑。

    Cookie 数据结构解析

    在自动登录机制中,Cookie 的值并非单一字符串,而是采用了特定的分隔格式。这种设计旨在将身份标识与验证凭据紧密结合,确保服务端能够高效地处理请求。

    核心组成部分

    • 冒号前的部分series(唯一序列号),用于在 persistent_logins 表中定位记录。
    • 冒号后的部分token(登录令牌),用于验证本次自动登录的合法性。

    服务端解析时,PersistentTokenBasedRememberMeServices 会按第一个冒号将 cookie 值拆分为两部分:前半部分作为 series,后半部分作为 token。如果 cookie 值中没有冒号,或者拆分后任一部分为空,服务端会判定该 cookie 无效,拒绝自动登录。

    设计优势分析

    这种 series:token 的格式设计,使得服务端可以仅凭一个 cookie 值同时获取到定位记录的主键(series)和验证凭据(token),无需额外的请求参数或数据库全表扫描,既高效又安全。

    以上就是基于Spring Security实现自动登录的示例代码的详细内容,更多关于Spring Security自动登录的资料请关注本站其它相关文章!

    您可能感兴趣的文章:
    • Spring Security 单点登录与自动登录机制的实现原理
    • SpringSecurity自动登录流程与实现详解
    • Spring Security基于散列加密方案实现自动登录功能
    • SpringBoot 配合 SpringSecurity 实现自动登录功能的代码
    • Spring Security学习之rememberMe自动登录的实现
    • Spring security实现记住我下次自动登录功能过程详解
    CREATE TABLE persistent_logins (
        username VARCHAR(64) NOT NULL COMMENT '用户名',
        series VARCHAR(64) NOT NULL COMMENT '唯一序列号,用于标识一次登录会话',
        token  VARCHAR(64) NOT NULL COMMENT '登录令牌,每次自动登录后更新',
        last_used TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后使用时间',
        PRIMARY KEY (series)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='记住我功能持久化登录信息表';
    
        org.springframework.boot
        spring-boot-starter-security
    
    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.springframework.boot
        spring-boot-starter-jdbc
    
    
        mysql
        mysql-connector-java
        runtime
    
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/security_demouseSSL=false&serverTimezone=UTC
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.core.userdetails.UserDetailsService;
    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.rememberme.JdbcTokenRepositoryImpl;
    import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
    import javax.sql.DataSource;
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    private final DataSource dataSource;
    private final UserDetailsService userDetailsService;
    // 通过构造器注入 DataSource 和 UserDetailsService
    public SecurityConfig(DataSource dataSource, UserDetailsService userDetailsService) {
    this.dataSource = dataSource;
    this.userDetailsService = userDetailsService;
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
    // 使用 BCrypt 加密密码
    return new BCryptPasswordEncoder();
    }
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
    // 创建 JdbcTokenRepositoryImpl 实例,用于将 token 持久化到数据库
    JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
    repo.setDataSource(dataSource);
    // 首次运行时设置为 true 可自动建表,生产环境建议手动建表后设为 false
    // repo.setCreateTableOnStartup(true);
    return repo;
    }
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
    // 配置表单登录
    .formLogin(form -> form
    .loginPage("/login")          // 自定义登录页
    .loginProcessingUrl("/doLogin") // 登录表单提交地址
    .defaultSuccessUrl("/home")    // 登录成功跳转
    .permitAll()
    )
    // 配置 remember-me 自动登录
    .rememberMe(remember -> remember
    .tokenRepository(persistentTokenRepository())  // 持久化 token 到数据库
    .tokenValiditySeconds(7 * 24 * 60 * 60)       // token 有效期:7 天(单位秒)
    .userDetailsService(userDetailsService)        // 用于根据用户名加载用户信息
    .rememberMeParameter("remember-me")           // 登录表单中记住我复选框的 name 属性
    .rememberMeCookieName("remember-me-cookie")   // 浏览器中存储的 cookie 名称
    )
    // 配置请求授权
    .authorizeHttpRequests(auth -> auth
    .requestMatchers("/login", "/css/", "/js/").permitAll()  // 公开资源
    .anyRequest().authenticated()                                // 其他请求需认证
    )
    // 退出登录配置
    .logout(logout -> logout
    .logoutSuccessUrl("/loginlogout")
    .deleteCookies("remember-me-cookie")  // 退出时清除 remember-me cookie
    .permitAll()
    );
    return http.build();
    }
    }
    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.stereotype.Service;
    import java.util.Collections;
    @Service
    public class CustomUserDetailsService implements UserDetailsService {
    // 模拟从数据库查询用户,实际项目中请注入 UserMapper/UserRepository
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 这里模拟一个用户,实际应从数据库查询
        if (!"admin".equals(username)) {
            throw new UsernameNotFoundException("用户不存在: " + username);
        }
        // 密码必须是 BCrypt 加密后的密文,明文 "123456" 对应的 BCrypt 密文
        String encodedPassword = "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy";
        return new User(
            "admin",
            encodedPassword,
            true,                // 账号是否可用
            true,                // 账号未过期
            true,                // 凭证未过期
            true,                // 账号未锁定
            Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
        );
    }
    }
    
    
    
        
        登录
    
    
    

    用户登录

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    @Controller
    public class HomeController {
    @GetMapping("/login")
    public String loginPage() {
        // 返回登录页面
        return "login";
    }
    @GetMapping("/home")
    public String homePage() {
    // 返回首页,只有登录成功后才能访问
    return "home";
    }
    }
    
    
    
        
        首页
    
    
    

    欢迎登录!

    你已经成功登录系统。

    如果勾选了"记住我",关闭浏览器后再次访问本页面将自动登录。

    退出登录
  • 免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

    相关文章

    更多

    精选合集

    更多

    大家都在玩

    热门话题

    大家都在看

    更多