1. 程式人生 > 其它 >網頁實現微信登入

網頁實現微信登入

技術標籤:tool

目錄

01-OAuth2

一、OAuth2解決什麼問題

1、開放系統間授權

照片擁有者想要在雲沖印服務上列印照片,雲沖印服務需要訪問雲端儲存服務上的資源

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ndNEXKNB-1612363692996)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/856a9787-25cd-4ab5-be5b-4c9defd835b1/128/index_files/1135145046.jpg)]

2、圖例

資源擁有者:照片擁有者

客戶應用:雲沖印

受保護的資源:照片

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-p9aiHcok-1612363692998)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/856a9787-25cd-4ab5-be5b-4c9defd835b1/128/index_files/1135065625.jpg)]

3、方式一:使用者名稱密碼複製

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-z9rOJ430-1612363693000)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/856a9787-25cd-4ab5-be5b-4c9defd835b1/128/index_files/1135050937.jpg)]

使用者將自己的"雲端儲存"服務的使用者名稱和密碼,告訴"雲沖印",(即資源伺服器的使用者名稱和密碼儲存在客戶應用伺服器上)後者就可以讀取使用者的照片了。這樣的做法有以下幾個嚴重的缺點。

(1)"雲沖印"為了後續的服務,會儲存使用者的密碼,這樣很不安全。

(2)Google不得不部署密碼登入,而我們知道,單純的密碼登入並不安全。

(3)"雲沖印"擁有了獲取使用者儲存在Google所有資料的權力,使用者沒法限制"雲沖印"獲得授權的範圍和有效期。

(4)使用者只有修改密碼,才能收回賦予"雲沖印"的權力。但是這樣做,會使得其他所有獲得使用者授權的第三方應用程式全部失效。

(5)只要有一個第三方應用程式被破解,就會導致使用者密碼洩漏,以及所有被密碼保護的資料洩漏。

總結:

將受保護的資源中的使用者名稱和密碼儲存在客戶應用的伺服器上,使用時直接使用這個使用者名稱和密碼登入

適用於同一公司內部的多個系統,不適用於不受信的第三方應用

4、方式二:通用開發者key

key是事先在"雲端儲存"服務和"雲沖印"服務間約定好的,適用於合作商或者授信的不同業務部門之間

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Yenuh50t-1612363693002)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/856a9787-25cd-4ab5-be5b-4c9defd835b1/128/index_files/1135026765.jpg)]

5、方式三:頒發令牌

需要考慮如何管理令牌、頒發令牌、吊銷令牌,需要統一的申請令牌和頒發令牌的協議

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ZIQTaRFO-1612363693003)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/856a9787-25cd-4ab5-be5b-4c9defd835b1/128/index_files/1135001875.jpg)]

令牌類比僕從鑰匙

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-S4EWjrxG-1612363693006)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/856a9787-25cd-4ab5-be5b-4c9defd835b1/128/index_files/1135180578.jpg)]

二、OAuth2簡介

1、OAuth主要角色

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-GWHsEAf9-1612363693007)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/856a9787-25cd-4ab5-be5b-4c9defd835b1/128/index_files/1134962843.jpg)]

2、最簡嚮導

川崎高彥:OAuth2領域專家,開發了一個OAuth2 sass服務,OAuth2 as Service,並且做成了一個公司

在融資的過程中為了向投資人解釋OAuth2是什麼,於是寫了一篇文章,《OAuth2最簡嚮導》

三、OAuth2的應用

1、微服務安全

現代微服務中系統微服務化以及應用的形態和裝置型別增多,不能用傳統的登入方式

核心的技術不是使用者名稱和密碼,而是token,由AuthServer頒發token,使用者使用token進行登入

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-fFnZUIFg-1612363693007)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/856a9787-25cd-4ab5-be5b-4c9defd835b1/128/index_files/1134978968.jpg)]

2、社交登入

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Oozbs3Ex-1612363693008)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/856a9787-25cd-4ab5-be5b-4c9defd835b1/128/index_files/1135225921.jpg)]

2-生成授權URL

一、準備工作

1、註冊

  • 微信開放平臺:https://open.weixin.qq.com

