17个SpringSecurity业务场景案例,全面掌握安全框架(第二部分)

142 篇文章 0 订阅
2 篇文章 0 订阅

在这里插入图片描述

Spring Security 通过一系列注解简化了安全配置,我们将深入探讨Spring Security框架的17个关键应用场景,包括认证、授权、OAuth2、CSRF保护等。每个案例都配有详细的时序图和代码示例,旨在帮助开发者全面理解并有效利用Spring Security的强大功能,以构建更安全、更可靠的应用程序。

肖哥弹架构 跟大家“弹弹” 框架注解使用,需要代码关注

欢迎 点赞,关注,评论。

关注公号Solomon肖哥弹架构获取更多精彩内容

历史热点文章

Spring Security 常用应用场景介绍:

  1. 认证(Authentication)
    • 应用场景:确保只有经过验证的用户才能访问应用程序。
  2. 授权(Authorization)
    • 应用场景:控制用户对特定资源的访问权限,如角色基础的访问控制。
  3. 表单登录(Form-Based Login)
    • 应用场景:为用户提供登录表单,处理登录请求和重定向。
  4. HTTP 基本认证(HTTP Basic Authentication)
    • 应用场景:为 RESTful API 或其他服务提供基础的用户名和密码认证。
  5. OAuth2 和 OpenID Connect
    • 应用场景:支持现代的授权框架,适用于需要第三方应用认证的场景。
  6. CSRF 保护(CSRF Protection)
    • 应用场景:防止跨站请求伪造攻击,保护 Web 应用程序的安全。
  7. 密码编码(Password Encoding)
    • 应用场景:安全地存储用户密码,防止密码泄露。
  8. 方法级安全性(Method Security)
    • 应用场景:控制对特定方法或 Bean 属性的访问,实现细粒度的安全控制。
  9. 异常处理(Exception Handling)
    • 应用场景:自定义安全相关的异常处理,如认证失败、授权失败。
  10. 记住我(Remember-Me)
    • 应用场景:为用户提供持久的登录状态,方便用户再次访问。
  11. 预授权(Pre-Invocation)
    • 应用场景:在方法执行前进行安全检查,确保方法调用的安全性。
  12. 表达式支持(Expression-Based)
    • 应用场景:使用 Spring Expression Language (SpEL) 实现复杂的安全逻辑。
  13. 安全上下文(Security Context)
    • 应用场景:管理和检索认证信息,如获取当前认证用户。
  14. 安全过滤器链(Security Filter Chain)
    • 应用场景:处理 HTTP 请求的安全检查,如认证、授权。
  15. 用户详细信息服务(UserDetailsService)
    • 应用场景:自定义用户认证信息的加载逻辑,如从数据库加载用户数据。
  16. 多因素认证(Multi-Factor Authentication)
    • 应用场景:增加额外的安全层,如短信验证码、电子邮件确认。
  17. 匿名访问(Anonymous Access)
    • 应用场景:允许未认证的用户访问某些公共资源。

Spring Security 场景案例

9、异常处理(Exception Handling)

业务场景: 在线预订系统,需要处理用户在预订过程中可能遇到的各种异常,如库存不足、支付失败等,并给用户友好的错误提示。

业务时序图

在这里插入图片描述

  1. 用户(User) 尝试通过浏览器(Browser)访问一个受保护的端点。
  2. 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
  3. 服务器(Server)认证管理器(AuthenticationManager) 处理认证请求。
  4. 登录处理(LoginProcessing) 验证用户的凭据。
  5. 认证管理器(AuthenticationManager) 根据验证结果返回认证成功或失败。
  6. 异常处理(ExceptionHandling) 捕获并处理在认证过程中抛出的任何异常。
  7. 服务器(Server) 根据异常处理的结果返回适当的 HTTP 响应或错误页面。
  8. 浏览器(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)

业务场景: 一个在线论坛,用户希望在下次访问时能够自动登录,而无需重新输入用户名和密码。

