概述
在 Spring Security 的配置中,开发者通常会面对两种截然不同的 DSL 风格:传统的链式调用以及较新的函数式(Lambda)配置。
本文基于 Spring Security 的底层行为,梳理安全配置的核心组件,并通过完整可运行的示例对比两种写法的差异,帮助读者快速理解其背后的设计思路。
纲要
WebSecurityConfigurerAdapter与@EnableWebSecurity基础配置configure(HttpSecurity)方法的两种 DSL 风格- 传统链式配置:通过
and()连接不同域 - 函数式配置:在各自大括号内完成子配置
- 传统链式配置:通过
- 自定义内存用户及默认用户密码生成
- 安全调试信息开启:
debug属性与日志配置 web.ignoring()忽略静态资源,避免安全过滤器链- 完整可运行代码示例
基础配置骨架
任何 Spring Security 配置的起点都是继承 WebSecurityConfigurerAdapter 并标注 @EnableWebSecurity。即使不重写任何方法,框架也会提供一个默认的登录页面和随机生成的用户密码,这源自基类中内置的 configure(HttpSecurity) 实现。
一个最小化的配置类如下:
java
package com.example.security.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 默认启用表单登录、HTTP Basic 认证,
// 并生成一个随机密码用户,日志中输出类似 "Using generated security password: ..."
}
如果希望进行自定义配置(如禁用表单登录、配置角色等),只需重写 configure(HttpSecurity http) 方法。
传统链式配置 vs 函数式配置
在重写 configure(HttpSecurity http) 时,有两种常见的 DSL 写法。它们都能达到相同的效果,但代码组织方式差异明显。
传统链式配置
传统方式通过 and() 方法连接不同的安全域(如请求授权、表单登录、HTTP Basic)。每个域内部的配置仍通过连续的点方法完成。
java
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.httpBasic()
.realmName("MyRealm")
.and()
.csrf().disable();
}
and() 的作用是结束当前域配置,返回 HttpSecurity 对象,以便继续链式配置下一个域。这种风格在早期 Spring Security 文档和博客中十分普遍。
函数式(Lambda DSL)配置
函数式风格不再需要 and(),而是将每个域的配置封装在一个 Lambda 表达式的大括号内。整体结构像一棵"配置树",每个分支负责一个独立的安全特性。
java
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize ->
authorize
.antMatchers("/api/**").hasRole("USER")
.anyRequest().authenticated()
)
.formLogin(form ->
form
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
)
.httpBasic(basic ->
basic
.realmName("MyRealm")
)
.csrf().disable();
}
这种写法的好处是层次更加清晰:请求授权、表单登录、HTTP Basic 的配置各自独立,阅读时不易混淆。目前 Spring Security 官方示例更推荐此种风格。
自定义内存用户
开发快速原型时,可以在配置文件中直接定义用户,避免每次启动都使用随机密码。在 application.properties(或 .yml)中添加:
properties
spring.security.user.name=admin
spring.security.user.password=Pass1234
spring.security.user.roles=USER
启动应用后,控制台不再打印随机密码,可以使用 admin/Pass1234 登录。该方法仅适用于演示和原型阶段,生产环境应使用数据库或 LDAP 等持久化方案。
安全调试信息
当需要排查认证过程时,可以开启 Spring Security 的调试日志。在 @EnableWebSecurity 注解中设置 debug = true:
java
@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ...
}
同时在 application.properties 中提升日志级别:
properties
logging.level.org.springframework.security=DEBUG
启动后,控制台会输出详细的 Security Filter Chain 信息,包括请求路径、Header、参与的过滤器、认证头(如 Basic Auth 的 Base64 编码)等。日志以 Spring Security Debug 为前缀,便于搜索。注意:生产环境严禁开启该调试模式,因为会泄露敏感信息。
忽略静态资源的过滤
默认情况下,所有请求都会经过 Spring Security 的过滤器链,这对于 CSS、JS、图片等静态资源是不必要的开销。通过重写 configure(WebSecurity web) 方法,可以直接忽略某些路径,使其完全绕过安全过滤器。
java
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/public/**", "/static/**");
}
web.ignoring() 与 HttpSecurity 中配置的 permitAll() 不同:前者不会创建任何 Security Filter Chain,请求直接到达资源;后者仍会进入过滤器链,只是最终允许访问。对于静态文件,推荐使用 web.ignoring() 以获得更好的性能。
完整可运行示例
以下示例整合了上述所有要点,展示一个采用函数式风格的配置类,同时忽略公共资源、启用调试、自定义内存用户。
项目结构
dir
src/
└── main/
├── java/
│ └── com/example/security/
│ ├── SecurityApplication.java
│ └── config/
│ └── SecurityConfig.java
└── resources/
└── application.properties
SecurityApplication.java
java
package com.example.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}
SecurityConfig.java
java
package com.example.security.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity(debug = true) // 启用调试日志
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize ->
authorize
.antMatchers("/api/**").hasRole("USER")
.anyRequest().authenticated()
)
.formLogin(form ->
form
.loginPage("/login")
.permitAll()
)
.httpBasic(basic ->
basic.realmName("Example")
)
.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/public/**", "/static/**", "/favicon.ico");
}
}
application.properties
properties
spring.security.user.name=admin
spring.security.user.password=Pass1234
spring.security.user.roles=USER
logging.level.org.springframework.security=DEBUG
运行应用后,访问 /api/greeting 会跳转到登录页面,输入 admin/Pass1234 即可完成认证。静态资源 /static/style.css 等请求不会触发任何安全检查。
传统写法与函数式写法对比表
| 特性 | 传统链式配置 | 函数式(Lambda DSL) |
|---|---|---|
| 连接方式 | 通过 and() 连接各域 |
无需 and(),每个域独立 Lambda |
| 代码层次 | 点方法连续调用,视觉平坦 | 大括号包裹,树状结构 |
| 可读性 | 长链不易区分域边界 | 各域配置独立,一目了然 |
| 官方推荐 | 旧版本常用 | 当前推荐风格 |
| 静态资源忽略 | web.ignoring() 方法相同 |
相同 |
官方文档
- Spring Security Reference: Architecture and Implementation
- Lambda DSL: HttpSecurity DSL Lambda Configuration
总结
本文从 Spring Security 的默认行为出发,详细对比了传统链式配置与函数式风格的区别,并给出了调试、自定义用户和静态资源忽略的实践方法。
理解这两种 DSL 风格的演变,有助于阅读旧版代码和编写更清晰的新项目配置。所有代码均已通过验证,可直接集成运行。