2、郵箱啟用

3、完善開發者資料

4、開發者資質認證

準備營業執照,1-2個工作日審批、300元

5、建立網站應用

提交稽核,7個工作日審批

6、熟悉微信登入流程

參考文件:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=e547653f995d8f402704d5cb2945177dc8aa4e7e&lang=zh_CN

獲取access_token時序圖

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-0wPOVz7B-1612363710038)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/a0800f50-1c5d-4fee-876b-549932fa5f18/128/index_files/730bd7bb80c05490a688330820419d9b.png)]

第一步:請求CODE(生成授權URL)

第二步:通過code獲取access_token(開發回撥URL)

二、後端開發

service_ucenter微服務

1、新增配置

application.yml新增相關配置資訊

wx:
  open:
    # 微信開放平臺 appid
    appId: wxed9954c01bb89b47
    # 微信開放平臺 appsecret
    appSecret: a7482517235173ddb4083788de60b90e
    # 微信開放平臺 重定向url(guli.shop需要在微信開放平臺配置)
    redirectUri: http://guli.shop/api/ucenter/wx/callback8160

2、建立常量類

建立util包,建立UcenterProperties.java類

package com.atguigu.guli.service.ucenter.util;
@Data
@Component
//注意prefix要寫到最後一個 "." 符號之前
@ConfigurationProperties(prefix="wx.open")
public class UcenterProperties {
    private String appId;
    private String appSecret;
    private String redirectUri;
}

3、建立controller

api包中建立ApiWxController

package com.atguigu.guli.service.ucenter.controller.api;
@CrossOrigin
@Controller//注意這裡沒有配置 @RestController
@RequestMapping("/api/ucenter/wx")
@Slf4j
public class ApiWxController {
    @Autowired
    private UcenterProperties ucenterProperties;
    @GetMapping("login")
    public String genQrConnect(HttpSession session){
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";
        //處理回撥url
        String redirecturi = "";
        try {
            redirecturi = URLEncoder.encode(ucenterProperties.getRedirectUri(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error(ExceptionUtils.getMessage(e));
            throw new GuliException(ResultCodeEnum.URL_ENCODE_ERROR);
        }
        //處理state:生成隨機數,存入session
        String state = UUID.randomUUID().toString();
        log.info("生成 state = " + state);
        session.setAttribute("wx_open_state", state);
        String qrcodeUrl = String.format(
                baseUrl,
                ucenterProperties.getAppId(),
                redirecturi,
                state
        );
        return "redirect:" + qrcodeUrl;
    }
 }

授權url引數說明

引數是否必須說明
appid應用唯一標識
redirect_uri請使用urlEncode對連結進行處理
response_type填code
scope應用授權作用域,擁有多個作用域用逗號(,)分隔,網頁應用目前僅填寫snsapi_login即
state用於保持請求和回撥的狀態,授權請求後原樣帶回給第三方。該引數可用於防止csrf攻擊(跨站請求偽造攻擊),建議第三方帶上該引數,可設定為簡單的隨機數加session進行校驗

4、測試

訪問:訪問以下授權url後會得到一個微信登入二維碼

http://localhost:8160/api/ucenter/wx/login

5、前端整合登入超連結

pages/login.vue和register.vue中替換微信登入的超連結

三、整合Spring Session

使用spring session實現分散式session共享,對原有程式碼無侵入,自動在redis中儲存session資訊

1、service_ucenter中新增依賴

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

2、service_ucenter中新增配置檔案

package com.atguigu.guli.service.base.config;
@Configuration
@EnableRedisHttpSession
public class HttpSessionConfig {
    
    //可選配置
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        //我們可以將Spring Session預設的Cookie Key從SESSION替換為原生的JSESSIONID
        serializer.setCookieName("JSESSIONID");
        // CookiePath設定為根路徑
        serializer.setCookiePath("/");
        // 配置了相關的正則表示式,可以達到同父域下的單點登入的效果
        serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
        return serializer;
    }
}

3-回撥方式說明

一、回撥方法定義

ApiWxController中新增方法

@GetMapping("callback")
public String callback(String code, String state){
    //回撥被拉起,並獲得code和state引數
    System.out.println("callback被呼叫");
    System.out.println("code = " + code);
    System.out.println("state = " + state);
    return null;
}

使用者點選“確認登入”後,微信伺服器會向穀粒學院的業務伺服器發起回撥,回撥地址就是yml中配置的redirecturi

二、發起回撥的方式

1、方式一:內網穿透

**步驟:**開通並啟動內網穿透ngrok–>開放平臺配置回撥地址–>yml配置

開放平臺配置:

**[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-FwnizXuu-1612363737524)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/84e6e96a-e46d-4c79-9d30-9236286da327/128/index_files/3e44c0d6-33ab-454e-b503-5f77c26a342b.png)]
**

