一掃即入,如何通過微信公衆號掃碼登錄網站?

本文收錄於 Github.com/niumoo/JavaNotes,Java 系列文檔,數據結構與算法!
本文收錄於網站:https://www.wdbyte.com/,我的公衆號:程序猿阿朗

引言

想象一下,週五晚上,你打開電腦,打算刷一刷最新上線的劇集,突然彈出網站登錄,哎呀,那個超級複雜的密碼是什麼來着?那一堆數字、字母和符號的大雜燴在我腦海中有好幾個版本?能不能有一種簡單的方式,不用密碼就可以認真登錄,這簡直不要太棒?這時掃碼登錄出現了,它不僅方便而且更加安全。好比你向安保亮了一下你的 VIP 通行證,便放你通過。

微信作爲國民級應用,微信掃碼登錄再常見不過了,它就像你的口袋裏的萬能鑰匙,去哪兒都不怕。不過微信掃碼登錄也有多種方式,如掃碼授權登錄,掃碼關注公衆號登錄等。這篇文章一起聊聊微信公衆號二維碼登錄是怎麼回事,它的工作流程是什麼,它怎麼保證你的身份安全。以及,如果你是一個開發者,如何在自己的網站上增加掃碼登錄。

公衆號掃碼登錄優勢

快捷方便

用戶只需打開微信掃一掃,幾秒鐘內就能完成登錄。這簡化了傳統的輸入用戶名和密碼的繁瑣過程,不過前提是你已經安裝了必要的 APP。

增強安全性

掃碼登錄的身份認證在服務端完成,而且如微信公衆號掃碼登錄這種方式,網站只需要拿到一個用戶身份標識用於識別用戶,不需要存儲用戶的額外信息。非常安全。

提升用戶粘性

通過掃描二維碼登錄把用戶引入公衆號關注,在登錄的同時還可以爲公衆號引流,提升用戶粘性,同時公衆號是一個非常方便的用戶觸達方式,未來新功能的發佈可以及時送達用戶。

公衆號掃碼登錄實現原理

要想理清公衆號掃碼登錄的實現原理,首先要知道在掃碼登錄過程中,有哪些參與方,它們之間的工作流程是怎麼樣的。這裏的參與方有用戶、瀏覽器、網站服務端、微信服務端四個參與方,總體的工作流程如下圖,下面會進行詳細介紹。

用戶:用戶是掃碼登錄的發起方,點擊登錄,然後掃描登錄二維碼。

瀏覽器:瀏覽器爲用戶展示二維碼,然後不斷的輪詢掃碼狀態。

服務端:網站服務端需要向微信服務端獲取攜帶 Ticket 信息的公衆號二維碼,在微信服務端回調時綁定用戶身份信息。

微信服務端:用戶掃碼後,會請求到微信服務端,微信服務端會攜帶掃描的二維碼的 Ticket 和用戶身份標識回調網站服務端。

微信服務端回調網站服務端時,攜帶的用戶身份信息其實只是一串無意義字符串,但是微信可以保證的是同一個微信用戶掃碼時攜帶的身份信息字符是相同的,以此識別用戶。也因此公衆號掃碼登錄用作身份認證非常安全。

開發準備工作

公衆號

首先你要有用於掃碼登錄的微信公衆號,微信公衆平臺提供了測試平臺,可以直接生成測試公衆號。

微信測試號:https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index

開發者文檔

獲取公衆號二維碼的過程需要參考微信公衆號官方文檔,下面幾篇內容需要重點關注。

  1. 公衆號接口接入指南

    鏈接:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

  2. 獲取 Access Token

    Access Token 是公衆號的全局唯一接口調用憑據,公衆號調用各接口時都需使用 Access Token 。每次獲取有效期目前爲2個小時,需定時刷新,重複獲取將導致上次獲取的 Access Token 失效。

    鏈接:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

  3. 生成帶 Ticket 二維碼

    使用該接口可以獲得多個帶不同場景值的二維碼,用戶掃描後,公衆號可以接收到事件推送。

    鏈接:https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html

  4. 接收事件推送

    在用戶掃碼後微信服務端會回調網站服務端,開發者需要按照指定消息格式對消息進行驗證處理。如獲取二維碼的 Ticket。

    鏈接:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html

  5. 回覆文本消息

    如果想要在用戶掃碼完成後自動響應如 “登錄成功” 之類的提示語,需要參考此文檔。

    鏈接:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html

網站服務端

想要微信服務端成功回調,服務端必須外網可以訪問,同時微信限制了端口只能是 80 或 443 ,因此只有兩種選擇 。

  1. 擁有自己的雲服務器(這裏推薦我司阿里雲服務器,如果只是學習體驗,搶佔式實例低配置一小時也就2毛錢左右,可以用於測試)。
  2. 用內網穿透軟件生成外網代理(一般 80 端口都需要收費)。

