Spring Boot 3中實現多種身份驗證方法開源案例

banq發表於2024-04-23

身份驗證是保護 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(<font>"${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(<font>"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 = <font>"/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

相關文章