網頁實現微信登入
技術標籤:tool
目錄
- 01-OAuth2
- 一、OAuth2解決什麼問題
- 二、OAuth2簡介
- 三、OAuth2的應用
- 2-生成授權URL
- **一、準備工作**
- 二、後端開發
- 三、整合Spring Session
- 3-回撥方式說明
- 一、回撥方法定義
- 二、發起回撥的方式
- 三、測試回撥跳轉伺服器
- 4-開發回撥URL
- 一、準備
- 二、獲取**access_token**
- 三、獲取使用者資訊
- 四、前端整合
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)]
**
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)]
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 = '/'
}
},