具體開發

配置微信公衆號

可以在微信測試號平臺上配置用於測試。

鏈接:https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index

項目總體結構

項目使用 SpringBoot 3.2.3 + Java 21 進行開發,這裏爲了方便演示,在一個 Maven 項目中完成所有代碼。本文步驟中只會給出關鍵代碼部分,完整代碼可以在文末的 GitHub 地址中找到。

下面是項目總體結構:

├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── wdbyte
        │           └── weixin
        │               ├── SpringBootApp.java
        │               ├── config
        │               │   └── JwtFilter.java   # JWT 身份認證攔截器
        │               ├── controller
        │               │   ├── WeixinServerController.java # 微信服務端調用接口
        │               │   └── WeixinUserController.java  # 瀏覽器調用接口
        │               ├── model
        │               │   ├── ApiResult.java     
        │               │   ├── ReceiveMessage.java  # 微信消息封裝類
        │               │   └── WeixinQrCode.java # 微信二維碼 Ticket 封裝類
        │               ├── service
        │               │   ├── WeixinUserService.java # 微信調用處理類
        │               │   └── impl
        │               │       └── WeixinUserServiceImpl.java
        │               └── util
        │                   ├── AesUtils.java # AES 加密工具類
        │                   ├── ApiResultUtil.java
        │                   ├── HttpUtil.java # HTTP 工具類
        │                   ├── JwtUtil.java # JWT 工具類
        │                   ├── KeyUtils.java 
        │                   ├── WeixinApiUtil.java # 微信 API 工具類,如獲取 AccessToken
        │                   ├── WeixinMsgUtil.java # 微信消息工具類
        │                   ├── WeixinQrCodeCacheUtil.java # 微信二維碼Ticket緩存
        │                   └── XmlUtil.java 
        └── resources
            ├── application.properties #配置文件
            ├── static
            └── templates

WeixinServerControllerWeixinUserController 暴漏了三個 API。

/weixin/check :用於對接微信服務端,接收微信服務端的調用。

/user/qrcode: 用於獲取二維碼圖片信息

/user/login/qrcode: 用於校驗是否掃描成功,成功則返回身份認證後的 JWT 字符串。

項目公衆號信息配置

application.properties 中配置公衆號所需的配置。

server.port=
weixin.appid=
weixin.appsecret=
weixin.token=

ase.util.secret=
key.jwt.secret=

驗證簽名

開發者提交信息後,微信服務器向填寫的 URL 發送 Get 請求,攜帶參數如下:

參數 描述
signature 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。
timestamp 時間戳
nonce 隨機數
echostr 隨機字符串

開發者需要對 signature 進行校驗,判斷是否來自微信服務器,公衆號相關的其他事件如消息、關注、掃碼等一樣會回調配置的 URL ,只不過這時是 POST 請求。

驗籤邏輯查看微信文檔:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

Java 實現如下:

// com.wdbyte.weixin.service.impl.WeixinUserServiceImpl.java

@Value("${weixin.token}")
private String token;

@Override
public void checkSignature(String signature, String timestamp, String nonce) {
    String[] arr = new String[] {token, timestamp, nonce};
    Arrays.sort(arr);
    StringBuilder content = new StringBuilder();
    for (String str : arr) {
        content.append(str);
    }
    String tmpStr = DigestUtils.sha1Hex(content.toString());
    if (tmpStr.equals(signature)) {
        log.info("check success");
        return;
    }
    log.error("check fail");
    throw new RuntimeException("check fail");
}

獲取 Access Token

獲取帶有 Ticket 的公衆號二維碼之前,需要先獲取公衆號的 Access Token,這是調用微信公衆號所有接口的前提。 Access Token 每日調用次數有限,應該進行緩存。

// com.wdbyte.weixin.util.WeixinApiUtil.java
@Value("${weixin.appid}")
public String appId;

@Value("${weixin.appsecret}")
public String appSecret;

private static String ACCESS_TOKEN = null;
private static LocalDateTime ACCESS_TOKEN_EXPIRE_TIME = null;

/**
 * 獲取 access token
 *
 * @return
 */
public synchronized String getAccessToken() {
    if (ACCESS_TOKEN != null && ACCESS_TOKEN_EXPIRE_TIME.isAfter(LocalDateTime.now())) {
        return ACCESS_TOKEN;
    }
    String api = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret="
        + appSecret;
    String result = HttpUtil.get(api);
    JSONObject jsonObject = JSON.parseObject(result);
    ACCESS_TOKEN = jsonObject.getString("access_token");
    ACCESS_TOKEN_EXPIRE_TIME = LocalDateTime.now().plusSeconds(jsonObject.getLong("expires_in") - 10);
    return ACCESS_TOKEN;
}

