当前位置: 首页 > news >正文

【Spring Security】认证(二)

Spring Security

  • 认证(Authentication)
    • 在代码中手动执行认证逻辑
    • 登出与会话清理
    • Session 与 Remember-Me 机制

认证(Authentication)

在代码中手动执行认证逻辑

通常,Spring Security 会自动通过 Filter 链来认证用户(例如表单登录、Basic Auth 等)。

但在一些场景下,我们希望:

  • 前端使用 JSON 登录
  • 或通过 外部接口/第三方认证
  • 或在登录逻辑中加入 自定义验证规则(验证码、短信、OAuth等)

这些情况下,我们需要 手动执行认证过程

在 Controller 中手动登录

假设我们有自定义的登录接口 /api/login

控制器代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/api")
public class AuthController {@Autowiredprivate AuthenticationManager authenticationManager;@PostMapping("/login")public String login(@RequestBody LoginRequest loginRequest) {// 构造 Authentication 对象(未认证状态)UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),loginRequest.getPassword());// 执行认证(会调用 UserDetailsService.loadUserByUsername)Authentication authentication = authenticationManager.authenticate(authenticationToken);// 认证成功后,将结果存入 SecurityContextSecurityContextHolder.getContext().setAuthentication(authentication);// 返回成功响应(这里简单返回用户名)return "登录成功,欢迎 " + authentication.getName();}
}

登录请求体类

public class LoginRequest {private String username;private String password;// Getter / Setterpublic String getUsername() { return username; }public void setUsername(String username) { this.username = username; }public String getPassword() { return password; }public void setPassword(String password) { this.password = password; }
}

配置类中暴露 AuthenticationManager Bean

在 Spring Security 5.7+,AuthenticationManager 不再自动暴露,需要我们手动注册:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;@Configuration
public class SecurityConfig {@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {return configuration.getAuthenticationManager();}
}

请求示例

请求:

POST /api/login
Content-Type: application/json{"username": "admin","password": "123456"
}

响应:

登录成功,欢迎 admin

常见用途

场景示例
前后端分离项目前端提交 JSON,后端验证后返回 JWT
移动端接口登录App 提交账号密码
管理员模拟登录管理员手动切换身份
外部系统同步认证外部系统调用认证接口获得凭证

登出与会话清理

默认登出机制

Spring Security 默认启用登出功能:

默认项默认值
请求路径/logout
请求方式POST(从 Spring Security 6.1 起默认如此)
登出后操作清除 SecurityContextHttpSession
默认跳转/login?logout

即:当用户发送 POST /logout 请求时,Security 自动执行登出操作,销毁认证状态并跳转回登录页。

整个 Logout 流程主要由以下组件组成:

组件作用
LogoutFilter负责拦截登出请求并触发登出流程
LogoutHandler执行登出具体逻辑(清理上下文、删除 Session、清理 Cookie 等)
LogoutSuccessHandler登出成功后的响应处理(跳转 / JSON)

默认过滤器:LogoutFilter

Spring Security 内部有一个专门的过滤器:

LogoutFilter↓SecurityContextLogoutHandler↓LogoutSuccessHandler

当用户访问 /logout 时,LogoutFilter 会:

  1. 调用 SecurityContextLogoutHandler
    • 删除认证信息 (SecurityContextHolder.clearContext())
    • 使 Session 失效 (session.invalidate())
    • 删除 “remember-me” token(如果启用)
  2. 调用 LogoutSuccessHandler:默认重定向到 /login?logout

自定义登出配置

可以在 SecurityFilterChain 中定制登出行为:

@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).formLogin(form -> form.loginPage("/login").loginProcessingUrl("/doLogin").permitAll()).logout(logout -> logout.logoutUrl("/doLogout") // 自定义登出路径.logoutSuccessUrl("/login?logout") // 成功后跳转路径.invalidateHttpSession(true) // 销毁 session.clearAuthentication(true) // 清除认证信息.deleteCookies("JSESSIONID") // 删除指定 Cookie.permitAll());return http.build();}
}

自定义 LogoutSuccessHandler(返回 JSON)

在前后端分离项目中,我们通常不希望重定向,而是返回一个 JSON 消息。

