欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringSecurity6.4中一次性令牌登錄(One-Time Token Login)實現(xiàn)

 更新時間:2025年03月14日 10:50:12   作者:Tomas Brunken  
Spring Security為一次性令牌認證提供了支持,本文就來介紹一下SpringSecurity6.4中一次性令牌登錄(One-Time Token Login)實現(xiàn),具有一定的參考價值,感興趣的可以了解一下

本系列Spring Boot 版本 3.4.0

本系列Spring Security 版本 6.4.2

Spring Security為一次性令牌(One-Time Token,OTT)認證提供了支持,通過oneTimeTokenLogin()DSL(Domain Specific Language,領(lǐng)域特定語言,它使用了一種特定的語法和約定來簡化配置和管理Spring應(yīng)用程序的過程。)即可使用該功能。在進行實現(xiàn)之前,需要明確OTT功能在框架中的范圍。

一、理解一次性令牌與一次性密碼的區(qū)別

人們常常會混淆一次性令牌(OTT)和一次性密碼(OTP),但在 Spring Security 中,這兩個概念在幾個關(guān)鍵方面有所不同。為了清晰起見,我們將假設(shè) OTP 指的是基于時間的一次性密碼(TOTP)或基于 HMAC 的一次性密碼(HOTP)。

1.區(qū)別一:設(shè)置要求

  • OTT:無需初始設(shè)置。用戶無需提前進行任何配置。
  • OTP:通常需要設(shè)置,例如使用外部工具生成和共享一個密鑰來生成一次性密碼。

2.區(qū)別二:令牌交付/發(fā)送

  • OTT:通常需要實現(xiàn)一個自定義的 OneTimeTokenGenerationSuccessHandler ,負責(zé)將令牌交付給最終用戶。
  • OTP:令牌通常由外部工具生成,因此無需通過應(yīng)用程序發(fā)送給用戶。

3.區(qū)別三:令牌生成

  • OTT:通過OneTimeTokenService.generate(GenerateOneTimeTokenRequest)方法在服務(wù)器端生成令牌。
  • OTP:令牌不一定是在服務(wù)器端生成的,通常是由客戶端使用共享密鑰創(chuàng)建的。

總結(jié)來說,一次性令牌(OTT)提供了一種無需額外賬戶設(shè)置即可驗證用戶的方法,與通常涉及更復(fù)雜設(shè)置過程并依賴外部工具生成令牌的一次性密碼(OTP)不同。

二、一次性令牌登錄的流程

一次性令牌登錄主要分為兩個步驟:

  • 用戶提交用戶標識符(通常為用戶名)請求令牌,令牌會以魔法鏈接(Magic Link)的形式,通過電子郵件、短信等方式發(fā)送給用戶。
  • 用戶將令牌提交到一次性令牌登錄端點,也就是點擊魔法鏈接。若令牌有效,則用戶成功登錄。
  • 整體流程

開始→用戶訪問一次性令牌登錄頁面
→點擊發(fā)送令牌按鈕
→發(fā)送POST /ott/generate 請求
→GenerateOneTimeTokenFilter 監(jiān)聽POST /ott/generate 請求,并通過OneTimeTokenService.generate()方法在服務(wù)器端生成令牌
→令牌生成成功后,調(diào)用 自定義的OneTimeTokenGenerationSuccessHandler接口實現(xiàn)類的handle()方法
→handle()方法負責(zé)通過Email或短信將包含令牌的魔法鏈接(Magic Link)發(fā)送給用戶
→用戶接收到郵件/短信后,點擊魔法鏈接
→服務(wù)器端接收GET /login/ott請求
→由 DefaultOneTimeTokenSubmitPageGeneratingFilter 生成一次性令牌提交頁面,并返回給用戶
→用戶點擊提交頁面中的登錄按鈕
→發(fā)送POST /login/ott 請求,服務(wù)端需要自定義一個HTTP接口,請求路徑為POST /login/ott→服務(wù)器端進入身份認證流程(FilterChainProxy.doFilter→AuthenticationFilter.doFilterInternal)
→AuthenticationFilter.attemptAuthentication方法中通過調(diào)用this.authenticationConverter.convert(request)構(gòu)造OneTimeTokenAuthenticationToken實例,其中this.authenticationConverter是OneTimeTokenAuthenticationConverter的實例
→AuthenticationFilter.doFilterInternal方法中通過調(diào)用AuthenticationManager.authenticate(OneTimeTokenAuthenticationToken)進行認證
→調(diào)用ProviderManager.authenticate(OneTimeTokenAuthenticationToken)→交給OneTimeTokenAuthenticationProvider認證
→OneTimeTokenAuthenticationProvider.authenticate()方法調(diào)用OneTimeTokenService.consume(OneTimeTokenAuthenticationToken)方法消費token,對token進行驗證
→驗證通過
→調(diào)用userDetailsService.loadUserByUsername獲取用戶信息和權(quán)限信息
→認證完成
→DispatcherServlet接著處理POST /login/ott 請求
→調(diào)用服務(wù)端自定義的對應(yīng)Controller方法
→該方法處理登錄成功后的一些邏輯,比如重定向到指定頁面,記錄日志等。
→結(jié)束