img

yml配置:

wx:
  open:
    # 微信開放平臺 appid
    appId: wxc606fb748aedee7c
    # 微信開放平臺 appsecret
    appSecret: 073e8e1117c1054b14586c8aa922bc9c
    # 微信開放平臺 重定向url(guli.shop需要在微信開放平臺配置)
    redirectUri: http://imhelen.free.idcfengye.com/api/ucenter/wx/callback

**注意:**yml檔案中redirecturi的域名必須和開放平臺中應用配置的授權回撥域的值完全一致,

但是開放平臺上的一個應用只能配置一個回撥地址,提供給一個開發者使用

2、方式二:外網伺服器跳轉

解決多人無法共享回撥域設定的問題。

**步驟:**將跳轉程式部署到外網伺服器–>開放平臺配置回撥地址–>yml配置

跳轉程式:部署在guli.shop上

guli.shop伺服器的介面可以接收微信的回撥請求,將微信回撥請求轉發到開發者的localhost的8160埠,並傳遞code和state引數

開放平臺配置*:*

授權回撥域一般設定為一個內網穿透地址,例如使用ngrok工具申請一個內網穿透地止

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-jxXhSdua-1612363737526)(file:///C:/Users/Administrator/Documents/My Knowledge/temp/84e6e96a-e46d-4c79-9d30-9236286da327/128/index_files/f5e23048-3e5c-41b9-a41b-df641f5be589.png)]

img

yml配置*:*

wx:
  open:
    # 微信開放平臺 appid
    appId: wxed9954c01bb89b47
    # 微信開放平臺 appsecret
    appSecret: a7482517235173ddb4083788de60b90e
    # 微信開放平臺 重定向url(guli.shop需要在微信開放平臺配置)
    redirectUri: http://guli.shop/api/ucenter/wx/callback8160

三、測試回撥跳轉伺服器

訪問回撥伺服器

http://guli.shop/api/ucenter/wx/callback8160?code=1234&state=666

跳轉到

http://localhost:8160/api/ucenter/wx/callback?code=1234&state=666

4-開發回撥URL

一、準備

1、httpclient工具類

放入common_util專案的util包

HttpClientUtils.java

2、pom依賴

common_util專案中加入依賴

<!--httpclient-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

二、獲取access_token

在WxApiController.java中新增如下方法

@GetMapping("callback")
public String callback(String code, String state, HttpSession session){
    //回撥被拉起,並獲得code和state引數
    log.info("callback被呼叫");
    log.info("code = " + code);
    log.info("state = " + state);
    if(StringUtils.isEmpty(code) || StringUtils.isEmpty(state) ){
        log.error("非法回撥請求");
        throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
    }
    String sessionState = (String)session.getAttribute("wx_open_state");
    if(!state.equals(sessionState)){
        log.error("非法回撥請求");
        throw new GuliException(ResultCodeEnum.ILLEGAL_CALLBACK_REQUEST_ERROR);
    }
    //攜帶授權臨時票據code,和appid以及appsecret請求access_token
    String accessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";
    Map<String, String> accessTokenParam = new HashMap();
    accessTokenParam.put("appid", ucenterProperties.getAppId());
    accessTokenParam.put("secret", ucenterProperties.getAppSecret());
    accessTokenParam.put("code", code);
    accessTokenParam.put("grant_type", "authorization_code");
    HttpClientUtils client = new HttpClientUtils(accessTokenUrl, accessTokenParam);
    String result = "";
    try {
        //傳送請求
        client.get();
        result = client.getContent();
        System.out.println("result = " + result);
    } catch (Exception e) {
        log.error("獲取access_token失敗");
        throw new GuliException(ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD);
    }
    Gson gson = new Gson();
    HashMap<String, Object> resultMap = gson.fromJson(result, HashMap.class);
    //判斷微信獲取access_token失敗的響應
    Object errcodeObj = resultMap.get("errcode");
    if(errcodeObj != null){
        String errmsg = (String)resultMap.get("errmsg");
        Double errcode = (Double)errcodeObj;
        log.error("獲取access_token失敗 - " + "message: " + errmsg + ", errcode: " + errcode);
        throw new GuliException(ResultCodeEnum.FETCH_ACCESSTOKEN_FAILD);
    }
    //微信獲取access_token響應成功
    String accessToken = (String)resultMap.get("access_token");
    String openid = (String)resultMap.get("openid");
    log.info("accessToken = " + accessToken);
    log.info("openid = " + openid);
    //根據access_token獲取微信使用者的基本資訊
    // TODO
    return null;
}

三、獲取使用者資訊

1、根據openid查詢使用者是否已註冊

業務介面:MemberService.java

/**
     * 根據openid返回使用者資訊
     * @param openid
     * @return
     */
Member getByOpenid(String openid);

業務實現:MemberServiceImpl.java

@Override
public Member getByOpenid(String openid) {
    QueryWrapper<Member> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("openid", openid);
    return baseMapper.selectOne(queryWrapper);
}

2、根據access_token獲取使用者資訊

@Autowired
private MemberService memberService;
@GetMapping("callback")
public String callback(String code, String state, HttpSession session){
    ............
       //根據access_token獲取微信使用者的基本資訊
        //根據openid查詢當前使用者是否已經使用微信登入過該系統
        Member member = memberService.getByOpenid(openid);
        if(member == null){
            //向微信的資源伺服器發起請求,獲取當前使用者的使用者資訊
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo";
            Map<String, String> baseUserInfoParam = new HashMap();
            baseUserInfoParam.put("access_token", accessToken);
            baseUserInfoParam.put("openid", openid);
            client = new HttpClientUtils(baseUserInfoUrl, baseUserInfoParam);
            String resultUserInfo = null;
            try {
                client.get();
                resultUserInfo = client.getContent();
            } catch (Exception e) {
                log.error(ExceptionUtils.getMessage(e));
                throw new GuliException(ResultCodeEnum.FETCH_USERINFO_ERROR);
            }
            HashMap<String, Object> resultUserInfoMap = gson.fromJson(resultUserInfo, HashMap.class);
            if(resultUserInfoMap.get("errcode") != null){
                log.error("獲取使用者資訊失敗" + ",message:" + resultMap.get("errmsg"));
                throw new GuliException(ResultCodeEnum.FETCH_USERINFO_ERROR);
            }
            String nickname = (String)resultUserInfoMap.get("nickname");
            String headimgurl = (String)resultUserInfoMap.get("headimgurl");
            Double sex = (Double)resultUserInfoMap.get("sex");
            //使用者註冊
            member = new Member();
            member.setOpenid(openid);
            member.setNickname(nickname);
            member.setAvatar(headimgurl);
            member.setSex(sex.intValue());
            memberService.save(member);
        }
        JwtInfo jwtInfo = new JwtInfo();
        jwtInfo.setId(member.getId());
        jwtInfo.setNickname(member.getNickname());
        jwtInfo.setAvatar(member.getAvatar());
        String jwtToken = JwtUtils.getJwtToken(jwtInfo, 1800);
        //攜帶token跳轉
        return "redirect:http://localhost:3000?token=" + jwtToken;
}

四、前端整合

components/AppHeader.vue

mounted() {
    // 微信登入url token獲取
    this.token = this.$route.query.token
    if (this.token) {
        // 將token存在cookie中
        cookie.set('guli_jwt_token', this.token, { domain: 'localhost' })
        // 跳轉頁面:擦除url中的token
        // 注意:window物件在created方法中無法被訪問,因此要寫在mounted中
        window.location.href = '/'
    }
},