<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Boot + OAuth2.0 實(shí)現(xiàn)微信掃碼登錄,這才叫優(yōu)雅!!

          共 10895字,需瀏覽 22分鐘

           ·

          2022-05-17 00:31

          今日推薦
          答應(yīng)我, 不要再用 if (obj != null) 判空了
          20個(gè)示例!詳解 Java8 Stream 用法,從此告別shi山(垃圾代碼)
          利用Java8新特征,重構(gòu)傳統(tǒng)設(shè)計(jì)模式,你學(xué)會了嗎?
          竟然有一半的人不知道 for 與 foreach 的區(qū)別???
          利用多線程批量拆分 List 導(dǎo)入數(shù)據(jù)庫,效率杠杠的!

          微信開放平臺:微信掃碼登錄功能

          官方文檔: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)行微信開放平臺授權(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)用后,微信會拉起應(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)后,將會重定向到redirect_uri的網(wǎng)址上,并且?guī)蟘ode和state參數(shù)

          redirect_uri?code=CODE&state=STATE

          若用戶禁止授權(quán),則重定向后不會帶上code參數(shù),僅會帶上state參數(shù)

          redirect_uri?state=STATE

          例如:登錄一號店網(wǎng)站應(yīng)用 https://passport.yhd.com/wechat/login.do 打開后,一號店會生成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端會跳轉(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)后果;存儲在客戶端,極有可能被惡意竊?。ㄈ绶淳幾g獲取Appsecret);
          • access_token 為用戶授權(quán)第三方應(yīng)用發(fā)起接口調(diào)用的憑證(相當(dāng)于用戶登錄態(tài)),存儲在客戶端,可能出現(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)用,有以下前提:

          1. access_token有效且未超時(shí);
          2. 微信用戶已授權(quán)給第三方應(yīng)用帳號相應(yīng)接口作用域(scope)。

          對于接口作用域(scope),能調(diào)用的接口有以下:

          2. 授權(quán)流程代碼

          因?yàn)槲⑿砰_放平臺的AppiD和APPSecret和微信公眾平臺的AppiD和AppSecret都是不同的,因此需要配置一下:

          #?開放平臺
          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;

          ????//開放平臺id
          ????private?String?openAppId;

          ????//開放平臺秘鑰
          ????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,但是如果沒有會報(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);
          ????}
          }


          推薦一個(gè) Spring Boot 基礎(chǔ)教程及實(shí)戰(zhàn)示例:

          https://github.com/javastacks/spring-boot-best-practice

          ① 將上一步獲取到的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;
          ????}

          最新面試題整理好了,大家可以在Java面試庫小程序在線刷題。

          ③ 在瀏覽器請求這個(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ǒ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()?{
          ????}
          }

          原文鏈接:

          https://blog.csdn.net/qq_42764468/article/details/107823201

          版權(quán)聲明:本文為CSDN博主「小小茶花女」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。






          最后,再給大家推薦一個(gè)GitHub項(xiàng)目,該項(xiàng)目整理了上千本常用技術(shù)PDF,技術(shù)書籍都可以在這里找到。

          GitHub地址:https://github.com/hello-go-maker/cs-books

          電子書已經(jīng)更新好了,拿走不謝,記得點(diǎn)一個(gè)star,持續(xù)更新中...


          瀏覽 64
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  毛片免费网址 | 欧美高清无码 在线观看 | 色色色干| 成人精品999 | 欧美性网址|