三、一次性令牌登錄的配置

1.OTT與默認生成的登錄頁面的集成

oneTimeTokenLogin()DSL可與formLogin()結(jié)合使用,這會在默認生成的登錄頁面中添加一個一次性令牌請求表單,同時設(shè)置DefaultOneTimeTokenSubmitPageGeneratingFilter來生成默認的一次性令牌提交頁面。

2.發(fā)送令牌給用戶

Spring Security無法確定令牌的交付方式,因此需提供自定義的OneTimeTokenGenerationSuccessHandler。

最常用的發(fā)送策略之一是通過電子郵件、短信等發(fā)送一個魔法鏈接。

在下面的示例中,我們將創(chuàng)建一個魔法鏈接并將其發(fā)送到用戶的電子郵件。

  • 一次性令牌登錄配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) {
        http
            // ...
            .formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin(Customizer.withDefaults());
        return http.build();
    }

}
  • 自定義OneTimeTokenGenerationSuccessHandler
@Component // ①
public class MagicLinkOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
    private final MailSender mailSender;
    private final OneTimeTokenGenerationSuccessHandler redirectHandler = new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) throws IOException, ServletException {
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
                .replacePath(request.getContextPath())
                .replaceQuery(null)
                .fragment(null)
                .path("/login/ott")
                .queryParam("token", oneTimeToken.getTokenValue()); // ②
        String magicLink = builder.toUriString();
        String email = getUserEmail(oneTimeToken.getUsername()); // ③
        this.mailSender.send(email, "Your Spring Security One Time Token", "Use the following link to sign in into the application: " + magicLink); // ④
        this.redirectHandler.handle(request, response, oneTimeToken); // ⑤
    }

    private String getUserEmail() {
        // ...
    }
}
  • 發(fā)送令牌給用戶成功后要跳轉(zhuǎn)到的頁面
@Controller
public class PageController {

    @GetMapping("/ott/sent")
    String ottSent() {
        return "my-template";
    }

}

① 將 MagicLinkOneTimeTokenGenerationSuccessHandler 配置為 Spring Bean

② 創(chuàng)建一個帶有 token 作為查詢參數(shù)的登錄處理 URL

③ 根據(jù)用戶名檢索用戶電子郵件

④ 使用 JavaMailSender API 向用戶發(fā)送帶有魔法鏈接的電子郵件

⑤ 使用 RedirectOneTimeTokenGenerationSuccessHandler 進行重定向到您想要的 URL

郵件內(nèi)容將類似于:

Use the following link to sign in into the application: http://localhost:8080/login/ott?token=a830c444-29d8-4d98-9b46-6aba7b22fe5b

默認提交頁面會檢測URL中的token查詢參數(shù),并將它的值自動填充到表單字段中。

3.配置一次性令牌提交頁面

默認的一次性令牌提交頁面由 DefaultOneTimeTokenSubmitPageGeneratingFilter 生成,并監(jiān)聽 GET /login/ott 。URL 也可以這樣更改:

  • 配置默認提交頁面 URL
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) {
        http
            // ...
            .formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin((ott) -> ott
                .defaultSubmitPageUrl("/ott/submit")
            );
        return http.build();
    }

}

@Component
public class MagicLinkGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
    // ...
}

