Spring Security 解析(三) —— 個性化認證 以及 RememberMe 實現

来源:https://www.cnblogs.com/bug9/archive/2019/08/28/11426879.html
-Advertisement-
Play Games

Spring Security 解析(三) —— 個性化認證 以及 RememberMe 實現   在學習Spring Cloud 時,遇到了授權服務oauth 相關內容時,總是一知半解,因此決定先把Spring Security 、Spring Security Oauth2 ...


Spring Security 解析(三) —— 個性化認證 以及 RememberMe 實現

  在學習Spring Cloud 時,遇到了授權服務oauth 相關內容時,總是一知半解,因此決定先把Spring Security 、Spring Security Oauth2 等許可權、認證相關的內容、原理及設計學習並整理一遍。本系列文章就是在學習的過程中加強印象和理解所撰寫的,如有侵權請告知。

項目環境:

  • JDK1.8
  • Spring boot 2.x
  • Spring Security 5.x

一、個性化認證

(一) 配置登錄

   在 授權過程 和 認證過程 中我們都是使用的 Security 預設的一個登錄頁面(/login),那麼如果我們想自定義一個登錄頁面該如何實現呢?其實很簡單,我們新建 FormAuthenticationConfig 配置類,然後在configure(HttpSecurity http) 方法中實現以下設置:

        http.formLogin()
                //可以設置自定義的登錄頁面 或者 (登錄)介面
                // 註意1: 一般來說設置成(登錄)介面後,該介面會配置成無許可權即可訪問,所以會走匿名filter, 也就意味著不會走認證過程了,所以我們一般不直接設置成介面地址
                // 註意2: 這裡配置的 地址一定要配置成無許可權訪問,否則將出現 一直重定向問題(因為無許可權後又會重定向到這裡配置的登錄頁url)
                .loginPage(securityProperties.getLogin().getLoginPage())
                //.loginPage("/loginRequire")
                // 指定驗證憑據的URL(預設為 /login) ,
                // 註意1:這裡修改後的 url 會意味著  UsernamePasswordAuthenticationFilter 將 驗證此處的 url
                // 註意2: 與 loginPage設置的介面地址是有 區別, 一但 loginPage 設置了的是訪問介面url,那麼此處配置將無任何意義
                // 註意3: 這裡設置的 Url 是有預設無許可權訪問的
                .loginProcessingUrl(securityProperties.getLogin().getLoginUrl())
                //分別設置成功和失敗的處理器
                .successHandler(customAuthenticationSuccessHandler)
                .failureHandler(customAuthenticationFailureHandler);

  最後在 SpringSecurityConfig 的 configure(HttpSecurity http) 方法中 調用 formAuthenticationConfig.configure(http) 即可;

   正如看到的一樣,我們通過 loginPage()設置 登錄頁面介面, 通過 loginProcessingUrl() 設置 UsernamePasswordAuthenticationFilter 要匹配的 介面地址(一定是Post)(看過授權過程的同學應該都知道其預設的是/login)。 這裡有以下幾點值得註意:

  • loginPage() 這裡配置的 地址(不管是介面url還是登錄頁面)一定要配置成無許可權訪問,否則將出現 一直重定向問題(因為無許可權後又會重定向到這裡配置的登錄頁url
  • loginPage() 一般來說不直接設置成(登錄)介面,因為設置了介面會配置成無許可權即可訪問(當然設置成登錄頁面也需要配置無許可權訪問),所以會走匿名filter, 也就意味著不會走認證過程了,所以我們一般不直接設置成介面地址
  • loginProcessingUrl() 這裡修改後的 url 會意味著 UsernamePasswordAuthenticationFilter 將 驗證此處的 url
  • loginProcessingUrl() 這裡設置的 Url 是有預設無許可權訪問的,與 loginPage設置的介面地址是有 區別, 一但 loginPage 設置了的是介面url,那麼此處配置將無任何意義
  • successHandler() 和 failureHandler 分別 設置認證成功處理器 和 認證失敗處理器 (如果對這2個處理器沒印象的話,建議回顧下授權過程)

(二) 配置成功和失敗處理器

   在授權過程中,我們增簡單提及到過這2個處理器,在Security中預設的處理器分別是SavedRequestAwareAuthenticationSuccessHandler 和 SimpleUrlAuthenticationFailureHandler ,這次我們自定義這2個處理器,分別為 CustomAuthenticationSuccessHandler ( extends SavedRequestAwareAuthenticationSuccessHandler ) 重寫 onAuthenticationSuccess() 方法 :

@Component("customAuthenticationSuccessHandler")
@Slf4j
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    @Autowired
    private SecurityProperties securityProperties;

    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        logger.info("登錄成功");
        // 如果設置了loginSuccessUrl,總是跳到設置的地址上
        // 如果沒設置,則嘗試跳轉到登錄之前訪問的地址上,如果登錄前訪問地址為空,則跳到網站根路徑上
        if (!StringUtils.isEmpty(securityProperties.getLogin().getLoginSuccessUrl())) {
            requestCache.removeRequest(request, response);
            setAlwaysUseDefaultTargetUrl(true);
            setDefaultTargetUrl(securityProperties.getLogin().getLoginSuccessUrl());
        }
        super.onAuthenticationSuccess(request, response, authentication);
    }

}

