微信掃碼登錄很難嗎?5步幫你搞定
微信開放平臺(tái):微信掃碼登錄功能
官方文檔:https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
1. 授權(quán)流程說明
微信OAuth2.0授權(quán)登錄讓微信用戶使用微信身份安全登錄第三方應(yīng)用或網(wǎng)站,在微信用戶授權(quán)登錄已接入微信OAuth2.0的第三方應(yīng)用后,第三方可以獲取到用戶的接口調(diào)用憑證(access_token),通過access_token可以進(jìn)行微信開放平臺(tái)授權(quán)關(guān)系接口調(diào)用,從而可實(shí)現(xiàn)獲取微信用戶基本開放信息和幫助用戶實(shí)現(xiàn)基礎(chǔ)開放功能等。
微信OAuth2.0授權(quán)登錄目前支持authorization_code模式,適用于擁有server端的應(yīng)用授權(quán)。該模式整體流程為:
① 第三方發(fā)起微信授權(quán)登錄請求,微信用戶允許授權(quán)第三方應(yīng)用后,微信會(huì)拉起應(yīng)用或重定向到第三方網(wǎng)站,并且?guī)鲜跈?quán)臨時(shí)票據(jù)code參數(shù);
② 通過code參數(shù)加上AppID和AppSecret等,通過API換取access_token;
③ 通過access_token進(jìn)行接口調(diào)用,獲取用戶基本數(shù)據(jù)資源或幫助用戶實(shí)現(xiàn)基本操作。

第一步:請求CODE
第三方使用網(wǎng)站應(yīng)用授權(quán)登錄前請注意已獲取相應(yīng)網(wǎng)頁授權(quán)作用域(scope=snsapi_login),則可以通過在PC端打開以下鏈接:https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
返回說明
用戶允許授權(quán)后,將會(huì)重定向到redirect_uri的網(wǎng)址上,并且?guī)蟘ode和state參數(shù)
redirect_uri?code=CODE&state=STATE
若用戶禁止授權(quán),則重定向后不會(huì)帶上code參數(shù),僅會(huì)帶上state參數(shù)
redirect_uri?state=STATE
例如:登錄一號店網(wǎng)站應(yīng)用?https://passport.yhd.com/wechat/login.do 打開后,一號店會(huì)生成state參數(shù),跳轉(zhuǎn)到 https://open.weixin.qq.com/connect/qrconnect?appid=wxbdc5610cc59c1631&redirect_uri=https%3A%2F%2Fpassport.yhd.com%2Fwechat%2Fcallback.do&response_type=code&scope=snsapi_login&state=3d6be0a4035d839573b04816624a415e#wechat_redirect?微信用戶使用微信掃描二維碼并且確認(rèn)登錄后,PC端會(huì)跳轉(zhuǎn)到?https://passport.yhd.com/wechat/callback.do?code=CODE&state=3d6be0a4035d839573b04816624a415e
第二步:通過code獲取access_token
通過code獲取access_token
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
返回說明
正確的返回:
{?
"access_token":"ACCESS_TOKEN",?
"expires_in":7200,?
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",?
"scope":"SCOPE",
"unionid":?"o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
錯(cuò)誤返回樣例:
{"errcode":40029,"errmsg":"invalid?code"}
Appsecret 是應(yīng)用接口使用密鑰,泄漏后將可能導(dǎo)致應(yīng)用數(shù)據(jù)泄漏、應(yīng)用的用戶數(shù)據(jù)泄漏等高風(fēng)險(xiǎn)后果;存儲(chǔ)在客戶端,極有可能被惡意竊?。ㄈ绶淳幾g獲取Appsecret);
access_token 為用戶授權(quán)第三方應(yīng)用發(fā)起接口調(diào)用的憑證(相當(dāng)于用戶登錄態(tài)),存儲(chǔ)在客戶端,可能出現(xiàn)惡意獲取access_token 后導(dǎo)致的用戶數(shù)據(jù)泄漏、用戶微信相關(guān)接口功能被惡意發(fā)起等行為;
refresh_token 為用戶授權(quán)第三方應(yīng)用的長效憑證,僅用于刷新access_token,但泄漏后相當(dāng)于access_token 泄漏,風(fēng)險(xiǎn)同上。
建議將secret、用戶數(shù)據(jù)(如access_token)放在App云端服務(wù)器,由云端中轉(zhuǎn)接口調(diào)用請求。
第三步:通過access_token調(diào)用接口
獲取access_token后,進(jìn)行接口調(diào)用,有以下前提:
access_token有效且未超時(shí);
微信用戶已授權(quán)給第三方應(yīng)用帳號相應(yīng)接口作用域(scope)。
對于接口作用域(scope),能調(diào)用的接口有以下:

