当前位置: 代码网 > it编程>编程语言>Java > 基于Spring Security实现自动登录的示例代码

基于Spring Security实现自动登录的示例代码

2026年07月27日 Java 我要评论
一、自动登录原理自动登录的原理就是首次认证成功后,服务端给浏览器和数据库存储了一份token,浏览器将token存储在cookie上,下次访问时,浏览器会自动携带cookie到服务端,服务端拿着浏览器

一、自动登录原理

自动登录的原理就是首次认证成功后,服务端给浏览器和数据库存储了一份token,浏览器将token存储在cookie上,下次访问时,浏览器会自动携带cookie到服务端,服务端拿着浏览器的token去数据库查找,如果找到了就认为认证成功,否则失败。

二、创建持久化表结构

spring security 提供了 jdbctokenrepositoryimpl 来持久化 remember-me token,需要一张 persistent_logins 表:

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='记住我功能持久化登录信息表';

三、添加 maven 依赖

确保 pom.xml 中包含 spring security 和 spring boot starter web 依赖:

<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-security</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-web</artifactid>
</dependency>
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-jdbc</artifactid>
</dependency>
<dependency>
    <groupid>mysql</groupid>
    <artifactid>mysql-connector-java</artifactid>
    <scope>runtime</scope>
</dependency>

四、配置数据源

application.yml 中配置数据库连接:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/security_demo?usessl=false&servertimezone=utc
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.driver

五、spring security 配置类

核心配置类,开启 remember-me 功能并指定 token 持久化方式:

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("/login?logout")
.deletecookies("remember-me-cookie")  // 退出时清除 remember-me cookie
.permitall()
);
return http.build();
}
}

六、自定义 userdetailsservice

用于从数据库加载用户信息,remember-me 自动登录时需要根据用户名查询用户:

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"))
    );
}
}

七、登录页面(thymeleaf 模板)

src/main/resources/templates/login.html 中创建登录页,注意 remember-me 复选框的 name 必须与配置中的 remembermeparameter 一致:

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <title>登录</title>
</head>
<body>
<h2>用户登录</h2>
<form th:action="@{/dologin}" method="post">
    <div>
        <label>用户名:</label>
        <input type="text" name="username" required />
    </div>
    <div>
        <label>密码:</label>
        <input type="password" name="password" required />
    </div>
    <div>
        <!-- 记住我复选框,name 必须与 securityconfig 中的 remembermeparameter 一致 -->
        <label>
            <input type="checkbox" name="remember-me" value="true" /> 记住我(自动登录)
        </label>
    </div>
    <div>
        <button type="submit">登录</button>
    </div>
</form>
</body>
</html>

八、controller 控制器

提供登录页和首页的请求映射:

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";
}
}

九、首页模板

src/main/resources/templates/home.html 中:

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <title>首页</title>
</head>
<body>
<h1>欢迎登录!</h1>
<p>你已经成功登录系统。</p>
<p>如果勾选了"记住我",关闭浏览器后再次访问本页面将自动登录。</p>
<a th:href="@{/logout}" rel="external nofollow" >退出登录</a>
</body>
</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 过期了也能查到数据,岂不是不安全?

a:实际上,remember-me 的安全性不仅依赖 token 过期时间,更依赖 series(唯一序列号) + token 双字段机制。每次自动登录成功后,服务端都会生成一个新的 token 并更新到数据库和 cookie 中,旧的 token 立即失效。如果攻击者窃取了 cookie 并在合法用户之前使用,服务端会发现数据库中的 token 与 cookie 携带的 token 不一致,此时系统会判定 token 可能被盗用,直接删除该 series 对应的整条记录,让攻击者和合法用户都无法再自动登录,从而保护账户安全。

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 自动登录成功后,persistenttokenbasedremembermeservices 会调用 persistenttokenrepository.updatetoken() 方法,生成一个新的随机 token 值,并更新到数据库 persistent_logins 表的 token 字段中(series 保持不变)。
  • 客户端(浏览器)自动更新 cookie:服务端更新数据库后,会通过响应头 set-cookie 将新的 series + token 组合写入浏览器的 remember-me-cookie 中。浏览器收到响应后自动覆盖旧的 cookie,整个过程对用户完全透明。

简单来说,每次自动登录都是一次"一换一"的更新:服务端更新数据库中的 token,同时通过响应让浏览器更新 cookie 中的 token,两者始终保持一致。这样即使旧的 token 被窃取,在合法用户下一次自动登录后也会立即失效。

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("/login?logout")
    .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(唯一序列号),用于在 persistent_logins 表中定位记录。
  • 冒号后的部分token(登录令牌),用于验证本次自动登录的合法性。

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

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

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

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com