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

【Spring Security】Spring Security 密码编辑器

Spring Security

  • Spring Security 密码编辑器
    • Maven 依赖
    • 密码编码器
    • 密码编码器代理

Spring Security 密码编辑器

Maven 依赖

Maven 版本管理

Spring Security 和 Spring Framework 是 独立版本体系

Spring Security 示例版本:

<spring.security.version>5.3.4.RELEASE</spring.security.version>

Spring Framework 示例版本:

<spring.version>5.2.8.RELEASE</spring.version>

注意:Spring Security 与 Spring Framework 版本不必一致,但需要兼容。一般查看官方兼容矩阵即可。

Maven 依赖示例(传统 Spring)

典型 POM.XML 结构:

<dependencies><!-- Spring MVC --><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><!-- Spring Security 核心模块 --><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-core</artifactId><version>${spring.security.version}</version></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId><version>${spring.security.version}</version></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId><version>${spring.security.version}</version></dependency><!-- Servlet API --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!-- JSTL --><dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency>
</dependencies>

这种方式适合传统 Spring MVC 项目,手动管理模块版本。

Spring Boot 集成(推荐方式)

Spring Boot 提供了 starter 依赖,自动引入所有核心和常用模块:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

Spring Boot 示例 POM:

<dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Thymeleaf + Spring Security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity6</artifactId></dependency><!-- 测试支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency>
</dependencies>

优点:

  1. 自动引入 core + web + config 等依赖;
  2. 版本统一管理,无需手动设置 spring-security.version
  3. 支持 Spring Boot 自动配置,快速集成安全功能。

依赖管理技巧

  1. 版本统一:使用 <properties> 统一定义 spring.security.versionspring.version,方便升级。
  2. 按需引入扩展模块
    • LDAP 项目 → 添加 spring-security-ldap
    • SSO 项目 → 添加 spring-security-cas
    • OAuth2 项目 → 添加 spring-security-oauth2
  3. 避免冗余依赖
    • Web 项目无需引入 LDAP 或 ACL 模块,保持轻量化。
  4. 测试支持
    • 使用 spring-security-test 可轻松模拟用户认证、角色授权,便于单元测试。

密码编码器

Spring Security 5 之前,我们可以直接在内存中使用明文密码,比如:

UserDetails user = User.builder().username("user").password("user123") // 明文.roles("USER").build();

但是 Spring Security 5 之后,为了增强安全性:

  • 默认 不再支持明文密码
  • 所有密码都必须经过编码(加密)。
  • 如果你要使用明文密码,需要显式加上 {noop} 前缀:
.password("{noop}user123") // 表示明文

这种做法仅用于开发或测试环境,生产环境绝对不能用。

NoOpPasswordEncoder

{noop} 对应的是 Spring 内置的 NoOpPasswordEncoder,它只是直接返回原始密码,不做加密。

@Bean
protected UserDetailsService userDetailsService() {UserDetails user = User.builder().username("user").password("{noop}user123").roles("USER").build();return new InMemoryUserDetailsManager(user);
}
  • 优点:简单、方便测试。
  • 缺点:不安全、已废弃(deprecated),不适合生产环境。

推荐做法:使用密码编码器(PasswordEncoder)

Spring Security 提供了多种 密码编码器,最常用的是 BCryptPasswordEncoder

使用 BCryptPasswordEncoder 的示例:

@Bean
protected PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();
}@Bean
protected UserDetailsService userDetailsService() {UserDetails user = User.builder().username("user").password(passwordEncoder().encode("user123")) // 密码加密.roles("USER").build();UserDetails admin = User.builder().username("admin").password(passwordEncoder().encode("admin123")) // 密码加密.roles("USER", "ADMIN").build();return new InMemoryUserDetailsManager(user, admin);
}