2. 授權(quán)流程代碼
因?yàn)槲⑿砰_放平臺(tái)的AppiD和APPSecret和微信公眾平臺(tái)的AppiD和AppSecret都是不同的,因此需要配置一下:
#?開放平臺(tái)
wechat.open-app-id=wx6ad144e54af67d87
wechat.open-app-secret=91a2ff6d38a2bbccfb7e9f9079108e2e
@Data
@Component
@ConfigurationProperties(prefix?=?"wechat")
public?class?WechatAccountConfig?{
????//公眾號appid
????private?String?mpAppId;
????//公眾號appSecret
????private?String?mpAppSecret;
????//商戶號
????private?String?mchId;
????//商戶秘鑰
????private?String?mchKey;
????
????//商戶證書路徑
????private?String?keyPath;
????//微信支付異步通知
????private?String?notifyUrl;
????//開放平臺(tái)id
????private?String?openAppId;
????//開放平臺(tái)秘鑰
????private?String?openAppSecret;
}
@Configuration
public?class?WechatOpenConfig?{
????@Autowired
????private?WechatAccountConfig?accountConfig;
????@Bean
????public?WxMpService?wxOpenService()?{
????????WxMpService?wxOpenService?=?new?WxMpServiceImpl();
????????wxOpenService.setWxMpConfigStorage(wxOpenConfigStorage());
????????return?wxOpenService;
????}
????@Bean
????public?WxMpConfigStorage?wxOpenConfigStorage()?{
????????WxMpInMemoryConfigStorage?wxMpInMemoryConfigStorage?=?new?WxMpInMemoryConfigStorage();
????????wxMpInMemoryConfigStorage.setAppId(accountConfig.getOpenAppId());
????????wxMpInMemoryConfigStorage.setSecret(accountConfig.getOpenAppSecret());
????????return?wxMpInMemoryConfigStorage;
????}
}
@Controller
@RequestMapping("/wechat")
@Slf4j
public?class?WeChatController?{
????@Autowired
????private?WxMpService?wxMpService;
????@Autowired
????private?WxMpService?wxOpenService;
????@GetMapping("/qrAuthorize")
????public?String?qrAuthorize()?{
????????//returnUrl就是用戶授權(quán)同意后回調(diào)的地址
????????String?returnUrl?=?"http://heng.nat300.top/sell/wechat/qrUserInfo";
????????//引導(dǎo)用戶訪問這個(gè)鏈接,進(jìn)行授權(quán)
????????String?url?=?wxOpenService.buildQrConnectUrl(returnUrl,?WxConsts.QRCONNECT_SCOPE_SNSAPI_LOGIN,?URLEncoder.encode(returnUrl));
????????return?"redirect:"?+?url;
????}
????//用戶授權(quán)同意后回調(diào)的地址,從請求參數(shù)中獲取code
????@GetMapping("/qrUserInfo")
????public?String?qrUserInfo(@RequestParam("code")?String?code)?{
????????WxMpOAuth2AccessToken?wxMpOAuth2AccessToken?=?new?WxMpOAuth2AccessToken();
????????try?{
????????????//通過code獲取access_token
????????????wxMpOAuth2AccessToken?=?wxOpenService.oauth2getAccessToken(code);
????????}?catch?(WxErrorException?e)?{
????????????log.error("【微信網(wǎng)頁授權(quán)】{}",?e);
????????????throw?new?SellException(ResultEnum.WECHAT_MP_ERROR.getCode(),?e.getError().getErrorMsg());
????????}
????????//從token中獲取openid
????????String?openId?=?wxMpOAuth2AccessToken.getOpenId();
????????//這個(gè)地址可有可無,反正只是為了拿到openid,但是如果沒有會(huì)報(bào)404錯(cuò)誤,為了好看隨便返回一個(gè)百度的地址
????????String??returnUrl?=?"http://www.baidu.com";
????????log.info("openid={}",?openId);
????????return?"redirect:"?+?returnUrl?+?"?openid="+openId;
????}
}
請求路徑:在瀏覽器打開
https://open.weixin.qq.com/connect/qrconnect?appid=wx6ad144e54af67d87&redirect_uri=http%3A%2F%2Fsell.springboot.cn%2Fsell%2Fqr%2FoTgZpwenC6lwO2eTDDf_-UYyFtqI&response_type=code&scope=snsapi_login&state=http%3a%2f%2fheng.nat300.top%2fsell%2fwechat%2fqrUserInfo
獲取了openid:openid=o9AREv7Xr22ZUk6BtVqw82bb6AFk

