掃碼登錄認證技術原理介紹及實踐

一、背景

最近業務要求PC端系統登錄使用APP應用掃碼登錄。

主要目的是:

1、簡化用戶錄入賬號密碼,達到快速登錄PC;

2、賬號登錄使用更加安全性;

3、爲了推廣更多讓大家打開使用APP(因爲行業的特殊性,實際業務場景中大都設計師都在使用PC端設計軟件,同時也習慣了PC端下單)。

 

二、處理流程

1、業務流程圖

 

因爲掃碼的時候有兩種處理邏輯,所以流程圖有業務處理方案。但不管哪種方案,背後技術處理邏輯是一樣的。

 

2、技術實現設計流圖程

3、處理步驟說明

a、用戶打開PC登錄頁面,PC登錄頁面向認證中心發起請求,認證中心生成uuid等信息,返回uuid等信息給前端,前端展示一個包含uuid的二維碼。

b、PC端登錄頁面定時向認證中心輪詢二維碼的狀態。

c、用戶登錄移動端,打開移動端攝像頭掃描PC端登錄頁面的二維碼。

d、移動端將二維碼中包含的uuid等信息發送給認證中心,認證中心將二維碼狀態設置爲“掃描成功”。

e、PC端登錄頁面輪詢到二維碼狀態爲“掃描成功”,提示“掃描成功”,以下圖片僅供參考。

 

 

 

 

 

f、移動端展示消息確認彈出框,顯示“登錄”、“取消登錄”按鈕,同時將移動端當前登錄的用賬號、當前移動端登錄的token和二維碼uuid等信息發送給認證中心。      

g、認證中心將用戶所選要登錄的賬號保存在二維碼信息裏面,並將二維碼狀態設置爲“已授權”。

h、登錄頁面從輪詢二維碼不存在時,提示“二維碼已過期” ,以下圖片僅供參考。

i、登錄頁面從輪詢二維碼狀態爲“已取銷”時,提示“你已取消此次操作,你可再次掃描,或關閉窗口”。

j、登錄頁面從輪詢二維碼狀態爲“已授權”時,認證中心生成PC端登錄的token,設置cookie,並向PC端前端發起重定向跳轉。

 

程序處理時序圖

 

三、代碼實現

auth2認證最簡單的代碼結構示例

public class Auth2Login {
    public static void main(String[] args) {
        //Step 1: 獲取授權請求URL
        String authRequestUrl = "https://example.com/oauth/authorize";

        //Step 2: 向授權服務器發送請求,獲取授權碼
        String authCode = getAuthCode(authRequestUrl);

        //Step 3: 使用授權碼,向認證服務器發送請求,獲取access token
        String accessToken = getAccessToken(authCode);

        //Step 4: 使用access token,訪問資源服務器,進行用戶登錄
        String userInfo = getUserInfo(accessToken);

        //Step 5: 根據user info進行用戶登錄
        login(userInfo);
    }

    public static String getAuthCode(String authRequestUrl) {
        //TODO
        return null;
    }

    public static String getAccessToken(String authCode) {
        //TODO
        return null;
    }

    public static String getUserInfo(String accessToken) {
        //TODO
        return null;
    }

    public static void login(String userInfo) {
        //TODO
    }
}

 

掃碼登錄認證關鍵代碼片段