和 CustomAuthenticationFailureHandler( extends SimpleUrlAuthenticationFailureHandler) 重寫 onAuthenticationFailure() 方法 :

@Component("customAuthenticationFailureHandler")
@Slf4j
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException {

        logger.info("登錄失敗");
        if (StringUtils.isEmpty(securityProperties.getLogin().getLoginErrorUrl())){

            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));

        } else {
            // 跳轉設置的登陸失敗頁面
            redirectStrategy.sendRedirect(request,response,securityProperties.getLogin().getLoginErrorUrl());
        }

    }
}

(三) 自定義的登陸頁面

這裡就不再描述,直接貼代碼:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>登錄</title>
</head>
<body>
<h2>登錄頁面</h2>
<form action="/loginUp" method="post">  
    <table>
        <tr>
            <td>用戶名:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密碼:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan='2'><input name="remember-me" type="checkbox" value="true"/>記住我</td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">登錄</button>
            </td>
        </tr>
    </table>
</form>
</body>
</html>

  註意這裡請求的地址是 loginProcessingUrl() 配置的地址

(四)測試驗證

  這裡就不在貼結果圖了,只要我們明白結果流程就行是這樣的就可以:
localhost:8080 ——> 點擊 測試驗證Security 許可權控制 ————> 跳轉到 我們自定義的 /loginUp.html 登錄頁,登錄後 ————> 有配置loginSuccessUrl,則跳轉到 loginSuccess.html;反之則直接跳轉到 /get_user/test 介面返回結果。 整個流程就全面涉及到了我們自定義的登錄頁面、自定義的登錄成功/失敗處理器。

二、 RememberMe (記住我)功能解析

(一)RememberMe 功能實現配置

首先我們一股腦的將rememberMe配置加上,然後看下現象:

1、 創建 persistent_logins 表,用於存儲token和用戶的關聯信息:

create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null);

2 、 添加rememberMe配置 信息

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        // 如果token表不存在,使用下麵語句可以初始化 persistent_logins(ddl在db目錄下) 表;若存在,請註釋掉這條語句,否則會報錯。
        //tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }
    
     @Override
    protected void configure(HttpSecurity http) throws Exception {

        formAuthenticationConfig.configure(http);
        http.   ....
                .and()
                // 開啟 記住我功能,意味著 RememberMeAuthenticationFilter 將會 從Cookie 中獲取token信息
                .rememberMe()
                // 設置 tokenRepository ,這裡預設使用 jdbcTokenRepositoryImpl,意味著我們將從資料庫中讀取token所代表的用戶信息
                .tokenRepository(persistentTokenRepository())
                // 設置  userDetailsService , 和 認證過程的一樣,RememberMe 有專門的 RememberMeAuthenticationProvider ,也就意味著需要 使用UserDetailsService 載入 UserDetails 信息
                .userDetailsService(userDetailsService)
                // 設置 rememberMe 的有效時間,這裡通過 配置來設置
                .tokenValiditySeconds(securityProperties.getLogin().getRememberMeSeconds())
                .and()
                .csrf().disable(); // 關閉csrf 跨站(域)攻擊防控
    }

