身份验证是保护 Spring Boot 应用程序安全的一个关键方面。在某些项目中,您可能会遇到需要为应用程序的不同部分支持多种身份验证方法。
在我正在进行的 Spring Boot 副项目中,我遇到了一个与使用各种方法验证 API 相关的令人着迷且常见的挑战。具体来说,在处理以 /api/internal 为前缀的内部 API 时,我选择通过标头中嵌入的 API 密钥进行用户身份验证。相反,对于 Web 应用程序用户界面,首选身份验证方法是 HttpBasic。事实证明,在单个项目中管理多个身份验证机制是一个值得注意的方面。
在本次讨论中,我将提供一个说明性示例来说明如何实现这些不同的身份验证方法。
1. 项目设置
确保您的build.gradle(对于 Gradle)或pom.xml(对于 Maven)文件中有必要的依赖项:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
|
2.安全配置
创建一个 SecurityConfig 类来配置 Spring Security。你可以为应用程序的不同部分创建多个 SecurityConfigurerAdapter 类。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 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.configurers.AbstractHttpConfigurer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.channel.ChannelProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@EnableWebSecurity @Configuration public class SpringSecurityConfig { @Autowired public APIAuthenticationErrEntrypoint apiAuthenticationErrEntrypoint; @Value("${internal.api-key}") private String internalApiKey; @Bean @Order(1) public SecurityFilterChain filterChainPrivate(HttpSecurity http) throws Exception { http .securityMatcher("/api/internal/**") .addFilterBefore(new InternalApiKeyAuthenticationFilter(internalApiKey), ChannelProcessingFilter.class) .exceptionHandling((auth) -> { auth.authenticationEntryPoint(apiAuthenticationErrEntrypoint); }) .cors(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable); return http.build(); } @Bean @Order(2) public SecurityFilterChain filterChainWebAppication(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authz -> authz .requestMatchers("/login").permitAll() .requestMatchers("/**").authenticated() .anyRequest().authenticated() ); http.formLogin(authz -> authz .loginPage("/login").permitAll() .loginProcessingUrl("/login") ); http.logout(authz -> authz .deleteCookies("JSESSIONID") .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) ); http.csrf(AbstractHttpConfigurer::disable); return http.build(); } @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsService()); return authenticationProvider; } @Bean public InMemoryUserDetailsManager userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); return new InMemoryUserDetailsManager(user); } }
|
3.实施身份验证过滤器
为 API 密钥身份验证创建自定义过滤器:
public class InternalApiKeyAuthenticationFilter implements Filter {
private final String internalApiKey; InternalApiKeyAuthenticationFilter(String internalApiKey) { this.internalApiKey = internalApiKey; } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; String apiKey = httpServletRequest.getHeader("x-api-key"); if (apiKey == null) { unauthorized(httpServletResponse); return; } if (!internalApiKey.equals(apiKey)) { unauthorized(httpServletResponse); return; } chain.doFilter(request, response); } @Override public void destroy() { } private void unauthorized(HttpServletResponse httpServletResponse) throws IOException { httpServletResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); httpServletResponse.setStatus(401); Map<String, Object> response = Map.of("message", "SC_UNAUTHORIZED"); String responseBody = new ObjectMapper().writeValueAsString(response); httpServletResponse.getWriter().write(responseBody); } }
|
一些使用 /api/internal/** 模式的请求将通过 InternalApiKeyAuthenticationFilter 从请求头获取 API 密钥,并与常量密钥进行比较以进行验证。
4.在控制器中使用
在控制器中,使用 @PreAuthorize 注解为不同的端点指定所需的角色。
@RestController @RequestMapping(value = "/api/internal") public class InternalAPIController {
@GetMapping(value = "/health") public ResponseEntity internalHealthCheck() { return ResponseEntity.ok("ok"); } } @Controller @RequestMapping(value = "/") public class HomeController { @GetMapping public String homePage(Model model) { return "home"; } }
|
结论
在 Spring Boot 项目中实施多种身份验证方法,可以满足应用程序不同部分的特定安全要求。通过利用 Spring Security 和自定义过滤器,您可以在同一项目中无缝集成 API 密钥身份验证和 HTTP 基本身份验证。
切记要自定义身份验证过滤器,以适应您的特定用例,并根据您的安全要求实施验证逻辑。这种灵活的身份验证方法可确保您的应用程序保持安全,同时满足不同的身份验证需求。
https://github.com/jackynote/springboot3-multi-authenticate