3. 用戶登錄和登出
@Controller
@RequestMapping("/seller")
public?class?SellerUserController?{
????@Autowired
????private?SellerService?sellerService;
????@Autowired
????private?StringRedisTemplate?redisTemplate;
????@Autowired
????private?ProjectUrlConfig?projectUrlConfig;
????@GetMapping("/login")
????public?ModelAndView?login(@RequestParam("openid")?String?openid, ??????????????????????????????HttpServletResponse?response, ??????????????????????????????Map?map) ?{
????????//1.?openid去和數(shù)據(jù)庫里的數(shù)據(jù)匹配
????????SellerInfo?sellerInfo?=?sellerService.findSellerInfoByOpenid(openid);
????????if?(sellerInfo?==?null)?{
????????????map.put("msg",?ResultEnum.LOGIN_FAIL.getMessage());
????????????map.put("url",?"/sell/seller/order/list");
????????????return?new?ModelAndView("common/error");
????????}
????????//2.?設(shè)置token至redis
????????String?token?=?UUID.randomUUID().toString();
????????//設(shè)置token的過期時(shí)間
????????Integer?expire?=?RedisConstant.EXPIRE;
????????redisTemplate.opsForValue().set(String.format(RedisConstant.TOKEN_PREFIX,?token),?openid,?expire,?TimeUnit.SECONDS);
????????//3.?設(shè)置token至cookie
????????CookieUtil.set(response,?CookieConstant.TOKEN,?token,?expire);
????????return?new?ModelAndView("redirect:"?+?"http://heng.nat300.top/sell/seller/order/list");
????}
????@GetMapping("/logout")
????public?ModelAndView?logout(HttpServletRequest?request, ???????????????????????HttpServletResponse?response, ???????????????????????Map?map) ?{
????????//1.?從cookie里查詢
????????Cookie?cookie?=?CookieUtil.get(request,?CookieConstant.TOKEN);
????????if?(cookie?!=?null)?{
????????????//2.?清除redis
????????????redisTemplate.opsForValue().getOperations().delete(String.format(RedisConstant.TOKEN_PREFIX,?cookie.getValue()));
????????????//3.?清除cookie
????????????CookieUtil.set(response,?CookieConstant.TOKEN,?null,?0);
????????}
????????map.put("msg",?ResultEnum.LOGOUT_SUCCESS.getMessage());
????????map.put("url",?"/sell/seller/order/list");
????????return?new?ModelAndView("common/success",?map);
????}
}
① 將上一步獲取到的openid存入數(shù)據(jù)庫