TIPS:2025.1.14官方文檔示例是使用submitPageUrl(String)DSL 方法進行更改。我使用 Spring Security6.4.2 測試時,只有defaultSubmitPageUrl(String)DSL 方法。因此示例修改為defaultSubmitPageUrl(String)DSL 方法。

4.自定義一次性令牌提交頁面

如果您想使用自己的一次性令牌提交頁面,您可以禁用默認頁面,然后提供自己的端點。

  • 禁用默認提交頁面
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin((ott) -> ott
                // 禁用默認提交頁面
                .showDefaultSubmitPage(false)
                // 自定義提交頁面URL
                .defaultSubmitPageUrl("/ott/submit")
            );
        return http.build();
    }

}

@Controller
public class MyController {

    // 自定義默認提交頁面入口
    @GetMapping("/ott/submit")
    public String ottSubmitPage() {
        return "my-ott-submit";
    }

}

@Component
public class MagicLinkGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
    // ...
        
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) throws IOException, ServletException {
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
                .replacePath(request.getContextPath())
                .replaceQuery(null)
                .fragment(null)
                .path("/ott/submit")
                .queryParam("token", oneTimeToken.getTokenValue()); // ②
        String magicLink = builder.toUriString();
        String email = getUserEmail(oneTimeToken.getUsername()); // ③
        this.mailSender.send(email, "Your Spring Security One Time Token", "Use the following link to sign in into the application: " + magicLink); // ④
        this.redirectHandler.handle(request, response, oneTimeToken); // ⑤
    }
    
    // ...
}

5.更改一次性令牌生成 URL

默認情況下, GenerateOneTimeTokenFilter 監(jiān)聽 POST /ott/generate 請求。該 URL 可以通過使用 tokenGeneratingUrl(String) DSL 方法進行更改:

// Java示例
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) {
        http
            // ...
            .formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin((ott) -> ott
                .tokenGeneratingUrl("/ott/my-generate-url")
            );
        return http.build();
    }
}

TIPS:2025.1.14官方文檔示例是使用generateTokenUrl(String)DSL 方法進行更改。我使用 Spring Security6.4.2 測試時,只有tokenGeneratingUrl(String)DSL 方法。因此示例修改為tokenGeneratingUrl(String)DSL 方法。

6.自定義如何生成和消費令牌

定義生成和使用一次性令牌常見操作的接口是OneTimeTokenService。Spring Security默認使用InMemoryOneTimeTokenService,生產(chǎn)環(huán)境可考慮使用JdbcOneTimeTokenService

一些自定義 OneTimeTokenService 最常見的原因包括但不限于:

  • 更改一次性令牌過期時間
  • 存儲更多生成令牌請求的信息
  • 更改令牌值的創(chuàng)建方式
  • 在使用一次性令牌時進行額外驗證

有兩種自定義方式:

  • 一是將自定義的OneTimeTokenService作為Bean提供,會被oneTimeTokenLogin()DSL自動識別:
@Configuration 
@EnableWebSecurity 
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) {
        http.formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin(Customizer.withDefaults());
        return http.build();
    }
    @Bean
    public OneTimeTokenService oneTimeTokenService() {
        return new MyCustomOneTimeTokenService();
    }
}
  • 二是將OneTimeTokenService實例傳遞給DSL,當存在多個SecurityFilterChain且每個需要不同的OneTimeTokenService時,這種方式很有用:
@Configuration 
@EnableWebSecurity 
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) {
        http.formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin((ott) -> ott
                .tokenService(new MyCustomOneTimeTokenService())
            );
        return http.build();
    }
}

TIPS:2025.1.14官方文檔示例是使用oneTimeTokenService(OneTimeTokenService)DSL 方法進行更改。我使用 Spring Security6.4.2 測試時,只有tokenService(OneTimeTokenService)DSL 方法。因此示例修改為tokenService(OneTimeTokenService)DSL 方法。

7.完整配置示例

@Configuration 
@EnableWebSecurity 
public class SecurityConfig {
    