這裡解釋下配置:

  • rememberMe() 開啟 記住我功能,意味著 RememberMeAuthenticationFilter 將會 從Cookie 中獲取token信息
  • tokenRepository() 配置 token的獲取策略,這裡配置成從資料庫中讀取
  • userDetailsService() 配置 UserDetaisService (如果不熟悉該對象,建議回顧認證過程)
  • tokenValiditySeconds() 設置 rememberMe 的有效時間,這裡通過 配置來設置

另一個重要的配置在登錄頁面,這裡的 必須是 name="remember-me" ,rememberMe就是通過驗證這個配置來開啟remermberMe功能的。

<input name="remember-me" type="checkbox" value="true"/>記住我</td>

  實操結果應該為:進入登陸頁面 ——> 勾選記住我後登錄 ——> 成功後,查看persistent_logins 表發現有一條數據——> 重啟項目 ——> 重新訪問需要登錄才能訪問的頁面,發現無需登錄即可訪問——> 刪除 persistent_logins 表數據,等待token設置的有效時間過期,然後重新刷新頁面發現跳轉到登陸頁面。

(二) RembemberMe 實現源碼解析

   首先我們查看UsernamePasswordAuthenticationFiler(AbstractAuthenticationProcessingFilter) 的 successfulAuthentication() 方法內部源碼:

protected void successfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {
        
        // 1 設置 認證成功的Authentication對象到SecurityContext中
        SecurityContextHolder.getContext().setAuthentication(authResult);
        
        // 2 調用 RememberMe 相關service處理
        rememberMeServices.loginSuccess(request, response, authResult);

        // Fire event
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }
        //3 調用成功處理器
        successHandler.onAuthenticationSuccess(request, response, authResult);
    }

其中我們發現我們本次重點關註的一行代碼: rememberMeServices.loginSuccess(request, response, authResult) , 查看這個方法內部源碼:

@Override
    public final void loginSuccess(HttpServletRequest request,
            HttpServletResponse response, Authentication successfulAuthentication) {
        // 這裡就在判斷用戶是否勾選了記住我
        if (!rememberMeRequested(request, parameter)) {
            logger.debug("Remember-me login not requested.");
            return;
        }

        onLoginSuccess(request, response, successfulAuthentication);
    }

通過 rememberMeRequested() 判斷是否勾選了記住我。
onLoginSuccess() 方法 最終會調用到 PersistentTokenBasedRememberMeServices 的 onLoginSuccess() 方法,貼出其方法源碼如下:

protected void onLoginSuccess(HttpServletRequest request,
            HttpServletResponse response, Authentication successfulAuthentication) {
        // 1 獲取賬戶名
        String username = successfulAuthentication.getName();
        
        // 2 創建  PersistentRememberMeToken 對象
        PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
                username, generateSeriesData(), generateTokenData(), new Date());
        try {
            // 3 通過 tokenRepository 存儲 persistentRememberMeToken 信息
            tokenRepository.createNewToken(persistentToken);
            // 4 將 persistentRememberMeToken 信息添加到Cookie中
            addCookie(persistentToken, request, response);
        }
        catch (Exception e) {
            logger.error("Failed to save persistent token ", e);
        }
    }