业务时序图

在这里插入图片描述

  1. 用户(User) 在登录表单上勾选“记住我”选项。
  2. 浏览器(Browser) 将带有“记住我”标记的登录表单提交给服务器(Server)。
  3. 服务器(Server) 接收到带有“记住我”请求的认证请求,并传递给 FilterChainProxy
  4. FilterChainProxy 调用 RememberMeServices 进行用户认证并处理“记住我”逻辑。
  5. RememberMeServices 验证用户凭据,并在认证成功后生成一个持久的认证令牌。
  6. FilterChainProxy 根据认证结果设置一个包含认证令牌的“记住我”Cookie。
  7. 服务器(Server) 将带有“记住我”Cookie的响应发送回浏览器。
  8. 浏览器(Browser) 将用户重定向到主页,并存储“记住我”Cookie。
  9. 用户(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)

业务场景: 一个企业内部的财务管理系统,其中某些敏感操作(如提交财务报告)需要在执行前进行权限检查,以确保只有具有适当权限的用户可以执行这些操作。

业务时序图

在这里插入图片描述

  1. 用户(User) 请求一个受保护的操作。
  2. 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
  3. 服务器(Server)认证管理器(AuthenticationManager) 对请求进行认证。
  4. 认证管理器(AuthenticationManager) 返回认证令牌给服务器。
  5. 服务器(Server)方法安全拦截器(MethodSecurityInterceptor) 检查安全上下文。
  6. 方法安全拦截器(MethodSecurityInterceptor) 请求 自定义方法安全服务(CustomMethodSecurityService) 评估预授权表达式。
  7. 自定义方法安全服务(CustomMethodSecurityService) 返回访问决策给方法安全拦截器。
  8. 方法安全拦截器(MethodSecurityInterceptor) 根据决策允许或拒绝访问。
  9. 服务器(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)

业务场景: 一个在线文档管理系统,其中某些文档的访问权限依赖于用户的多个属性,例如部门和角色。需要使用复杂的表达式来确定用户是否有权访问特定文档。

业务时序图