自定义实现类

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@Component
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {private final ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void onLogoutSuccess(HttpServletRequest request,HttpServletResponse response,Authentication authentication) throws IOException {response.setContentType("application/json;charset=UTF-8");response.setStatus(HttpServletResponse.SC_OK);Map<String, Object> result = new HashMap<>();result.put("success", true);result.put("message", "登出成功");response.getWriter().write(objectMapper.writeValueAsString(result));}
}

配置注册

@Autowired
private CustomLogoutSuccessHandler customLogoutSuccessHandler;@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(customLogoutSuccessHandler) // 使用自定义处理器.invalidateHttpSession(true).clearAuthentication(true).permitAll());return http.build();
}

请求示例

请求

POST /logout
Cookie: JSESSIONID=xxxxxx

响应

{"success": true,"message": "登出成功"
}

手动触发登出(在控制器中)

有时我们需要在业务逻辑中手动登出:

@GetMapping("/manualLogout")
public void manualLogout(HttpServletRequest request, HttpServletResponse response) {Authentication auth = SecurityContextHolder.getContext().getAuthentication();if (auth != null) {new SecurityContextLogoutHandler().logout(request, response, auth);}
}

Spring Security 登出时主要执行以下操作:

  1. 清除上下文SecurityContextHolder.clearContext()
  2. 使会话失效session.invalidate()
  3. 删除 Cookie(如果配置)
  4. 清除 remember-me token
  5. 触发登出成功处理器

登出后,SecurityContextHolder.getContext().getAuthentication() 会返回 null,表示用户已不再认证。

Session 与 Remember-Me 机制

认证状态的“持久化”问题

认证通过 ≠ 永久登录。

在用户登录成功后,Spring Security 会将用户的 Authentication 对象保存到 Session 中:

SecurityContextHolder.getContext().setAuthentication(authResult);

每次请求,过滤器都会从 Session 中取出 Authentication 放回 SecurityContext

因此:

动作结果
登录成功Authentication 写入 Session
访问受保护资源从 Session 读取认证信息
Session 失效认证信息消失 → 用户退出登录

Session 的存储与恢复流程

请求生命周期可以用下图表示:

① 登录成功↓
SecurityContextPersistenceFilter 保存认证信息↓
Session 保存 SecurityContext② 再次访问↓
SecurityContextPersistenceFilter 从 Session 中取出 SecurityContext↓
SecurityContextHolder 填充 Authentication

这意味着:

  • SecurityContext 实际是保存在 Session 中;
  • Session 是认证状态的容器

核心类与过滤器

组件作用
SecurityContextHolder当前线程的安全上下文(ThreadLocal 存储)
SecurityContextPersistenceFilter请求开始与结束时加载/清理 SecurityContext
HttpSessionSecurityContextRepository从 Session 中读写 SecurityContext

Session 管理配置

基本配置

http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 需要时创建 Session);
策略含义
ALWAYS总是创建 Session
IF_REQUIRED需要时创建(默认)
NEVER不创建,但可使用现有 Session
STATELESS无状态,不使用 Session(适合 JWT)

防止并发登录控制

Spring Security 可以控制同一用户账号的同时登录数量:

http.sessionManagement(session -> session.maximumSessions(1) // 同一账号最多只能登录 1 个 session.maxSessionsPreventsLogin(false) // 若为 true,则拒绝新登录;false 表示踢出旧 session);

推荐:企业后台管理系统通常设置为 1,防止账号被多处同时使用。

Remember-Me 概念理解

Remember-Me 是“持久化登录”的一种方案:即使 Session 过期、浏览器关闭,用户仍然能自动登录。

工作原理简图

① 用户勾选 “记住我” 登录成功↓
服务端生成 Remember-Me Token↓
浏览器保存 Cookie(token 值)↓
② 下次访问时↓
Cookie 被发送 → 后端验证 token → 自动登录

开启 Remember-Me

最基本配置如下:

http.rememberMe(remember -> remember.key("my-remember-key")          // 签名密钥,防止伪造.tokenValiditySeconds(7 * 24 * 60 * 60) // 有效期 7 天.rememberMeParameter("rememberMe") // 表单字段名.userDetailsService(userDetailsService) // 用于重新加载用户信息);

登录表单需有一个字段:

<input type="checkbox" name="rememberMe" value="true">

Remember-Me 的两种实现方式

方式说明
基于 Cookie(默认)Token 存储在 Cookie 中(签名加密)
基于数据库(持久化)Token 存在数据库表中,支持多设备登录
  1. 基于 Cookie 的实现(默认)

    使用 TokenBasedRememberMeServices

    token = Base64(username + ":" + expiryTime + ":" + md5(username + expiryTime + password + key))
    

    验证时:

    • 取出 cookie;
    • 解码;
    • 验证签名是否有效;
    • 若有效 → 自动加载用户信息。

    缺点:Cookie 可被盗取(安全风险较高)。

  2. 基于数据库的实现

    启用 PersistentTokenBasedRememberMeServices

    • 创建数据库表

      Spring Security 需要一个固定结构的表:

      CREATE TABLE persistent_logins (username VARCHAR(64) NOT NULL,series VARCHAR(64) PRIMARY KEY,token VARCHAR(64) NOT NULL,last_used TIMESTAMP NOT NULL
      );
      
    • 配置 JDBC TokenRepository

      @Bean
      public PersistentTokenRepository tokenRepository(DataSource dataSource) {JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();repo.setDataSource(dataSource);// repo.setCreateTableOnStartup(true); // 启动时自动建表(首次可启用)return repo;
      }
      
    • 注册到 SecurityConfig

      @Autowired
      private PersistentTokenRepository tokenRepository;http.rememberMe(remember -> remember.tokenRepository(tokenRepository).tokenValiditySeconds(14 * 24 * 60 * 60).userDetailsService(userDetailsService));
      

      优点:

      • 支持多端登录;
      • 可以在数据库中手动清除 Token;
      • 安全性高。

Remember-Me 过滤器流程

在过滤器链中,Remember-Me 对应:

RememberMeAuthenticationFilter

其作用是:

  1. 检查请求中是否有 Remember-Me Cookie;
  2. 验证 token;
  3. 如果验证通过,自动登录用户;
  4. 创建新的 Authentication
  5. 写入 SecurityContextHolder

手动清除 Remember-Me Cookie

当登出时,应同时清除 cookie(否则仍可自动登录):

http.logout(logout -> logout.deleteCookies("remember-me") // 删除 remember-me cookie.invalidateHttpSession(true).clearAuthentication(true));
http://www.dtcms.com/a/488386.html

相关文章:

  • 网站最常用字体网站关于我们的页面
  • Linux系统--信号(1--准备)
  • 怀柔营销型网站建设网站服务器租用价格 百度一下
  • 如何查找网站备案品牌开发者应考虑的因素
  • 充值网站建设旅游网络营销论文
  • 各大网站新闻热门软件排行榜
  • 追觅的想象空间:以技术为翼,向生态无垠
  • wap网站搭建品牌注册查询系统
  • 在线购物网站怎么做承德公司做网站
  • ptmalloc原理(简)
  • 优选算法之双指针:从原理到实战,解决数组与链表
  • 泰安网站建设企业门户网站静态模板
  • 做招聘网站的背景图片手机建网站 优帮云
  • 湖州网站建设湖州网站建设网站建设运营计划书
  • Spring的核心思想与注解
  • 洛阳响应式建站wordpress无法搜索插件
  • 幻灯片在什么网站做dw做的静态网站怎么分享链接
  • 做网站如何推销小公司做网站的好处
  • 力扣Hot100--106.对称二叉树
  • 胡恩全10.15作业
  • 网站推广需要多少钱教学网站开发源码
  • ACSM-CPT 8周冲刺每日学习计划(10/16–12/15)
  • 公司备案网站名称代理ip注册网站都通不过
  • 杭州建设银行网站智慧团建登录不上
  • 大型商城网站开发wordpress侧边栏加图片
  • Linux内核架构浅谈37-深入理解Linux页帧标志:从PG_locked到PG_dirty的核心原理与实践
  • 建设网站的功能及目的是什么wordpress好用的地图
  • 佛山找企业的网站WORDPRESS添加前台会员注册
  • 【完整源码+数据集+部署教程】 【零售和消费品&存货】条形码检测系统源码&数据集全套:改进yolo11-TADDH
  • 上海建网站公司排名常用的设计软件有哪些