测试案例:用户/管理员登录

  1. 引入 Spring Security、Thymeleaf 的相关依赖

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity6</artifactId>
    </dependency>
    
  2. src/main/resources/templates 目录下创建测试页面

    • 登录页(login.html

      <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org">
      <head><meta charset="UTF-8"><title>Login</title>
      </head>
      <body><h1>登录</h1><!-- 显示登录错误信息 --><div th:if="${param.error}" style="color: red;">用户名或密码错误</div><!-- 显示登出信息 --><div th:if="${param.logout}" style="color: green;">已成功登出</div><!-- 登录表单:Spring Security 会自动处理提交的 username 和 password --><form th:action="@{/login}" method="post"><div><label>用户名:</label><input type="text" name="username" required></div><div><label>密码:</label><input type="password" name="password" required></div><div><button type="submit">登录</button></div></form>
      </body>
      </html>
      
    • 首页(home.html

      <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org"xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
      <head><meta charset="UTF-8"><title>首页</title>
      </head>
      <body><h1>欢迎,<span sec:authentication="name"></span></h1> <!-- 显示当前用户名 --><p>你的角色:<span sec:authentication="authorities"></span></p> <!-- 显示角色 --><!-- 仅 ADMIN 可见的链接 --><div sec:authorize="hasRole('ADMIN')"><a th:href="@{/admin}">管理员页面</a></div><!-- 登出链接 --><form th:action="@{/logout}" method="post"><button type="submit">登出</button></form>
      </body>
      </html>
      
    • 管理员页面(admin/admin.html

      ADMIN 角色可访问:

      <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org">
      <head><meta charset="UTF-8"><title>管理员页面</title>
      </head>
      <body><h1>管理员专属页面</h1><a th:href="@{/home}">返回首页</a>
      </body>
      </html>
      
  3. 创建相关配置类

    • PasswordConfig 密码加密配置类

      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      import org.springframework.security.crypto.password.PasswordEncoder;@Configuration
      public class PasswordConfig {@Beanprotected PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
      }
      
    • UserConfig 用户配置类

      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      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.crypto.password.PasswordEncoder;
      import org.springframework.security.provisioning.InMemoryUserDetailsManager;@Configuration
      public class UserConfig {// 注入 PasswordConfig 中定义的 PasswordEncoderprivate final PasswordEncoder passwordEncoder;public UserConfig(PasswordConfig passwordConfig) {this.passwordEncoder = passwordConfig.passwordEncoder();}@Beanprotected UserDetailsService userDetailsService() {UserDetails user = User.builder().username("user").password(passwordEncoder.encode("user123")) // 密码加密.roles("USER").build();UserDetails admin = User.builder().username("admin").password(passwordEncoder.encode("admin123")) // 密码加密.roles("USER", "ADMIN").build();return new InMemoryUserDetailsManager(user, admin);}}
      
    • SecurityConfig 权限控制配置类

      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.web.SecurityFilterChain;@Configuration
      @EnableWebSecurity
      public class SecurityConfig {// 注入 UserConfig 和 PasswordConfig 中定义的 PasswordEncoder 和 UserDetailsServiceprivate final UserConfig userConfig;private final PasswordConfig passwordConfig;public SecurityConfig(UserConfig userConfig, PasswordConfig passwordConfig) {this.userConfig = userConfig;this.passwordConfig = passwordConfig;}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http// 配置登录页和权限规则.authorizeHttpRequests(auth -> auth.requestMatchers("/login", "/css/**").permitAll() // 登录页和静态资源允许匿名访问.requestMatchers("/admin/**").hasRole("ADMIN") // /admin/** 路径需要 ADMIN 角色.anyRequest().authenticated() // 其他路径需要认证)// 配置登录页(Thymeleaf 页面).formLogin(form -> form.loginPage("/login") // 自定义登录页路径.defaultSuccessUrl("/home", true) // 登录成功后跳转首页.failureUrl("/login?error=true") // 登录失败跳转)// 配置登出.logout(logout -> logout.logoutSuccessUrl("/login?logout=true") // 登出成功跳转);return http.build();}
      }
      
    • 三个配置类也可以合为一个配置类:

      package com.scarletkite.springsecuritydemo.security;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.User;
      import org.springframework.security.core.userdetails.UserDetails;
      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.provisioning.InMemoryUserDetailsManager;
      import org.springframework.security.web.SecurityFilterChain;@Configuration
      @EnableWebSecurity
      public class SecurityConfig {// 1. 定义 PasswordEncoder Bean@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 2. 定义 UserDetailsService Bean(直接调用当前类的 passwordEncoder() 方法)@Beanpublic UserDetailsService userDetailsService() {UserDetails user = User.builder().username("user").password(passwordEncoder().encode("user123")) // 直接调用本类的 passwordEncoder().roles("USER").build();UserDetails admin = User.builder().username("admin").password(passwordEncoder().encode("admin123")) // 直接调用本类的 passwordEncoder().roles("USER", "ADMIN").build();return new InMemoryUserDetailsManager(user, admin);}// 3. 删除构造器注入(不再需要,因为内部方法可直接调用)// 4. 定义 SecurityFilterChain Bean(配置安全规则)@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth.requestMatchers("/login", "/css/**").permitAll().requestMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated()).formLogin(form -> form.loginPage("/login").defaultSuccessUrl("/home", true).failureUrl("/login?error=true")).logout(logout -> logout.logoutSuccessUrl("/login?logout=true"));return http.build();}
      }
      
  4. 创建控制器(处理页面跳转)

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;@Controller
    public class PageController {// 登录页@GetMapping("/login")public String login() {return "login";}// 首页@GetMapping("/home")public String home() {return "home";}// 管理员页面@GetMapping("/admin")public String admin() {return "admin/admin";}
    }
    
  5. 在浏览器地址栏输入 localhost:8080/login 进行测试

BCrypt 密码结构解析

BCrypt 密码在内存中或数据库中存储的格式如下:

$2a$10$acaXGauv/3buNdwQWeOgu.iab3LLDclrH64xVMsSxd9Lp/otgUfMm

各字段含义:

字段说明
2a使用的编码算法,bcrypt
10算法强度(strength,cost factor)
前 22 个字符随机盐(salt)
后 31 个字符哈希后的密码(hashed password)

BCrypt 的特点:

  • 每次编码都使用随机盐(salt)
  • 相同的明文密码每次编码结果都不同
  • 提供强抗破解能力,适合生产环境

使用场景

  1. 内存用户(InMemoryUserDetailsManager)

    • 对开发或测试环境,使用 BCryptPasswordEncoder 也可以。
  2. 数据库用户(Persistent storage)

    • 推荐在用户注册时先用 BCryptPasswordEncoder.encode() 加密密码,再存入数据库。

    • 登录验证时,Spring Security 会自动调用 matches() 方法比较原始密码和加密后的密码。

      passwordEncoder.matches(rawPassword, encodedPassword)
      

密码编码器代理

什么是 DelegatingPasswordEncoder?

DelegatingPasswordEncoderSpring Security 的密码编码器代理(Delegator)

它的作用是:

“根据密码前缀(id)动态选择合适的密码加密算法。”

比如数据库中存了三种不同类型的密码:

{bcrypt}$2a$10$Fbp...
{noop}123456
{pbkdf2}9ddae64d...

Spring Security 看到 {bcrypt} 前缀,就自动使用 BCryptPasswordEncoder 去验证;
看到 {pbkdf2} 就用 PBKDF2 算法。

总结:

DelegatingPasswordEncoder = “密码算法调度中心”,用来统一管理多种加密方式。

为什么需要它?

背景问题:Spring Security 5 之前,密码一般只用一种算法,比如:

@Bean
PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();
}

但随着项目演进,你可能遇到这些问题:

  • 老系统用 MD5 或 SHA 存密码;
  • 新系统想改用更安全的 BCrypt;
  • 不同模块或第三方用户来源不同;
  • 想平滑过渡,不影响旧用户登录。

于是 Spring Security 引入了 DelegatingPasswordEncoder 来统一管理不同加密算法的兼容性与迁移。

内部原理结构

它的核心思想就是一个 Map + 默认算法 ID

public class DelegatingPasswordEncoder implements PasswordEncoder {private final String idForEncode; // 当前默认算法private final Map<String, PasswordEncoder> idToPasswordEncoder; // 所有可用算法private final PasswordEncoder defaultPasswordEncoderForMatches; // 匹配时默认使用的// encode() 时加上 {id}// matches() 时根据 {id} 选择对应 encoder
}

Spring 提供了一个工厂方法:

PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

这行代码创建了一个默认配置的 DelegatingPasswordEncoder,支持以下算法:

ID对应编码器
bcryptBCryptPasswordEncoder
ldapLdapShaPasswordEncoder
MD4Md4PasswordEncoder
MD5MessageDigestPasswordEncoder
noopNoOpPasswordEncoder
pbkdf2Pbkdf2PasswordEncoder
scryptSCryptPasswordEncoder
sha256StandardPasswordEncoder
argon2Argon2PasswordEncoder

使用方式

  1. 创建并使用默认 DelegatingPasswordEncoder

    @Bean
    public PasswordEncoder passwordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
    

    它的默认算法是 {bcrypt}

    String encoded = passwordEncoder().encode("123456");
    // 输出类似:{bcrypt}$2a$10$NPMD...
    

    会发现,前缀 {bcrypt} 自动加上了

    验证时,Spring Security 会:

    1. 读取密码前缀 {bcrypt}
    2. 从内部 Map 查找对应编码器
    3. 调用 matches() 进行验证
  2. 自定义 DelegatingPasswordEncoder

    比如,想默认用 PBKDF2,但仍然支持旧的 BCrypt:

    @Bean
    public PasswordEncoder passwordEncoder() {String idForEncode = "pbkdf2";Map<String, PasswordEncoder> encoders = new HashMap<>();encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());encoders.put("bcrypt", new BCryptPasswordEncoder());encoders.put("noop", NoOpPasswordEncoder.getInstance());return new DelegatingPasswordEncoder(idForEncode, encoders);
    }
    

    效果:

    • 新注册用户 → PBKDF2 编码(前缀 {pbkdf2}
    • 老用户若用 {bcrypt} → 仍然能匹配
    • 临时测试账号可用 {noop} 明文密码
  3. 旧密码无前缀怎么办?

    如果数据库中的密码没有 {id} 前缀,比如旧系统存的是纯 BCrypt:

    可以设置一个默认匹配策略:

    DelegatingPasswordEncoder delegatingPasswordEncoder =(DelegatingPasswordEncoder) PasswordEncoderFactories.createDelegatingPasswordEncoder();// 设置一个默认匹配器,当没有前缀时使用 bcrypt
    delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder());
    

工作流程图

       ┌──────────────┐│ encode("123")│└──────┬───────┘↓+--------------------------+| DelegatingPasswordEncoder|+--------------------------+| idForEncode = "bcrypt"   || idToPasswordEncoder = {  ||   bcrypt → BCryptEncoder ||   pbkdf2 → Pbkdf2Encoder ||   noop → NoOpEncoder     || }                        |+--------------------------+↓输出 {bcrypt}$2a$10$...

验证过程:

matches(raw, "{bcrypt}$2a$10$...")
→ 提取 {bcrypt}
→ 找出 BCryptPasswordEncoder
→ 调用 matches()
→ 返回 true / false

前面的 PasswordConfig 配置类可以改为:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;import java.util.HashMap;
import java.util.Map;@Configuration
public class PasswordConfig {@Beanpublic PasswordEncoder passwordEncoder() {String idForEncode = "bcrypt";Map<String, PasswordEncoder> encoders = new HashMap<>();encoders.put("bcrypt", new BCryptPasswordEncoder());encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());encoders.put("argon2", new Argon2PasswordEncoder(10, 8, 1, 16, -1));encoders.put("noop", NoOpPasswordEncoder.getInstance());DelegatingPasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);passwordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder());return passwordEncoder;}}