在这里插入图片描述

  1. 用户(User) 请求一个资源。
  2. 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
  3. 服务器(Server)认证管理器(AuthenticationManager) 对请求进行认证。
  4. 认证管理器(AuthenticationManager) 返回认证令牌给服务器。
  5. 服务器(Server)方法安全拦截器(MethodSecurityInterceptor) 检查授权。
  6. 方法安全拦截器(MethodSecurityInterceptor) 请求 表达式评估器(ExpressionEvaluator) 处理 SpEL 表达式。
  7. 表达式评估器(ExpressionEvaluator) 根据当前的安全上下文和定义的表达式返回评估结果。
  8. 方法安全拦截器(MethodSecurityInterceptor) 根据评估结果做出访问决策。
  9. 服务器(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应用程序,需要在每个请求中识别和处理当前认证用户的安全上下文,以确保用户只能访问其租户的数据。

业务时序图

在这里插入图片描述

  1. 用户(User) 尝试通过浏览器(Browser)访问一个受保护的资源。
  2. 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
  3. 服务器(Server) 请求当前的安全上下文(SecurityContextHolder.getContext())。
  4. 安全上下文持有器(SecurityContextHolder) 提供当前的认证信息。
  5. 认证(Authentication) 对象检查用户是否有权访问请求的资源。
  6. 资源(Resource) 根据认证信息返回访问决策。
  7. 认证(Authentication) 根据需要更新安全上下文。
  8. 安全上下文持有器(SecurityContextHolder) 返回更新后的安全上下文。
  9. 服务器(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)

业务场景: 一个电子商务网站,需要确保所有支付相关的请求都经过安全检查,如验证用户是否已认证、会话是否有效等。

业务时序图

在这里插入图片描述

  1. 用户(User) 通过浏览器(Browser)请求一个受保护的资源。
  2. 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
  3. 服务器(Server) 通过 FilterChainProxy 开始处理请求。
  4. FilterChainProxy 依次调用安全过滤器链中的各个过滤器。
  5. Filter1 (例如CSRF保护过滤器)处理请求。
  6. Filter2 (例如会话管理过滤器)处理请求。
  7. Filter3 (例如自定义认证过滤器)处理请求,并调用 AuthenticationManager 进行认证。
  8. AuthenticationManager 根据请求进行认证,并返回认证成功或失败的结果。
  9. Filter3 完成认证处理,并返回给 FilterChainProxy
  10. FilterChainProxy 完成所有过滤器的处理。
  11. 服务器(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)系统,需要根据用户的角色分配不同的访问权限。系统需要从数据库加载用户详细信息,包括用户的角色和权限。

业务时序图

在这里插入图片描述

  1. 用户(User) 在浏览器(Browser)中输入凭证。
  2. 浏览器(Browser) 将登录表单提交到服务器(Server)。
  3. 服务器(Server) 通过 认证管理器(AuthenticationManager) 请求用户详细信息。
  4. 认证管理器(AuthenticationManager) 调用 用户详细信息服务(UserDetailsService)loadUserByUsername 方法。
  5. 用户详细信息服务(UserDetailsService) 向数据库(Database)查询用户数据。
  6. 数据库(Database) 返回用户数据给用户详细信息服务。
  7. 用户详细信息服务(UserDetailsService) 将封装好的 UserDetails 对象返回给认证管理器。
  8. 认证管理器(AuthenticationManager) 进行认证处理。
  9. 服务器(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 来加载用户详细信息。
  • UserDetailsServiceCustomUserDetailsService 提供了从数据库加载用户信息的逻辑。
  • 控制器ResourceController 提供了两个端点,分别用于展示管理员和普通用户的数据。

16、多因素认证(Multi-Factor Authentication)

业务场景: 一个在线银行系统,要求用户在进行敏感操作(如转账)时必须通过多因素认证,以增强安全性。

业务时序图

在这里插入图片描述

  1. 用户(User) 请求登录。
  2. 浏览器(Browser) 向服务器(Server)发送 HTTP 请求。
  3. 服务器(Server)登录模块(Login Module) 验证用户名和密码。
  4. 登录模块(Login Module) 认证成功,触发多因素认证。
  5. 多因素认证管理器(MultiFactorAuthenticationManager)短信服务(SMS Service) 发送包含验证码的短信。
  6. 短信服务(SMS Service) 将验证码发送给用户。
  7. 用户(User) 接收到短信并输入验证码。
  8. 浏览器(Browser) 将包含验证码的 HTTP 请求发送到服务器。
  9. 服务器(Server)多因素认证管理器(MultiFactorAuthenticationManager) 验证验证码。
  10. 多因素认证管理器(MultiFactorAuthenticationManager) 根据验证码验证结果返回认证成功或失败。
  11. 服务器(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)

业务场景: 一个内容分享平台,允许未注册的用户访问公共内容,但需要登录才能发布内容或访问付费内容。

业务时序图

在这里插入图片描述

  1. 用户(User) 请求一个公共资源。
  2. 浏览器(Browser) 向服务器(Server)发送 HTTP GET 请求。
  3. 服务器(Server)FilterChainProxy 开始处理请求。
  4. FilterChainProxy 调用 认证管理器(AuthenticationManager) 检查是否需要认证。
  5. 认证管理器(AuthenticationManager) 确定请求不需要认证。
  6. FilterChainProxy 访问 公共资源(PublicResource)
  7. 公共资源(PublicResource) 返回资源数据。
  8. FilterChainProxy 将数据传递给响应。
  9. 服务器(Server) 将 HTTP 响应发送回浏览器。
  10. 浏览器(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端点来获取公共内容和私有内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Solomon_肖哥弹架构

你的欣赏就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值