② 將授權(quán)后跳轉(zhuǎn)的地址改為登錄地址
?//用戶授權(quán)同意后回調(diào)的地址,從請求參數(shù)中獲取code
????@GetMapping("/qrUserInfo")
????public?String?qrUserInfo(@RequestParam("code")?String?code)?{
????????WxMpOAuth2AccessToken?wxMpOAuth2AccessToken?=?new?WxMpOAuth2AccessToken();
????????try?{
????????????//通過code獲取access_token
????????????wxMpOAuth2AccessToken?=?wxOpenService.oauth2getAccessToken(code);
????????}?catch?(WxErrorException?e)?{
????????????log.error("【微信網(wǎng)頁授權(quán)】{}",?e);
????????????throw?new?SellException(ResultEnum.WECHAT_MP_ERROR.getCode(),?e.getError().getErrorMsg());
????????}
????????//從token中獲取openid
????????String?openId?=?wxMpOAuth2AccessToken.getOpenId();
????????//授權(quán)成功后跳轉(zhuǎn)到賣家系統(tǒng)的登錄地址
????????String??returnUrl?=?"http://heng.nat300.top/sell/seller/login";
????????log.info("openid={}",?openId);
????????return?"redirect:"?+?returnUrl?+?"?openid="+openId;
????}
③ 在瀏覽器請求這個(gè)鏈接:https://open.weixin.qq.com/connect/qrconnect?appid=wx6ad144e54af67d87&redirect_uri=http%3A%2F%2Fsell.springboot.cn%2Fsell%2Fqr%2FoTgZpwenC6lwO2eTDDf_-UYyFtqI&response_type=code&scope=snsapi_login&state=http%3a%2f%2fheng.nat300.top%2fsell%2fwechat%2fqrUserInfo
第三應(yīng)用請求使用微信掃碼登錄,而不是使用本網(wǎng)站的密碼:
用戶同意授權(quán)后登入第三方應(yīng)用的后臺(tái)管理系統(tǒng):
4. Spring AOP校驗(yàn)用戶有沒有登錄
@Aspect
@Component
@Slf4j
public?class?SellerAuthorizeAspect?{
????@Autowired
????private?StringRedisTemplate?redisTemplate;
????@Pointcut("execution(public?*?com.hh.controller.Seller*.*(..))"?+
????"&&?!execution(public?*?com.hh.controller.SellerUserController.*(..))")
????public?void?verify()?{}
????@Before("verify()")
????public?void?doVerify()?{
????????
????????ServletRequestAttributes?attributes?=?(ServletRequestAttributes)?RequestContextHolder.getRequestAttributes();
????????HttpServletRequest?request?=?attributes.getRequest();
????????//查詢cookie
????????Cookie?cookie?=?CookieUtil.get(request,?CookieConstant.TOKEN);
????????//如果cookie中沒有token說明已經(jīng)登出或者根本沒有登錄
????????if?(cookie?==?null)?{
????????????log.warn("【登錄校驗(yàn)】Cookie中查不到token");
????????????//校驗(yàn)不通過,拋出異常
????????????throw?new?SellerAuthorizeException();
????????}
????????//去redis里查詢
????????String?tokenValue?=?redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX,?cookie.getValue()));
????????//如果redis中沒有對應(yīng)的openid,同樣表示登出或者根本沒有登錄
????????if?(StringUtils.isEmpty(tokenValue))?{
????????????log.warn("【登錄校驗(yàn)】Redis中查不到token");
????????????throw?new?SellerAuthorizeException();
????????}
????}
}
5. 攔截登錄校驗(yàn)不通過拋出的異常
攔截及登錄校驗(yàn)不通過的異常,讓其跳轉(zhuǎn)到登錄頁面,掃碼登錄
@ControllerAdvice
public?class?SellExceptionHandler?{
????//攔截登錄異常
????@ExceptionHandler(value?=?SellerAuthorizeException.class) ????public?ModelAndView?handlerAuthorizeException()?{
????????//攔截異常后,跳轉(zhuǎn)到登錄界面
????????return?new?ModelAndView("redirect:".concat("https://open.weixin.qq.com/connect/qrconnect?"?+
????????????????"appid=wx6ad144e54af67d87"?+
????????????????"&redirect_uri=http%3A%2F%2Fsell.springboot.cn%2Fsell%2Fqr%2F"?+
????????????????"oTgZpwenC6lwO2eTDDf_-UYyFtqI"?+
????????????????"&response_type=code&scope=snsapi_login"?+
????????????????"&state=http%3a%2f%2fheng.nat300.top%2fsell%2fwechat%2fqrUserInfo"));
????}
????@ExceptionHandler(value?=?SellException.class) ????@ResponseBody ????public?ResultVO?handlerSellerException(SellException?e)?{
????????return?ResultVOUtil.error(e.getCode(),?e.getMessage());
????}
????@ExceptionHandler(value?=?ResponseBankException.class) ????@ResponseStatus(HttpStatus.FORBIDDEN) ????public?void?handleResponseBankException()?{
????}
}
來源:hengheng.blog.csdn.net/article/details/107823201 ###
-End-
?關(guān)注公眾號:Java后端編程,回復(fù)下面關(guān)鍵字?
要Java學(xué)習(xí)完整路線,回復(fù)??路線?
缺Java入門視頻,回復(fù):?視頻?
要Java面試經(jīng)驗(yàn),回復(fù)??面試?
缺Java項(xiàng)目,回復(fù):?項(xiàng)目?
進(jìn)Java粉絲群:?加群?
PS:如果覺得我的分享不錯(cuò),歡迎大家隨手點(diǎn)贊、在看。
(完) 加我"微信"?獲取一份 最新Java面試題資料 請備注:666,不然不通過~
最近好文
1、再見了,收費(fèi)的XShell,我改用國產(chǎn)良心工具!
3、SpringBoot快速開發(fā)利器:Spring Boot CLI
最近面試BAT,整理一份面試資料《Java面試BAT通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。 獲取方式:關(guān)注公眾號并回復(fù)?java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。 明天見(??ω??)??