        /**
     * 配置Security過濾鏈,用于處理一次性令牌登錄
     *
     * @param http 用于配置Web安全的HttpSecurity對象
     * @param userDetailsService 自定義的用戶信息查詢服務(wù),實現(xiàn)了UserDetailsService接口
     * @param mailSender 郵件服務(wù)
     * @return 配置好的SecurityFilterChain對象
     */
    @Bean
    @Order(1)
    public SecurityFilterChain ottLoginSecurityFilterChain(HttpSecurity http, final OneTimeTokenService oneTimeTokenService, final UserDetailsServiceImpl userDetailsService, final JavaMailSender mailSender) throws Exception {
        http.formLogin(Customizer.withDefaults())
                // 配置請求授權(quán),所有請求都需要認證
                .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
                // 禁用CSRF保護,適用于本例的簡單演示場景,在生產(chǎn)環(huán)境中需謹慎考慮。若是前后端分離情況,需要禁用
                .csrf(CsrfConfigurer::disable)
                // 配置一次性令牌登錄
                .oneTimeTokenLogin(ott->ott
                        // (可選)設(shè)置一次性令牌生成的URL,默認值:/ott/generate
                        .tokenGeneratingUrl("/ott/generate")
                        // (可選)設(shè)置一次性令牌生成和驗證的服務(wù),實現(xiàn)OneTimeTokenService接口,默認值:InMemoryOneTimeTokenService實例
                        .tokenService(oneTimeTokenService)
                        // (必做)配置一次性令牌生成成功后的處理,默認值:RedirectOneTimeTokenGenerationSuccessHandler實例
                        .tokenGenerationSuccessHandler(new CustomOneTimeTokenGenerationSuccessHandler(mailSender))
                        // (可選)設(shè)置是否顯示默認的登錄提交頁面,默認值:true
                        .showDefaultSubmitPage(true)
                        // (可選)設(shè)置默認登錄提交頁面的URL,默認值:/login/ott
                        .defaultSubmitPageUrl("/login/ott")
                        // (可選)配置認證轉(zhuǎn)換器,用于將HTTP請求中的token轉(zhuǎn)換為OneTimeTokenAuthenticationToken認證對象,默認值:OneTimeTokenAuthenticationConverter實例
                        .authenticationConverter(new OneTimeTokenAuthenticationConverter())
                        // (可選)配置認證提供者,用于驗證OneTimeTokenAuthenticationToken認證對象,默認值:OneTimeTokenAuthenticationProvider實例
                        .authenticationProvider(new OneTimeTokenAuthenticationProvider(oneTimeTokenService, userDetailsService))
                        // (可選)配置認證成功后的處理
                        .authenticationSuccessHandler(((request, response, authentication) -> {
                            System.out.println("認證成功");
                        }))
                        // (可選)配置認證失敗后的處理
                        .authenticationFailureHandler((request, response, exception) -> {
                            System.out.println("認證失敗:"+exception.getMessage());
                            new DefaultRedirectStrategy().sendRedirect(request, response, "/login");
                        })
                        // (可選)設(shè)置一次性令牌登錄成功后的URL
                        // (必做)添加一個 /login/ott 類型的HTTP接口
                        .loginProcessingUrl("/login/ott")

                );

        // 構(gòu)建并返回配置好的Security過濾鏈
        return http.build();
    }
    
    @Bean
    public OneTimeTokenService oneTimeTokenService() {
        // 創(chuàng)建自定義的OneTimeTokenService實例
        // 為了測試方便,直接使用內(nèi)置的InMemoryOneTimeTokenService
        return new InMemoryOneTimeTokenService();
    }
    
    static class CustomOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {

        private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
        private final JavaMailSender mailSender;

