Spring Security 通过一系列注解简化了安全配置,我们将深入探讨Spring Security框架的17个关键应用场景,包括认证、授权、OAuth2、CSRF保护等。每个案例都配有详细的时序图和代码示例,旨在帮助开发者全面理解并有效利用Spring Security的强大功能,以构建更安全、更可靠的应用程序。
肖哥弹架构 跟大家“弹弹” 关注公号回复 ‘mvcc’ 获得手写数据库事务代码
欢迎 点赞,关注,评论。
关注公号Solomon肖哥弹架构获取更多精彩内容
历史热点文章
- 解锁大语言模型参数:零基础掌握大型语言模型参数奥秘与实践指南
- 高性能连接池之HikariCP框架分析:高性能逐条分解(架构师篇)
- 缓存雪崩/穿透/击穿/失效原理图/14种缓存数据特征+10种数据一致性方案
- Java 8函数式编程全攻略:43种函数式业务代码实战案例解析(收藏版)
- 一个项目代码讲清楚DO/PO/BO/AO/E/DTO/DAO/ POJO/VO
- 17个Mybatis Plugs注解:Mybatis Plugs插件架构设计与注解案例(必须收藏)
Spring Security 常用应用场景介绍:
- 认证(Authentication)
- 应用场景:确保只有经过验证的用户才能访问应用程序。
- 授权(Authorization)
- 应用场景:控制用户对特定资源的访问权限,如角色基础的访问控制。
- 表单登录(Form-Based Login)
- 应用场景:为用户提供登录表单,处理登录请求和重定向。
- HTTP 基本认证(HTTP Basic Authentication)
- 应用场景:为 RESTful API 或其他服务提供基础的用户名和密码认证。
- OAuth2 和 OpenID Connect
- 应用场景:支持现代的授权框架,适用于需要第三方应用认证的场景。
- CSRF 保护(CSRF Protection)
- 应用场景:防止跨站请求伪造攻击,保护 Web 应用程序的安全。
- 密码编码(Password Encoding)
- 应用场景:安全地存储用户密码,防止密码泄露。
- 方法级安全性(Method Security)
- 应用场景:控制对特定方法或 Bean 属性的访问,实现细粒度的安全控制。
- 异常处理(Exception Handling)
- 应用场景:自定义安全相关的异常处理,如认证失败、授权失败。
- 记住我(Remember-Me)
- 应用场景:为用户提供持久的登录状态,方便用户再次访问。
- 预授权(Pre-Invocation)
- 应用场景:在方法执行前进行安全检查,确保方法调用的安全性。
- 表达式支持(Expression-Based)
- 应用场景:使用 Spring Expression Language (SpEL) 实现复杂的安全逻辑。
- 安全上下文(Security Context)
- 应用场景:管理和检索认证信息,如获取当前认证用户。
- 安全过滤器链(Security Filter Chain)
- 应用场景:处理 HTTP 请求的安全检查,如认证、授权。
- 用户详细信息服务(UserDetailsService)
- 应用场景:自定义用户认证信息的加载逻辑,如从数据库加载用户数据。
- 多因素认证(Multi-Factor Authentication)
- 应用场景:增加额外的安全层,如短信验证码、电子邮件确认。
- 匿名访问(Anonymous Access)
- 应用场景:允许未认证的用户访问某些公共资源。
Spring Security 场景案例
1、认证(Authentication)
业务场景: 一个在线书店系统,用户需要登录后才能查看订单和购买书籍。
业务时序图
- 用户(User) 访问登录页面。
- 表单登录认证过滤器(FormLoginAuthenticationFilter) 显示登录表单。
- 用户填写凭据并提交。
- 表单登录认证过滤器(FormLoginAuthenticationFilter) 调用 认证管理器(AuthenticationManager) 的
attemptAuthentication
方法。 - 认证管理器(AuthenticationManager) 请求 用户详细信息服务(UserDetailsService) 根据用户名加载用户信息。
- 用户详细信息服务(UserDetailsService) 调用自定义的 CustomUserDetailsService 获取用户详细信息。
- CustomUserDetailsService 返回用户详细信息给 认证管理器(AuthenticationManager) 。
- 认证管理器(AuthenticationManager) 请求 密码编码器(PasswordEncoder) 对用户输入的密码进行编码。
- 密码编码器(PasswordEncoder) 将编码后的密码与存储的密码进行比较。
- 认证管理器(AuthenticationManager) 根据比较结果决定认证是否成功。
- 表单登录认证过滤器(FormLoginAuthenticationFilter) 根据认证结果重定向用户到主页或显示错误。
- 认证信息被设置到 SecurityContextHolder 中。
Spring Security 配置
首先,我们需要配置Spring Security来处理用户的登录请求。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.permitAll()
.and()
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("password")).roles("USER")
.and()
.withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
业务逻辑代码
接下来,我们创建一个服务来处理用户的登录逻辑。
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.stereotype.Service;
@Service
public class AuthService {
@Autowired
private AuthenticationManager authenticationManager;
/**
* 用户登录方法
*
* @param username 用户名
* @param password 密码
* @return 登录成功返回 true,否则返回 false
*/
public boolean authenticateUser(String username, String password) {
try {
// 调用 Spring Security 的认证管理器进行认证
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
// 将认证信息放入安全上下文中
authenticationManager.authenticate(authentication);
// 认证成功
return true;
} catch (Exception e) {
// 认证失败
return false;
}
}
}
登录控制器
最后,我们需要一个控制器来处理用户的登录请求。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/home")
public String home() {
return "home";
}
}
登录页面 (login.html)
<!DOCTYPE html>
<html>
<head>
<title>Login Page</title>
</head>
<body>
<form action="/login" method="post">
<div>
<label>Username: <input type="text" name="username"/></label>
</div>
<div>
<label>Password: <input type="password" name="password"/></label>
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
</body>
</html>
2、授权(Authorization)
业务场景: 一个企业资源规划(ERP)系统,其中不同用户根据其角色拥有不同的访问权限。例如,只有财务部门的员工才能访问薪资信息。
业务时序图
- 用户(User) 请求受保护的资源。
- 过滤器链代理(FilterChainProxy) 处理安全过滤器链。
- 认证管理器(AuthenticationManager) 验证用户身份。
- 安全上下文持有器(SecurityContextHolder) 加载认证信息。
- 自定义用户详细信息服务(CustomUserDetailsService) 从数据库或其他存储中加载用户的权限和角色。
- 认证管理器(AuthenticationManager) 将用户的权限和角色返回给安全上下文。
- 安全过滤器链(FilterChainProxy) 检查是否有方法级安全性注解,如
@PreAuthorize
或@Secured
。 - 方法安全拦截器(MethodSecurityInterceptor) 检查注解并评估表达式。
- 基于表达式的访问控制列表(ExpressionBasedAccessControlList) 根据定义的SpEL表达式计算访问决策。
- 方法安全拦截器(MethodSecurityInterceptor) 根据访问决策允许或拒绝访问。
- 过滤器链代理(FilterChainProxy) 根据方法安全拦截器的结果,授予用户访问权限或返回403错误。
Spring Security 配置
首先,我们需要配置Spring Security来处理授权请求。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/hr/**").hasRole("HR")
.antMatchers("/finance/**").hasAuthority("FINANCE_READ")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.and()
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN")
.and()
.withUser("hr").password(passwordEncoder().encode("hr")).roles("HR")
.and()
.withUser("finance").password(passwordEncoder().encode("finance")).authorities("FINANCE_READ");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
业务逻辑代码
接下来,我们创建一个服务来处理访问薪资信息的授权逻辑。
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.stereotype.Service;
@Service
public class FinanceService {
/**
* 获取部门薪资信息
*
* @param departmentId 部门ID
* @return 薪资信息
*/
@PostAuthorize("returnObject != null and returnObject.departmentId == authentication.principal.departmentId")
public Salary getDepartmentSalary(int departmentId) {
// 模拟从数据库获取薪资信息
return new Salary(departmentId, 50000);
}
}
class Salary {
private int departmentId;
private double amount;
public Salary(int departmentId, double amount) {
this.departmentId = departmentId;
this.amount = amount;
}
public int getDepartmentId() {
return departmentId;
}
public double getAmount() {
return amount;
}
}
控制器
最后,我们需要一个控制器来处理用户的请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FinanceController {
@Autowired
private FinanceService financeService;
@GetMapping("/finance/{departmentId}/salary")
public Salary getSalary(@PathVariable int departmentId) {
return financeService.getDepartmentSalary(departmentId);
}
}
3、表单登录(Form-Based Login)
业务场景: 一个在线图书馆系统,用户需要通过填写登录表单来访问他们的借阅历史和个人信息。
业务时序图
- 用户(User) 访问登录页面。
- 表单登录认证过滤器(FormLoginAuthenticationFilter) 显示登录表单。
- 用户填写凭据并提交。
- 表单登录认证过滤器(FormLoginAuthenticationFilter) 调用 认证管理器(AuthenticationManager) 的
attemptAuthentication
方法。 - 认证管理器(AuthenticationManager) 请求 用户详细信息服务(UserDetailsService) 根据用户名加载用户信息。
- 用户详细信息服务(UserDetailsService) 调用 密码编码器(PasswordEncoder) 对数据库中的密码进行编码。
- 密码编码器(PasswordEncoder) 将编码后的密码返回给认证管理器。
- 认证管理器(AuthenticationManager) 根据比较结果决定认证是否成功。
- 表单登录认证过滤器(FormLoginAuthenticationFilter) 根据认证结果设置 安全上下文持有器(SecurityContextHolder) 。
- 安全上下文持有器(SecurityContextHolder) 根据认证结果重定向用户到主页或显示错误。
Spring Security 配置
首先,我们需要配置Spring Security来处理表单登录请求。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home", "/register").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.permitAll()
.and()
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("user").password(passwordEncoder().encode("password")).roles("USER")
.and()
.withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
登录控制器
接下来,我们创建一个控制器来处理登录页面的请求。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/home")
public String home() {
return "home";
}
}
登录页面 (login.html)
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<form action="/login" method="post">
<div>
<label>Username: <input type="text" name="username"/></label>
</div>
<div>
<label>Password: <input type="password" name="password"/></label>
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
</body>
</html>
Home页面 (home.html)
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Welcome to the Library System</h1>
<p>Access your borrowing history and personal information.</p>
</body>
</html>
配置静态资源
确保Spring Boot项目能够正确地服务静态资源,如HTML页面。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
}
}
4、HTTP 基本认证(HTTP Basic Authentication)
业务场景: 一个内部API服务,用于提供员工薪资信息。只有经过身份验证的员工才能访问此API。
业务时序图
- 用户(User) 在浏览器(Browser)中输入需要访问的 URL。
- 浏览器(Browser) 发送 HTTP 请求到服务器(Server)。
- 服务器(Server) 通过 HttpBasicAuthenticationFilter 发起 HTTP 基本认证挑战。
- HttpBasicAuthenticationFilter 向用户提示输入凭据。
- 用户(User) 在浏览器中输入用户名和密码。
- 浏览器(Browser) 在随后的请求中将凭据发送到服务器,包含在 HTTP 头的 Authorization 字段中。
- HttpBasicAuthenticationFilter 调用 AuthenticationManager 进行认证。
- AuthenticationManager 请求 UserDetailsService 根据用户名加载用户信息。
- UserDetailsService 调用 PasswordEncoder 对数据库中的密码进行编码。
- PasswordEncoder 将编码后的密码返回给认证管理器。
- AuthenticationManager 根据比较结果决定认证是否成功。
- HttpBasicAuthenticationFilter 根据认证结果向服务器发送响应,可能是 200 OK 或 401 Unauthorized。
- 服务器(Server) 将响应返回给浏览器,浏览器显示响应内容或显示错误信息。
Spring Security 配置
首先,我们需要配置Spring Security以启用HTTP基本认证。
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.config.http.SessionCreationPolicy;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/salaries/**").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic() // 启用HTTP基本认证
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 由于是REST API,不需要session
http.csrf().disable(); // 禁用CSRF保护
}
@Bean
@Override
public UserDetailsService userDetailsService() {
// 这里使用内存中的用户详情服务,生产环境应该使用数据库或其他服务
return new InMemoryUserDetailsManager(
User.withUsername("employee").password("{noop}password").authorities("SALARY_READ").build()
);
}
}
业务逻辑代码
创建一个控制器来处理薪资信息的请求。
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class SalaryController {
@GetMapping("/salaries")
@PreAuthorize("hasAuthority('SALARY_READ')")
public ResponseEntity<Double> getSalary() {
// 这里应该包含从数据库或其他存储获取薪资信息的逻辑
double salary = 50000; // 模拟薪资数据
return ResponseEntity.ok(salary);
}
}
测试HTTP基本认证
为了测试HTTP基本认证,你可以使用工具如Postman或curl。以下是使用curl的示例:
curl -u employee:password -X GET http://localhost:8080/api/salaries
这个命令使用用户名“employee”和密码“password”来访问薪资信息API。
说明
- 配置类:配置了HTTP基本认证和权限检查。
- 控制器:提供了一个端点来获取薪资信息,该端点使用了
@PreAuthorize
注解来确保只有具有SALARY_READ
权限的用户可以访问。 - 测试:使用curl命令行工具进行基本认证测试。
5、OAuth2 和 OpenID Connect
业务场景: 一个社交网络平台允许用户通过他们的Google账号进行登录。
业务时序图
- 用户(User) 尝试访问应用程序(Application)。
- 应用程序(Application) 重定向用户到身份提供者(Identity Provider)以启动认证。
- 身份提供者(Identity Provider) 接收到认证请求,并提示用户输入凭据。
- 用户(User) 提供凭据进行认证。
- 身份提供者(Identity Provider) 验证用户凭据,并显示同意屏幕,请求用户授权应用程序访问其数据。
- 用户(User) 同意授权。
- 身份提供者(Identity Provider) 将授权码返回给应用程序。
- 应用程序(Application) 将授权码发送到授权服务器(Authorization Server)以换取访问令牌。
- 授权服务器(Authorization Server) 验证授权码并返回访问令牌给应用程序。
- 应用程序(Application) 使用访问令牌请求资源服务器(Resource Server)以获取资源。
- 资源服务器(Resource Server) 验证访问令牌并返回请求的资源数据给应用程序。
- 应用程序(Application) 将资源数据展示给用户。
Spring Security 配置
首先,我们需要配置Spring Security以启用OAuth2客户端支持。
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder;
@Configuration
@EnableWebSecurity
public class OAuth2Config extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login();
}
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
ClientRegistration googleRegistration = Builder
aClientRegistration()
.withRegistrationId("google")
.withClientName("Google")
.withClientSecret("{client-secret}")
.withAuthorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.withRedirectUriTemplate("{baseUrl}/oauth2/callback/{registrationId}")
.withScope("openid", "profile", "email")
.withClientAuthenticationMethod(ClientAuthenticationMethod.POST)
.withAuthorizationUri("https://accounts.google.com/o/oauth2/v2/auth")
.withTokenUri("https://oauth2.googleapis.com/token")
.withUserInfoUri("https://openidconnect.googleapis.com/v1/userinfo")
.build();
return new InMemoryClientRegistrationRepository(googleRegistration);
}
}
业务逻辑代码
创建一个控制器来处理用户资料的请求。
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/profile")
public String getUserProfile(@AuthenticationPrincipal OidcUser principal) {
return "Hello, " + principal.getFullName() + "!";
}
}
说明
- 配置类:配置了Spring Security以使用OAuth2客户端。
- 控制器:提供了一个端点来获取通过OAuth2认证的用户资料。
6、CSRF 保护(CSRF Protection)
业务场景: 一个在线购物网站,需要确保所有的交易请求都是由用户主动发起的,以防止跨站请求伪造攻击。
业务时序图
- 用户(User) 请求页面。
- 浏览器(Browser) 向服务器(Server)发送 HTTP GET 请求。
- 服务器(Server) 接收请求并由应用程序(Application)生成一个 CSRF 令牌。
- 应用程序(Application) 将生成的 CSRF 令牌发送回服务器。
- 服务器(Server) 将带有 CSRF 令牌的页面发送回浏览器。
- 浏览器(Browser) 接收带有 CSRF 令牌的页面并展示给用户。
- 用户(User) 提交表单。
- 浏览器(Browser) 将带有 CSRF 令牌的 HTTP POST 请求发送到服务器。
- CSRF 令牌生成器(CSRF Token Generator) 验证接收到的 CSRF 令牌。
- 应用程序(Application) 确认 CSRF 令牌有效。
- 服务器(Server) 对 POST 请求做出响应。
Spring Security 配置
首先,我们需要配置Spring Security以启用CSRF保护。
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRepository;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.and()
.logout()
.permitAll()
.and()
.csrf() // 启用CSRF保护
.csrfTokenRepository(csrfTokenRepository()); // 自定义CSRF令牌存储
}
@Bean
public CsrfTokenRepository csrfTokenRepository() {
// 使用Cookie存储CSRF令牌
return CookieCsrfTokenRepository.withHttpOnlyFalse();
}
}
业务逻辑代码
创建一个控制器来处理购物车和订单的请求。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class ShoppingCartController {
@GetMapping("/cart")
public String showCart() {
return "cart"; // 显示购物车页面
}
@PostMapping("/place-order")
public String placeOrder() {
// 处理订单逻辑
return "order-confirmation"; // 显示订单确认页面
}
}
说明
- 配置类:配置了Spring Security以使用CSRF保护,使用
CookieCsrfTokenRepository
来存储CSRF令牌。 - 控制器:提供了两个端点,一个用于显示购物车,另一个用于处理订单。这两个操作都需要用户已经通过认证。
7、密码编码(Password Encoding)
业务场景: 用户注册和密码重置功能,需要确保存储在数据库中的密码是加密的,以提高安全性。
业务时序图
- 用户(User) 在浏览器(Browser)中输入密码。
- 浏览器(Browser) 将包含密码的注册表单提交到服务器(Server)。
- 服务器(Server) 请求密码编码器(PasswordEncoder)对密码进行编码。
- 密码编码器(PasswordEncoder) 对用户输入的密码进行加密处理。
- 密码编码器(PasswordEncoder) 将加密后的密码返回给服务器。
- 服务器(Server) 将加密后的密码存储到数据库(Database)。
- 数据库(Database) 确认密码已存储,并通知服务器。
- 服务器(Server) 向浏览器发送注册成功的响应。
Spring Security 配置
首先,我们需要配置Spring Security以使用密码编码器。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/register", "/reset-password").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home", true)
.and()
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser("user").password(passwordEncoder().encode("password")).roles("USER");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
业务逻辑代码
创建一个服务来处理用户注册和密码重置。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 注册新用户
*
* @param username 用户名
* @param rawPassword 明文密码
*/
public void registerUser(String username, String rawPassword) {
String encodedPassword = passwordEncoder.encode(rawPassword);
// 存储用户信息和加密后的密码到数据库
System.out.println("User registered with encoded password: " + encodedPassword);
}
/**
* 重置用户密码
*
* @param username 用户名
* @param newPassword 新的明文密码
*/
public void resetPassword(String username, String newPassword) {
String encodedPassword = passwordEncoder.encode(newPassword);
// 更新数据库中的密码
System.out.println("User password reset with encoded password: " + encodedPassword);
}
}
控制器
创建一个控制器来处理注册和密码重置的请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public String register(@RequestParam String username, @RequestParam String password) {
userService.registerUser(username, password);
return "User registered successfully!";
}
@PostMapping("/reset-password")
public String resetPassword(@RequestParam String username, @RequestParam String newPassword) {
userService.resetPassword(username, newPassword);
return "Password reset successfully!";
}
}
说明
- 配置类:配置了Spring Security,并定义了一个密码编码器 Bean,使用BCrypt算法。
- 业务逻辑:提供了注册用户和重置密码的方法,这些方法使用密码编码器来加密密码。
- 控制器:提供了处理注册和密码重置请求的端点。
8、方法级安全性(Method Security)
业务场景: 一个在线内容管理系统,其中某些管理功能(如删除内容)仅对具有特定权限的用户开放。
业务时序图
- 用户(User) 在浏览器(Browser)中请求一个敏感操作。
- 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
- 服务器(Server) 的 认证管理器(AuthenticationManager) 对请求进行认证。
- 认证管理器(AuthenticationManager) 返回认证令牌给服务器。
- 服务器(Server) 的 方法安全拦截器(MethodSecurityInterceptor) 检查安全上下文。
- 方法安全拦截器(MethodSecurityInterceptor) 请求 自定义方法安全服务(CustomMethodSecurityService) 评估安全表达式。
- 自定义方法安全服务(CustomMethodSecurityService) 返回访问决策给方法安全拦截器。
- 方法安全拦截器(MethodSecurityInterceptor) 根据决策允许或拒绝访问。
- 服务器(Server) 将响应返回给浏览器。
Spring Security 配置
首先,我们需要配置Spring Security以启用方法级安全性。
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
// 可以在这里配置方法安全性相关的Bean,如自定义的权限评估器
}
业务逻辑代码
创建一个服务来处理内容管理,包括一个需要特定权限的方法。
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class ContentManagementService {
/**
* 删除内容
*
* @param contentId 内容ID
* @return 操作结果
*/
@PreAuthorize("hasAuthority('CONTENT_DELETE')")
public String deleteContent(Long contentId) {
// 执行删除内容的逻辑
System.out.println("Content with ID " + contentId + " has been deleted.");
return "Deleted successfully";
}
}
控制器
创建一个控制器来处理删除内容的请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ContentController {
@Autowired
private ContentManagementService contentManagementService;
@DeleteMapping("/content/{contentId}")
public String deleteContent(@PathVariable Long contentId) {
return contentManagementService.deleteContent(contentId);
}
}
说明
- 配置类:启用了方法级安全性,允许使用
@PreAuthorize
和其他方法安全性注解。 - 业务逻辑:
ContentManagementService
提供了删除内容的方法,该方法通过@PreAuthorize
注解限制只有具有CONTENT_DELETE
权限的用户可以执行。 - 控制器:
ContentController
提供了一个HTTP DELETE端点来删除指定ID的内容。