Spring Security 通过一系列注解简化了安全配置,我们将深入探讨Spring Security框架的17个关键应用场景,包括认证、授权、OAuth2、CSRF保护等。每个案例都配有详细的时序图和代码示例,旨在帮助开发者全面理解并有效利用Spring Security的强大功能,以构建更安全、更可靠的应用程序。
肖哥弹架构 跟大家“弹弹” 框架注解使用,需要代码关注
欢迎 点赞,关注,评论。
关注公号Solomon肖哥弹架构获取更多精彩内容
历史热点文章
- 28个验证注解,通过业务案例让你精通Java数据校验(收藏篇)
- Java 8函数式编程全攻略:43种函数式业务代码实战案例解析(收藏版)
- 69 个Spring mvc 全部注解:真实业务使用案例说明(必须收藏)
- 24 个Spring bean 全部注解:真实业务使用案例说明(必须收藏)
- MySQL索引完全手册:真实业务图文讲解17种索引运用技巧(必须收藏)
- 一个项目代码讲清楚DO/PO/BO/AO/E/DTO/DAO/ POJO/VO
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 场景案例
9、异常处理(Exception Handling)
业务场景: 在线预订系统,需要处理用户在预订过程中可能遇到的各种异常,如库存不足、支付失败等,并给用户友好的错误提示。
业务时序图
- 用户(User) 尝试通过浏览器(Browser)访问一个受保护的端点。
- 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
- 服务器(Server) 的 认证管理器(AuthenticationManager) 处理认证请求。
- 登录处理(LoginProcessing) 验证用户的凭据。
- 认证管理器(AuthenticationManager) 根据验证结果返回认证成功或失败。
- 异常处理(ExceptionHandling) 捕获并处理在认证过程中抛出的任何异常。
- 服务器(Server) 根据异常处理的结果返回适当的 HTTP 响应或错误页面。
- 浏览器(Browser) 显示错误信息给用户。
Spring Security 配置
首先,我们需要配置Spring 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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@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")
.failureHandler(customAuthenticationFailureHandler())
.and()
.exceptionHandling()
.accessDeniedPage("/access-denied");
}
@Bean
public SimpleUrlAuthenticationFailureHandler customAuthenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler();
handler.setDefaultFailureUrl("/login?error=true");
return handler;
}
}
业务逻辑代码
创建一个服务来处理预订请求,并抛出自定义异常。
import org.springframework.stereotype.Service;
@Service
public class BookingService {
public void makeBooking(String item) throws BookingException {
if ("sold-out-item".equals(item)) {
throw new BookingException("The item is sold out.");
}
// 执行预订逻辑
System.out.println("Booking successful for item: " + item);
}
}
public class BookingException extends RuntimeException {
public BookingException(String message) {
super(message);
}
}
控制器
创建一个控制器来处理预订请求,并处理可能的异常。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BookingController {
@Autowired
private BookingService bookingService;
@PostMapping("/booking")
public ResponseEntity<String> makeBooking(@RequestBody String item) {
try {
bookingService.makeBooking(item);
return ResponseEntity.ok("Booking successful");
} catch (BookingException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}
说明
- 配置类:配置了Spring Security,并自定义了认证失败处理器和访问拒绝处理器。
- 业务逻辑:
BookingService
提供了预订方法,该方法在特定条件下抛出BookingException
。 - 控制器:
BookingController
提供了一个HTTP POST端点来处理预订请求,并在发生异常时返回适当的错误信息。
10、记住我(Remember-Me)
业务场景: 一个在线论坛,用户希望在下次访问时能够自动登录,而无需重新输入用户名和密码。
业务时序图
- 用户(User) 在登录表单上勾选“记住我”选项。
- 浏览器(Browser) 将带有“记住我”标记的登录表单提交给服务器(Server)。
- 服务器(Server) 接收到带有“记住我”请求的认证请求,并传递给 FilterChainProxy。
- FilterChainProxy 调用 RememberMeServices 进行用户认证并处理“记住我”逻辑。
- RememberMeServices 验证用户凭据,并在认证成功后生成一个持久的认证令牌。
- FilterChainProxy 根据认证结果设置一个包含认证令牌的“记住我”Cookie。
- 服务器(Server) 将带有“记住我”Cookie的响应发送回浏览器。
- 浏览器(Browser) 将用户重定向到主页,并存储“记住我”Cookie。
- 用户(User) 看到主页,并且由于“记住我”Cookie的存在,可以在未来的会话中自动登录。
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.core.userdetails.User;
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.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.and()
.rememberMe()
.and()
.logout()
.logoutSuccessUrl("/login?logout");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("password")).roles("USER");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
}
业务逻辑代码
创建一个控制器来处理登录和主页请求。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class MainController {
@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>
<label>Remember me: <input type="checkbox" name="remember-me"/></label>
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
</body>
</html>
说明
- 配置类:配置了Spring Security,并启用了“记住我”功能。使用
PersistentTokenRepository
来存储“记住我”令牌。 - 控制器:提供了登录和主页的视图。
- 登录页面:用户可以选择是否启用“记住我”功能。
11、预授权(Pre-Invocation)
业务场景: 一个企业内部的财务管理系统,其中某些敏感操作(如提交财务报告)需要在执行前进行权限检查,以确保只有具有适当权限的用户可以执行这些操作。
业务时序图
- 用户(User) 请求一个受保护的操作。
- 浏览器(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 FinancialService {
/**
* 提交财务报告
*
* @param report 财务报告内容
* @return 提交结果
*/
@PreAuthorize("hasAuthority('FINANCE_SUBMIT')")
public String submitFinancialReport(String report) {
// 执行提交财务报告的逻辑
System.out.println("Financial report submitted: " + report);
return "Submission successful";
}
}
控制器
创建一个控制器来处理提交财务报告的请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FinancialController {
@Autowired
private FinancialService financialService;
@PostMapping("/submit-report")
public ResponseEntity<String> submitReport(@RequestBody String report) {
return ResponseEntity.ok(financialService.submitFinancialReport(report));
}
}
说明
- 配置类:启用了方法级安全性,允许使用
@PreAuthorize
和其他方法安全性注解。 - 业务逻辑:
FinancialService
提供了提交财务报告的方法,该方法通过@PreAuthorize
注解限制只有具有FINANCE_SUBMIT
权限的用户可以执行。 - 控制器:
FinancialController
提供了一个HTTP POST端点来提交财务报告。
12、表达式支持(Expression-Based)
业务场景: 一个在线文档管理系统,其中某些文档的访问权限依赖于用户的多个属性,例如部门和角色。需要使用复杂的表达式来确定用户是否有权访问特定文档。
业务时序图
- 用户(User) 请求一个资源。
- 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
- 服务器(Server) 的 认证管理器(AuthenticationManager) 对请求进行认证。
- 认证管理器(AuthenticationManager) 返回认证令牌给服务器。
- 服务器(Server) 的 方法安全拦截器(MethodSecurityInterceptor) 检查授权。
- 方法安全拦截器(MethodSecurityInterceptor) 请求 表达式评估器(ExpressionEvaluator) 处理 SpEL 表达式。
- 表达式评估器(ExpressionEvaluator) 根据当前的安全上下文和定义的表达式返回评估结果。
- 方法安全拦截器(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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/documents/**").access("hasRole('ADMIN') or hasAuthority('DOCUMENT_READ')")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.and()
.logout()
.logoutSuccessUrl("/login?logout");
}
}
业务逻辑代码
创建一个服务来处理文档访问,并使用表达式来定义访问权限。
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class DocumentService {
/**
* 获取文档内容
*
* @param documentId 文档ID
* @return 文档内容
*/
@PreAuthorize("hasAuthority('DOCUMENT_READ') and #documentId == authentication.principal.departmentId")
public String getDocumentContent(Long documentId) {
// 模拟从数据库获取文档内容
return "Content of the document " + documentId;
}
}
控制器
创建一个控制器来处理文档访问请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DocumentController {
@Autowired
private DocumentService documentService;
@GetMapping("/documents/{documentId}")
public ResponseEntity<String> getDocument(@PathVariable Long documentId) {
return ResponseEntity.ok(documentService.getDocumentContent(documentId));
}
}
说明
- 配置类:配置了Spring Security,并启用了方法级安全性,允许使用SpEL表达式来定义复杂的权限规则。
- 业务逻辑:
DocumentService
提供了获取文档内容的方法,该方法通过@PreAuthorize
注解限制访问权限,使用SpEL表达式结合用户的角色和属性。 - 控制器:
DocumentController
提供了一个HTTP GET端点来获取文档内容。
13、安全上下文(Security Context)
业务场景: 一个多租户的SaaS应用程序,需要在每个请求中识别和处理当前认证用户的安全上下文,以确保用户只能访问其租户的数据。
业务时序图
- 用户(User) 尝试通过浏览器(Browser)访问一个受保护的资源。
- 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
- 服务器(Server) 请求当前的安全上下文(
SecurityContextHolder.getContext()
)。 - 安全上下文持有器(SecurityContextHolder) 提供当前的认证信息。
- 认证(Authentication) 对象检查用户是否有权访问请求的资源。
- 资源(Resource) 根据认证信息返回访问决策。
- 认证(Authentication) 根据需要更新安全上下文。
- 安全上下文持有器(SecurityContextHolder) 返回更新后的安全上下文。
- 服务器(Server) 根据安全上下文返回适当的响应给浏览器。
Spring Security 配置
首先,我们需要配置Spring 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.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.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()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.and()
.logout()
.logoutSuccessUrl("/login?logout");
}
@Bean
@Override
public UserDetailsService userDetailsService() {
// 这里使用内存中的用户详情服务,生产环境应该使用数据库或其他服务
return new InMemoryUserDetailsManager(
User.withUsername("user").password(passwordEncoder().encode("password")).roles("USER").build()
);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
业务逻辑代码
创建一个服务来处理多租户数据访问,并使用安全上下文来获取当前认证用户的信息。
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@Service
public class MultiTenantDataService {
/**
* 获取当前认证用户的租户ID
*
* @return 租户ID
*/
public String getTenantId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof User) {
User user = (User) authentication.getPrincipal();
return user.getUsername(); // 假设用户名即租户ID
}
return null;
}
/**
* 根据租户ID获取数据
*
* @param tenantId 租户ID
* @return 租户数据
*/
public String getDataForTenant(String tenantId) {
// 根据租户ID从数据库获取数据
return "Data for tenant " + tenantId;
}
}
控制器
创建一个控制器来处理数据请求,并确保数据请求与当前认证用户的租户ID相关联。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DataController {
@Autowired
private MultiTenantDataService multiTenantDataService;
@GetMapping("/data")
public String getData() {
String tenantId = multiTenantDataService.getTenantId();
return multiTenantDataService.getDataForTenant(tenantId);
}
}
说明
- 配置类:配置了Spring Security,启用了表单登录和注销。
- 业务逻辑:
MultiTenantDataService
提供了获取当前认证用户租户ID的方法,并根据租户ID获取数据。 - 控制器:
DataController
提供了一个HTTP GET端点来获取与当前认证用户租户ID相关联的数据。
14、安全过滤器链(Security Filter Chain)
业务场景: 一个电子商务网站,需要确保所有支付相关的请求都经过安全检查,如验证用户是否已认证、会话是否有效等。
业务时序图
- 用户(User) 通过浏览器(Browser)请求一个受保护的资源。
- 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
- 服务器(Server) 通过 FilterChainProxy 开始处理请求。
- FilterChainProxy 依次调用安全过滤器链中的各个过滤器。
- Filter1 (例如CSRF保护过滤器)处理请求。
- Filter2 (例如会话管理过滤器)处理请求。
- Filter3 (例如自定义认证过滤器)处理请求,并调用 AuthenticationManager 进行认证。
- AuthenticationManager 根据请求进行认证,并返回认证成功或失败的结果。
- Filter3 完成认证处理,并返回给 FilterChainProxy。
- FilterChainProxy 完成所有过滤器的处理。
- 服务器(Server) 根据处理结果向浏览器发送 HTTP 响应。
Spring Security 配置
首先,我们需要配置Spring 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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/payment/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.and()
.logout()
.logoutSuccessUrl("/login?logout");
}
@Bean
public CustomFilter customFilter() throws Exception {
CustomFilter filter = new CustomFilter();
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
@Override
protected void configure(org.springframework.security.config.annotation.web.builders.FilterChainProxy filterChain) throws Exception {
filterChain
.addFilterBefore(customFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
业务逻辑代码
创建一个自定义过滤器来处理支付请求的安全检查。
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
@Component
public class CustomFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
chain.doFilter(request, response);
} else {
((HttpServletResponse) response).sendRedirect("/login");
}
}
}
控制器
创建一个控制器来处理支付请求。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
@GetMapping("/payment/process")
public String processPayment() {
// 处理支付逻辑
return "Payment processed successfully";
}
}
说明
- 配置类:配置了Spring Security,并添加了一个自定义过滤器
CustomFilter
到安全过滤器链中,确保所有支付请求都经过安全检查。 - 自定义过滤器:
CustomFilter
检查用户是否已认证,如果未认证则重定向到登录页面。 - 控制器:
PaymentController
提供了一个HTTP GET端点来处理支付请求。
15、用户详细信息服务(UserDetailsService)
业务场景: 一个基于角色的访问控制(RBAC)系统,需要根据用户的角色分配不同的访问权限。系统需要从数据库加载用户详细信息,包括用户的角色和权限。
业务时序图
- 用户(User) 在浏览器(Browser)中输入凭证。
- 浏览器(Browser) 将登录表单提交到服务器(Server)。
- 服务器(Server) 通过 认证管理器(AuthenticationManager) 请求用户详细信息。
- 认证管理器(AuthenticationManager) 调用 用户详细信息服务(UserDetailsService) 的
loadUserByUsername
方法。 - 用户详细信息服务(UserDetailsService) 向数据库(Database)查询用户数据。
- 数据库(Database) 返回用户数据给用户详细信息服务。
- 用户详细信息服务(UserDetailsService) 将封装好的
UserDetails
对象返回给认证管理器。 - 认证管理器(AuthenticationManager) 进行认证处理。
- 服务器(Server) 根据认证结果重定向到相应页面或返回错误信息给浏览器。
Spring Security 配置
首先,我们需要配置Spring Security以使用自定义的 UserDetailsService
。
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.and()
.logout()
.logoutSuccessUrl("/login?logout");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
业务逻辑代码
创建一个 UserDetailsService
实现来从数据库加载用户详细信息。
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 模拟从数据库加载用户信息
if ("admin".equals(username)) {
return new org.springframework.security.core.userdetails.User(
username, passwordEncoder.encode("password"), List.of(new SimpleGrantedAuthority("ROLE_ADMIN")));
} else if ("user".equals(username)) {
return new org.springframework.security.core.userdetails.User(
username, passwordEncoder.encode("password"), List.of(new SimpleGrantedAuthority("ROLE_USER")));
}
throw new UsernameNotFoundException("User not found");
}
}
控制器
创建一个控制器来处理受保护的资源请求。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ResourceController {
@GetMapping("/admin/data")
public String getAdminData() {
return "Sensitive admin data";
}
@GetMapping("/user/data")
public String getUserData() {
return "User data";
}
}
说明
- 配置类:配置了Spring Security,并使用自定义的
UserDetailsService
来加载用户详细信息。 - UserDetailsService:
CustomUserDetailsService
提供了从数据库加载用户信息的逻辑。 - 控制器:
ResourceController
提供了两个端点,分别用于展示管理员和普通用户的数据。
16、多因素认证(Multi-Factor Authentication)
业务场景: 一个在线银行系统,要求用户在进行敏感操作(如转账)时必须通过多因素认证,以增强安全性。
业务时序图
- 用户(User) 请求登录。
- 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
- 服务器(Server) 的 登录模块(Login Module) 验证用户名和密码。
- 登录模块(Login Module) 认证成功,触发多因素认证。
- 多因素认证管理器(MultiFactorAuthenticationManager) 向 短信服务(SMS Service) 发送包含验证码的短信。
- 短信服务(SMS Service) 将验证码发送给用户。
- 用户(User) 接收到短信并输入验证码。
- 浏览器(Browser) 将包含验证码的 HTTP 请求发送到服务器。
- 服务器(Server) 的 多因素认证管理器(MultiFactorAuthenticationManager) 验证验证码。
- 多因素认证管理器(MultiFactorAuthenticationManager) 根据验证码验证结果返回认证成功或失败。
- 服务器(Server) 根据多因素认证结果重定向到相应页面或显示错误信息。
Spring Security 配置
首先,我们需要配置Spring 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.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.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("/transfer/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.and()
.authenticationManager(authenticationManagerBean());
}
@Bean
@Override
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withUsername("user").password(passwordEncoder().encode("password")).roles("USER").build()
);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
业务逻辑代码
创建一个服务来处理转账请求,并在执行前进行多因素认证。
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@Service
public class BankingService {
/**
* 执行转账操作
*
* @param amount 转账金额
*/
public void transferMoney(double amount) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.isAuthenticated() && checkSecondFactor(authentication)) {
// 执行转账逻辑
System.out.println("Transfer successful. Amount: " + amount);
} else {
throw new RuntimeException("Authentication or second factor check failed.");
}
}
private boolean checkSecondFactor(Authentication authentication) {
// 检查第二因素,例如短信验证码或电子邮件确认
return true; // 假设检查通过
}
}
控制器
创建一个控制器来处理转账请求。
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 BankingController {
@Autowired
private BankingService bankingService;
@PostMapping("/transfer")
public String transfer(@RequestParam double amount) {
bankingService.transferMoney(amount);
return "Transfer initiated";
}
}
说明
- 配置类:配置了Spring Security,启用了表单登录和注销。
- 业务逻辑:
BankingService
提供了转账方法,该方法在执行前进行多因素认证。 - 控制器:
BankingController
提供了一个HTTP POST端点来处理转账请求。
17、匿名访问(Anonymous Access)
业务场景: 一个内容分享平台,允许未注册的用户访问公共内容,但需要登录才能发布内容或访问付费内容。
业务时序图
- 用户(User) 请求一个公共资源。
- 浏览器(Browser) 向服务器(Server)发送 HTTP GET 请求。
- 服务器(Server) 的 FilterChainProxy 开始处理请求。
- FilterChainProxy 调用 认证管理器(AuthenticationManager) 检查是否需要认证。
- 认证管理器(AuthenticationManager) 确定请求不需要认证。
- FilterChainProxy 访问 公共资源(PublicResource) 。
- 公共资源(PublicResource) 返回资源数据。
- FilterChainProxy 将数据传递给响应。
- 服务器(Server) 将 HTTP 响应发送回浏览器。
- 浏览器(Browser) 显示资源给用户。
Spring Security 配置
首先,我们需要配置Spring 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.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("/public/**").anonymous()
.antMatchers("/private/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/private/home")
.and()
.logout()
.logoutSuccessUrl("/public/home");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
业务逻辑代码
创建一个服务来处理内容访问。
import org.springframework.stereotype.Service;
@Service
public class ContentService {
/**
* 获取公共内容
*
* @return 公共内容
*/
public String getPublicContent() {
// 模拟公共内容
return "This is public content available to everyone.";
}
/**
* 获取私有内容
*
* @return 私有内容
*/
public String getPrivateContent() {
// 模拟私有内容
return "This is private content available only to authenticated users.";
}
}
控制器
创建一个控制器来处理内容请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ContentController {
@Autowired
private ContentService contentService;
@GetMapping("/public/content")
public String publicContent() {
return contentService.getPublicContent();
}
@GetMapping("/private/content")
public String privateContent() {
return contentService.getPrivateContent();
}
}
说明
- 配置类:配置了Spring Security,允许匿名用户访问
/public/**
路径下的资源,而需要认证的用户才能访问/private/**
路径下的资源。 - 业务逻辑:
ContentService
提供了获取公共内容和私有内容的方法。 - 控制器:
ContentController
提供了两个HTTP GET端点来获取公共内容和私有内容。