        public CustomOneTimeTokenGenerationSuccessHandler(JavaMailSender mailSender) {
            this.mailSender = mailSender;
        }

        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) throws IOException, ServletException {
            // 發(fā)送郵件或短信給用戶
            UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request))
                    .replacePath(request.getContextPath())
                    .replaceQuery(null)
                    .fragment(null)
                    .path("/login/ott") // 設(shè)置默認登錄提交頁面的URL
                    .queryParam("token", oneTimeToken.getTokenValue());
            String magicLink = builder.toUriString();
            // 發(fā)件人:從數(shù)據(jù)庫或配置文件中讀取,這里只是測試,直接寫死
            String from = "test@test.com";
            String email = getUserEmail(oneTimeToken.getUsername());

            SimpleMailMessage msg = new SimpleMailMessage();
            msg.setFrom(from);
            msg.setTo(email);
            msg.setSubject("Your Spring Security One Time Token");
            msg.setText("Use the following link to sign in into the application: " + magicLink);

            this.mailSender.send(msg);

            // 為了測試方便,重定向到token提交頁
            String redirectUrl = "/login/ott?token=" + oneTimeToken.getTokenValue();
            this.redirectStrategy.sendRedirect(request, response, redirectUrl);
        }

        private String getUserEmail(String username) {
            // TODO 獲取用戶郵箱地址
            return "test@test.com";
        }
    }
}
@RestController
public class OTTLoginController {

    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    @PostMapping("/login/ott")
    public void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
        System.out.println("認證成功后跳轉(zhuǎn)到主頁");
        this.redirectStrategy.sendRedirect(request,response,"/");
    }

}

通過以上配置,可以根據(jù)具體需求靈活使用Spring Security的一次性令牌登錄功能。

引用

One-Time Token Login

到此這篇關(guān)于SpringSecurity6.4中一次性令牌登錄(One-Time Token Login)實現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity 一次性令牌登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot查詢PGSQL分表后的數(shù)據(jù)的代碼示例

    SpringBoot查詢PGSQL分表后的數(shù)據(jù)的代碼示例

    數(shù)據(jù)庫用的pgsql,在表數(shù)據(jù)超過100w條的時候執(zhí)行定時任務(wù)進行了分表,分表后表名命名為原的表名后面拼接時間,但是我在java業(yè)務(wù)代碼中,我想查詢之前的那條數(shù)據(jù)就查不到了,本文給大家介紹了SpringBoot中如何查詢PGSQL分表后的數(shù)據(jù),需要的朋友可以參考下
    2024-05-05
  • springmvc后臺基于@ModelAttribute獲取表單提交的數(shù)據(jù)

    springmvc后臺基于@ModelAttribute獲取表單提交的數(shù)據(jù)

    這篇文章主要介紹了springmvc后臺基于@ModelAttribute獲取表單提交的數(shù)據(jù),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-10-10
  • Java執(zhí)行shell命令的實現(xiàn)

    Java執(zhí)行shell命令的實現(xiàn)

    本文主要介紹了Java執(zhí)行shell命令的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • Java列出2到100之間所有素數(shù)的方法

    Java列出2到100之間所有素數(shù)的方法

    這篇文章主要介紹了Java列出2到100之間所有素數(shù)的方法,涉及java數(shù)值運算的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • Java創(chuàng)建多線程的幾種方式實現(xiàn)

    Java創(chuàng)建多線程的幾種方式實現(xiàn)

    這篇文章主要介紹了Java創(chuàng)建多線程的幾種方式實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • SpringBoot @ComponentScan掃描的局限性方式

    SpringBoot @ComponentScan掃描的局限性方式

    文章總結(jié):SpringBoot的@ComponentScan注解在掃描組件時存在局限性,只能掃描指定的包及其子包,無法掃描@SpringBootApplication注解自動配置的組件,使用@SpringBootApplication注解可以解決這一問題,它集成了@Configuration、@EnableAutoConfiguration
    2025-01-01
  • SpringMVC form標簽引入及使用方法

    SpringMVC form標簽引入及使用方法

    這篇文章主要介紹了SpringMVC form標簽引入及使用方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-06-06
  • 深入理解Java設(shè)計模式之職責(zé)鏈模式

    深入理解Java設(shè)計模式之職責(zé)鏈模式

    這篇文章主要介紹了JAVA設(shè)計模式之職責(zé)鏈模式的的相關(guān)資料,文中示例代碼非常詳細,供大家參考和學(xué)習(xí),感興趣的朋友可以了解
    2021-11-11
  • Java使用easyExcel導(dǎo)出excel數(shù)據(jù)案例

    Java使用easyExcel導(dǎo)出excel數(shù)據(jù)案例

    這篇文章主要介紹了Java使用easyExcel導(dǎo)出excel數(shù)據(jù)案例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • Java常用字符串工具類 字符串智能截取(3)

    Java常用字符串工具類 字符串智能截?。?)

    這篇文章主要為大家詳細介紹了Java常用字符串工具類,字符串的智能截取,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05

最新評論