一文徹底搞定Spring Security 認證,實現登陸登出功能

lgx211發表於2024-11-04

Spring Security 是一個強大且靈活的安全框架,提供了身份驗證(認證)和授權(授權)功能。下面我們將詳細介紹 Spring Security 的認證功能流程,並提供自定義實現登入介面的示例,包括自定義認證過濾器和登出功能。

一、Spring Security 認證流程的深入分析

Spring Security 的認證流程是多層次的,涉及多個元件的協作。以下是每個步驟的深入分析:

  1. 請求攔截

    當使用者請求一個受保護的資源時,Spring Security 會使用過濾器鏈來處理請求。FilterChainProxy 是 Spring Security 的核心過濾器,它將請求傳遞給註冊的過濾器鏈。

  2. 認證過濾器

    預設情況下,UsernamePasswordAuthenticationFilter 會被用作處理使用者名稱和密碼的認證。它從請求中提取認證資訊,通常是透過 POST 請求的表單資料傳遞。

    關鍵方法 attemptAuthentication 中,使用 AuthenticationManager 來處理認證請求。AuthenticationManager 負責委託認證給具體的認證提供者。

  3. 使用者詳情服務(UserDetailsService)

    認證過程中的一個重要步驟是從資料來源中載入使用者資訊。UserDetailsService 介面提供了一個 loadUserByUsername 方法,負責根據使用者名稱載入使用者詳情。

    通常,使用者資訊儲存在資料庫中,UserDetails 物件將包含使用者名稱、密碼和許可權資訊。Spring Security 提供了多種 UserDetailsService 的實現,開發者也可以自定義實現。

  4. 密碼驗證

    一旦獲取到使用者詳情,接下來的步驟是驗證密碼。使用 PasswordEncoder 對使用者輸入的密碼與儲存在資料庫中的密碼進行比對。

    Spring Security 支援多種加密演算法(如 BCrypt、PBKDF2、SCrypt),並允許開發者自定義密碼編碼器。

  5. 成功和失敗處理

    認證成功後,successfulAuthentication 方法被呼叫。在此方法中,開發者可以實現自定義的成功邏輯,例如返回 JWT 令牌、設定使用者會話等。

    如果認證失敗,unsuccessfulAuthentication 方法會被呼叫,可以根據需要返回錯誤訊息或重定向到登入頁面。

二、自定義登入介面的實現

1. 自定義認證過濾器的設計

建立自定義認證過濾器時,需要繼承 UsernamePasswordAuthenticationFilter 並重寫相應的方法。以下是詳細實現:

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;

    public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        return authenticationManager.authenticate(authenticationToken);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        // 可以返回使用者資訊或 JWT 令牌
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_OK);
        ObjectMapper objectMapper = new ObjectMapper();
        String token = "some_generated_jwt"; // 實際上要生成 JWT
        response.getWriter().write(objectMapper.writeValueAsString("token: " + token));
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getWriter().write("{\"error\": \"" + failed.getMessage() + "\"}");
    }
}

2. 配置 Spring Security 的詳細步驟

在配置類中,我們將新增自定義過濾器並設定使用者儲存方式。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 配置使用者儲存方式
        auth.inMemoryAuthentication()
            .withUser("user").password("{noop}password").roles("USER")
            .and()
            .withUser("admin").password("{noop}admin").roles("ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        CustomAuthenticationFilter customFilter = new CustomAuthenticationFilter(authenticationManagerBean());
        customFilter.setFilterProcessesUrl("/login"); // 自定義登入路徑

        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/login").permitAll() // 允許訪問登入介面
            .anyRequest().authenticated() // 其他請求需要認證
            .and()
            .addFilter(customFilter) // 新增自定義認證過濾器
            .logout()
            .logoutUrl("/logout") // 自定義登出路徑
            .logoutSuccessUrl("/login?logout") // 登出成功後的重定向地址
            .invalidateHttpSession(true) // 登出時使 HTTP 會話失效
            .deleteCookies("JSESSIONID"); // 刪除指定的 Cookie
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

三、登出功能的實現

在 Spring Security 中,登出功能非常簡單。配置登出路徑和成功重定向即可。在上述配置中,我們已經為登出功能新增了以下配置:

  • logoutUrl("/logout"):指定登出的 URL。
  • logoutSuccessUrl("/login?logout"):登出成功後的重定向 URL。
  • invalidateHttpSession(true):登出時使 HTTP 會話失效。
  • deleteCookies("JSESSIONID"):在登出時刪除指定的 Cookie。

四、設計考慮與常見問題

  1. 設計考慮

    靈活性:自定義認證過濾器允許我們實現不同的認證邏輯,如 OAuth2、JWT 等,保持系統的靈活性。

    安全性:在實現過程中,確保敏感資訊(如密碼)不被明文傳輸和儲存,推薦使用 HTTPS 和合適的密碼加密方式。

    錯誤處理:對失敗的認證提供明確的反饋,方便使用者理解問題所在,提升使用者體驗。

  2. 常見問題

    跨域問題:在前後端分離的應用中,登入介面可能會遇到跨域請求問題。可以透過設定 CORS 策略來解決。

    狀態管理:如果使用 JWT 進行認證,需注意如何管理狀態和續期機制。

    併發登入問題:需要考慮多個裝置或瀏覽器同時登入的情況,可能需要實現會話管理。

相關文章