/**
     * 初始化,主要通過請求基本參婁生成UUID,並把uuid寫入redis
     * @param cmd 請求參數
     * @return
     */
    public Response init(LoginQrCodeInitCmd cmd) {

        String clientId = cmd.getClientId();
        String clientRedirectUri = cmd.getClientRedirectUri();

        ClientDetailsE clientDetails = oauthService.loadClientDetails(clientId);
        if (clientDetails == null || clientDetails.getId() == null) {
            return Response.buildFailure(AuthcenterCode.INVALID_CLIENT, String.format(AuthcenterCode.INVALID_CLIENT.getDesc(), clientId));
        }

        if (!clientDetails.getGrantTypes().contains(GrantType.QR_CODE.toString())) {
            return Response.buildFailure(AuthcenterCode.INVALID_GRANT_TYPE, String.format(AuthcenterCode.INVALID_GRANT_TYPE.getDesc(), clientId));
        }

        LoginQrCodeE qrCodeE = LoginQrCodeE.instance().init(clientId, clientRedirectUri);
        return DataResponse.of(BeanToolkit.instance().copy(qrCodeE, LoginQrCodeCO.class));
    }

    /**
     * 通過UUID獲取登錄二維碼
     * @param uuid 唯一字符串
     * @return QR code對象
     */
    public LoginQrCodeE getLoginQrCode(String uuid) {
        return LoginQrCodeE.instance().of(uuid);
    }

    /**
     * 通過UUID掃碼
     * @param uuid 唯一字符串
     * @return
     */
    public Response scan(String uuid) {
        LoginQrCodeE.instance().scan(uuid);
        return Response.buildSuccess();
    }

    /**
     * 取消登錄確認
     * @param uuid 唯一字符串
     * @return
     */
    public Response cancel(String uuid) {
        LoginQrCodeE.instance().cancel(uuid);
        return Response.buildSuccess();
    }

    /***
     * 驗證登錄
     * @param cmd 用戶登錄對象信息
     * @return 如果成功返回登錄信息結構體
     */
    public Response authorize(LoginQrCodeAuthorizeCmd cmd) {

        String uuid = cmd.getUuid();
        String selectedAccountId = cmd.getSelectedAccountId();
        String token = cmd.getToken();

        //是否有掃碼
        if (LoginQrCodeE.instance().of(uuid).notScanned()) {
            return Response.buildFailure(AuthcenterCode.QR_CODE_NOT_SCANNED);
        }

        /**
         * 找出token
         */
        AccessTokenE accessTokenE = oauthRepository.findAccessToken(token);
        if (accessTokenE == null) {
            return Response.buildFailure(AuthcenterCode.INVALID_TOKEN);
        }

        AccountE userAccount = oauthRepository.findAccountByToken(token);
        if (userAccount == null) {
            // 當前令牌不存在用戶態(賬號)
            return Response.buildFailure(AuthcenterCode.TOKEN_ACCOUNT_RELA_NOT_EXIST);
        }
        List<String> userAccountIds = accountRepository.forceGetAccountIdsByMainUserId(userAccount.getMainUserId());
        if (userAccountIds == null) {
            // 當前賬號異常
            return Response.buildFailure(AuthcenterCode.UNKNOWN_ACCOUNT);
        }

        if (!userAccountIds.contains(selectedAccountId)) {
            // 所選賬號與當前令牌登錄人信息不一致
            return Response.buildFailure(AuthcenterCode.INVALID_SWITCH_ACCOUNT);
        }

        LoginQrCodeE.instance().authorize(uuid, selectedAccountId);
        return Response.buildSuccess();
    }

    /**
     * 對外提供輪旬時間服務方法,當查詢redis key=uuid是否超時
     * @param uuid 用戶訪問請求的UUID
     * @return 登錄碼狀態對象
     * @throws OAuthSystemException
     */
    public LoginQrCodeE handle(String uuid) throws OAuthSystemException {
        LoginQrCodeE loginQrCode = getLoginQrCode(uuid);
        // 當處於“已授權”狀態時,才能觸發準備登錄
        if (loginQrCode.authorized()) {
            return loginQrCode.ready();
        }

        // 當處於“準備登錄”狀態時,才能觸發登錄
        if (loginQrCode.loginReady()) {
            return login(loginQrCode);
        }

        return loginQrCode;
    }

    /**
     * 掃碼登錄
     * @param loginQrCode 二維碼帶的對象信息
     * @return
     * @throws OAuthSystemException 認證異常
     */
    public LoginQrCodeE login(LoginQrCodeE loginQrCode) throws OAuthSystemException {
        String clientId = loginQrCode.getClientId();
        String accountId = loginQrCode.getAccountId();
        ClientDetailsE clientDetails = clientDetailsRepository.findByClientId(clientId);
        AccountE userAccount = accountRepository.getAccountById(accountId);
        accountRepository.checkAccount(userAccount);
        AuthorizeE authorize = oauthRepository.findAccountAuthorizeByAccountId(accountId);
        authorizeRepository.checkAuthorizeDataIntegrity(authorize);
        if (authorize == null) {
            throw new UnknownAuthorizeException("Cannot find AuthorizeE mainUserId="+mainUserId);
        }
        AccessTokenE accessToken = oauthService.retrieveQrCodeAccessToken(clientDetails, authorize, userAccount, new HashSet<>(),
                new BizCodeE(loginQrCode.getAppCode(), loginQrCode.getSubAppCode()));

        return loginQrCode.login(accessToken.getToken(), accessToken.getRefreshToken(), accessToken.getCastgt());
    }

 

 代碼僅是展示關鍵的處理過程,結構還是比較清晰的;這裏不提供完整的項目工程,因爲這是公司的產權,況且每個公司的業務要求不同,大家理解後再去實現的自己掃碼認證邏輯,處理方法大同小異。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章