DelegatingPasswordEncoder 是 Spring Security 密码策略的核心。

它让我们可以:

  • 安全地管理多种密码算法;
  • 平滑升级加密方案;
  • 保持老用户可登录;
  • 确保系统的密码验证逻辑统一、可扩展、可演化。
http://www.dtcms.com/a/473349.html

相关文章:

  • MCU ADC外设工作原理介绍
  • k8s的ymal文件
  • 杭州公司建设网站网站建设标签
  • 博客系统小笔记
  • 后端开发和软件开发有什么区别
  • 分布式专题——41 RocketMQ集群高级特性
  • 自然语言处理分享系列-词语和短语的分布式表示及其组合性(一)
  • 从0到1实现鸿蒙智能设备状态监控:轻量级架构、分布式同步与MQTT实战全解析
  • RWKV架构讲解
  • Docker 镜像维护指南:从配置优化到 MySQL 实战运行
  • 电视盒子助手开心电视助手 v8.0 删除电视内置软件 电视远程控制ADB去除电视广告
  • 【完整源码+数据集+部署教程】 航拍杂草检测与分类系统源码和数据集:改进yolo11-RVB-EMA
  • My SQL--创建数据库、表
  • mysql高可用架构之MHA部署(三)——故障转移后邮件告警配置(保姆级)
  • 做酒的网站有哪些jsp获取网站域名
  • OpenCV(八):NumPy
  • 小微宝安网站建设有哪些做分析图用的网站
  • RabbitMQ 核心概念解析
  • 开发实战 - ego商城 - 2 nodejs搭建后端环境
  • 基于Java Swing的智能数据结构可视化系统 | 支持自然语言交互的AI算法助手
  • QQmusic sign值逆向实战 - Webpack打包分析
  • 城乡建设部网站首页网站建设公司应该怎么做推广
  • Linux环境下Hive4.0.1(最新版本)部署
  • dolphinscheduler之hivecli 任务
  • spark3访问低版本hive填坑记
  • 池化 (Pooling) 学习笔记
  • LeetCode160.相交链表【最通俗易懂版双指针】
  • Neo4j+Gephi制作社区检测染色图
  • 毕业设计代做网站机械工信部网站备案流程
  • aws ec服务器设置密码登录,ec服务器root登录 aws服务器初始化配置