生成登錄二維碼

使用 Access Token 獲取二維碼 Ticket 用來換取二維碼圖片。

// com.wdbyte.weixin.util.WeixinApiUtil.java
private static String QR_CODE_URL_PREFIX = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";

/**
 * 二維碼 Ticket 過期時間
 */
private static int QR_CODE_TICKET_TIMEOUT = 10 * 60;


/**
 * 獲取二維碼 Ticket
 *
 * https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
 *
 * @return
 */
public WeixinQrCode getQrCode() {
    String api = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getAccessToken();
    String jsonBody = String.format("{\n"
        + "  \"expire_seconds\": %d,\n"
        + "  \"action_name\": \"QR_STR_SCENE\",\n"
        + "  \"action_info\": {\n"
        + "    \"scene\": {\n"
        + "      \"scene_str\": \"%s\"\n"
        + "    }\n"
        + "  }\n"
        + "}", QR_CODE_TICKET_TIMEOUT, KeyUtils.uuid32());
    String result = HttpUtil.post(api, jsonBody);
    log.info("get qr code params:{}", jsonBody);
    log.info("get qr code result:{}", result);
    WeixinQrCode weixinQrCode = JSON.parseObject(result, WeixinQrCode.class);
    weixinQrCode.setQrCodeUrl(QR_CODE_URL_PREFIX + URI.create(weixinQrCode.getTicket()).toASCIIString());
    return weixinQrCode;
}
class WeixinQrCode {
    private String ticket;
    private Long expireSeconds;
    private String url;
    private String qrCodeUrl;
}

響應內容格式如下:

{
  "ticket": "gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm3sUw==",
  "expire_seconds": 60,
  "url": "http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"
}

其中 Ticket 就是二維碼憑證,用戶掃碼後微信會把此 Ticket 回調給網站服務端。可以在下面的鏈接後面拼上 Ticket 換取二維碼圖片。

https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=

掃碼回調與身份綁定

用戶掃碼後,微信服務端會把二維碼鞋帶的 Ticket 和用戶的身份標識作爲消息內容回調到網站服務器。

格式如下:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[subscribe]]></Event>
  <EventKey><![CDATA[qrscene_123123]]></EventKey>
  <Ticket><![CDATA[TICKET]]></Ticket>
</xml>

其中 FromUserName 是用戶身份標識,EventKeyqrscene_標識掃碼,Ticket 則是二維碼的 Ticket。至此,服務端就可以識別出二維碼是被哪個用戶掃碼了。綁定 Ticket 和用戶身份標識。

注:FromUserName 是唯一的用戶身份標識,同一個用戶每次掃描的 FromUserName 相同。

瀏覽器輪詢掃描狀態

瀏覽器鞋帶 Ticket 信息不斷的輪詢 /user/login/qrcode 接口查看 Ticket 是否被掃描成功,如果通過 Ticket 可以查到用戶身份標識,說明二維碼被掃描成功,返回用戶信息。登錄完成。

掃碼登錄測試

瀏覽器不斷的輪詢 https://api.wdbyte.com/user/login/qrcode?ticket=Ticket值 獲取掃碼狀態。

二維碼尚未掃描,則返回:

{
  "code": -1,
  "data": "check faild",
  "message": "error"
}

微信掃碼關注公衆號。

掃碼成功後輪詢接口會響應 JWT 格式的身份信息,這裏使用了 AES 對 JWT 進行了加密。

{
  "code": 200,
  "data": "mihzE8Z1Y9t2EoppNSzzytV4TOgn+Nc50ORZjsW/oVkxchL4EzGA6rr1tQ0Q7J24Ipm4otjCYf95Nu8JbV31Q/ImKvlta3f5bgvOdWSlO2tNvOwqgzBSItABohbCLVLxjGCci4VtNaEFgQjoDjc1uhwP/GCSohVFc7csO9SxpOm8HKtlRhATjwPrtiQ9iLErfsUs27I0k5OHp55AzuQOYCvza//i3wk8nlv/MDkk7y1nvsZkllyKQGHPB4Ulcraz",
  "message": "success"
}

至此,登錄完成。

完整代碼: github.com/niumoo/JavaNotes/tree/master/springboot/springboot-weixin-qrcode-login

一如既往,文章中代碼存放在 Github.com/niumoo/javaNotes.

本文收錄於 Github.com/niumoo/JavaNotes,Java 系列文檔,數據結構與算法!
本文收錄於網站:https://www.wdbyte.com/,我的公衆號:程序猿阿朗

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