分析下源碼步驟:

  • 獲取 賬戶信息 username
  • 傳入 username 創建 PersistentRememberMeToken 對象
  • 通過 tokenRepository 存儲 persistentRememberMeToken信息
  • 將 persistentRememberMeToken 信息添加到Cookie中

  這裡的 tokenRepository 就是我們配置 rememberMe功能所設置的。經過上面的解析我們看到了rememberServices 將 創建一個 token 信息,並存儲到資料庫(因為我們配置的是資料庫存儲方式 JdbcTokenRepositoryImpl )中,並將token信息添加到Cookie中了。到這裡,我們看到了RememberMe實現前的一些業務處理,那麼後面如何實現RememberMe,我想大家心裡大概都有個底了。這裡直接拋出之前授權過程中我們沒有提及到的 filter 類 RememberMeAuthenticationFilter,它是介於 UsernamePasswordAuthenticationFilter 和 AnonymousAuthenticationFilter 之間的一個filter,它主要負責的就是前面的filter都沒有認證成功後從Cookie中獲取token信息然後再通過tokenRepository 獲取 登錄用戶名,然後UserDetailsServcie 載入 UserDetails 信息 ,最後創建 Authticaton(RememberMeAuthenticationToken) 信息再調用 AuthenticationManager.authenticate() 進行認證過程。

RememberMeAuthenticationFilter

  我們來看下 RememberMeAuthenticationFilter 的dofiler方法源碼:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            //  1 調用 rememberMeServices.autoLogin() 獲取Authtication 信息
            Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
                    response);

            if (rememberMeAuth != null) {
                // Attempt authenticaton via AuthenticationManager
                try {
                    // 2 調用 authenticationManager.authenticate() 認證
                    rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
                    
                    ......
                    }

                }
                catch (AuthenticationException authenticationException) {
                .....
            }

            chain.doFilter(request, response);
        }

我們主要關註 rememberMeServices.autoLogin(request,response) 方法實現,查看器源碼:

@Override
    public final Authentication autoLogin(HttpServletRequest request,
            HttpServletResponse response) {
        // 1 從Cookie 中獲取 token 信息
        String rememberMeCookie = extractRememberMeCookie(request);

        if (rememberMeCookie == null) {
            return null;
        }
        
        if (rememberMeCookie.length() == 0) {
            cancelCookie(request, response);
            return null;
        }

        UserDetails user = null;

        try {
            // 2 解析 token信息
            String[] cookieTokens = decodeCookie(rememberMeCookie);
            // 3 通過 token 信息 生成 Uerdetails 信息
            user = processAutoLoginCookie(cookieTokens, request, response);
            userDetailsChecker.check(user);

            logger.debug("Remember-me cookie accepted");
            // 4 通過 UserDetails 信息創建 Authentication 
            return createSuccessfulAuthentication(request, user);
        } 
        .....
    }

內部實現步驟:

  • 從Cookie中獲取 token 信息並解析
  • 通過 解析的token 生成 UserDetails (processAutoLoginCookie() 方法實現 )
  • 通過 UserDetails 生成 Authentication ( createSuccessfulAuthentication() 創建 RememberMeAuthenticationToken )

其中最關鍵的一部是 processAutoLoginCookie() 方法是如何生成UserDetails 對象的,我們查看這個方法源碼實現:

protected UserDetails processAutoLoginCookie(String[] cookieTokens,
            HttpServletRequest request, HttpServletResponse response) {
        final String presentedSeries = cookieTokens[0];
        final String presentedToken = cookieTokens[1];
        // 1 通過 tokenRepository 載入資料庫token信息
        PersistentRememberMeToken token = tokenRepository
                .getTokenForSeries(presentedSeries);

        PersistentRememberMeToken newToken = new PersistentRememberMeToken(
                token.getUsername(), token.getSeries(), generateTokenData(), new Date());
        // 2 判斷 用戶傳入token和數據中的token是否一致,不一致可能存在安全問題
        if (!presentedToken.equals(token.getTokenValue())) {
            tokenRepository.removeUserTokens(token.getUsername());
            throw new CookieTheftException(
                    messages.getMessage(
                            "PersistentTokenBasedRememberMeServices.cookieStolen",
                            "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
        }
        try {
            // 3 更新 token 並添加到Cookie中
            tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
                    newToken.getDate());
            addCookie(newToken, request, response);
        }
        catch (Exception e) {
            throw new RememberMeAuthenticationException(
                    "Autologin failed due to data access problem");
        }
        // 4 通過 UserDetailsService().loadUserByUsername() 方法載入UserDetails 信息並返回
        return getUserDetailsService().loadUserByUsername(token.getUsername());
    }

我們看下其內部步驟:

  • 通過 tokenRepository 載入資料庫token信息
  • 判斷 用戶傳入token和數據中的token是否一致,不一致可能存在安全問題
  • 更新 token 並添加到Cookie中
  • 通過 UserDetailsService().loadUserByUsername() 方法載入UserDetails 信息並返回

   看到這裡相信大家以下就明白了,當初為啥在啟用rememberMe功能時要配置 tokenRepository 和 UserDetailsService了。

這裡我就不再演示整個實現的流程了,老規矩,上流程圖:

https://img2018.cnblogs.com/blog/1772687/201908/1772687-20190828221251139-652651366.jpg

   本文介紹個性化認證和RememberMe的代碼可以訪問代碼倉庫中的 security 模塊 ,項目的github 地址 : https://github.com/BUG9/spring-security

         如果您對這些感興趣,歡迎star、follow、收藏、轉發給予支持!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 分散式配置中心 剝離配置文件,實現動態修改,自動更新。 【假設沒有分散式配置中心,修改配置文件後都需要重啟服務,對於數量龐多的微服務開發來說,就會非常繁瑣】 分散式配置中心有哪些 disconf(依賴zookeeper) zookeeper diamond (阿裡巴巴) Apollo Redis x ...
  • 一、迴圈結構 在程式當中總有一些需要反覆的/重覆的執行的代碼,假設沒有迴圈結構,那麼這段需要重覆執行的代碼自然式子最需要重覆編寫的,代碼無法得到重覆使用,所以多數編程語言都是支持迴圈結構的,將來把需要反覆執行的代碼片段放到“迴圈體”,再聯合“計數器”,共同控制這段需要反覆執行的代碼。 1.基本上所有 ...
  • 現在幾乎大部分的`App`都支持使用多個第三方賬號進行登錄,如:微信、QQ、微博等,我們把此稱為多賬號統一登陸。而這些賬號的表設計,流程設計至關重要,不然後續擴展性賊差。本文不提供任何代碼實操,但是梳理一下博主根據我司賬號模塊的設計,提供思路,僅供參考。 ...
  • 遞歸函數包含了對自身的調用。 階乘計算就是一個遞歸調用的經典例子。 ...
  • 1 匯流排 內部匯流排:實現CPU內部各器件(運算器、控制器、寄存器等)之間的聯繫。 匯流排(外部匯流排):實現CPU和主板上其他器件的聯繫。 地址匯流排 CPU通過地址匯流排指定存儲器單元。 一個CPU有N根地址線,則這個CPU的地址匯流排的寬度為N,這個CPU最多可以尋找2的N次方個記憶體單元(位元組)。 控制總 ...
  • 根據我的理解和搜集的資料,儘可能清晰完整的回答(逐步完善,持續更新) 1、String類為什麼是final的 首先分析String的源碼: 類被final關鍵字限定,說明它不可以被繼承,沒有子類。即持有一個String對象的引用,它必然是String類,而不會是其他的類。 value是用來存儲值的, ...
  • ★、本實例使用百度智能雲-人工智慧-人臉識別API實現。 ★、樓下安裝了刷臉進門。閑暇時無聊寫了個Demo 主界面顯示如下圖: 本實例,包括了所有人臉識別API的調用。 1、 創建樓號,對應API中創建用戶組,詳見: https://ai.baidu.com/docs#/Face-Set-V3/58 ...
  • python中有 try——except 的方法捕獲異常,可以獲取到異常的種類以及自定義異常, 但是有時候對於debug測試來說,信息不全,比如說 觸發異常的具體位置在哪: import traceback try: num= int('abc')except Exception: tracebac ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...