关于java:微信扫码登录很难吗5步帮你搞定

0次阅读

共计 10311 个字符,预计需要花费 26 分钟才能阅读完成。

微信开放平台:微信扫码登录性能

官网文档:https://developers.weixin.qq….\_App/WeChat\_Login/Wechat\_Login.html

1. 受权流程阐明

微信 OAuth2.0 受权登录让微信用户应用微信身份平安登录第三方利用或网站,在微信用户受权登录已接入微信 OAuth2.0 的第三方利用后,第三方能够获取到用户的接口调用凭证(access\_token),通过 access\_token 能够进行微信开放平台受权关系接口调用,从而可实现获取微信用户根本凋谢信息和帮忙用户实现根底凋谢性能等。

微信 OAuth2.0 受权登录目前反对 authorization\_code 模式,实用于领有 server 端的利用受权。该模式整体流程为:

① 第三方发动微信受权登录申请,微信用户容许受权第三方利用后,微信会拉起利用或重定向到第三方网站,并且带上受权长期票据 code 参数;

② 通过 code 参数加上 AppID 和 AppSecret 等,通过 API 换取 access\_token;

③ 通过 access\_token 进行接口调用,获取用户根本数据资源或帮忙用户实现基本操作。

第一步:申请 CODE

第三方应用网站利用受权登录前请留神已获取相应网页受权作用域(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

返回阐明

用户容许受权后,将会重定向到 redirect\_uri 的网址上,并且带上 code 和 state 参数

redirect_uri?code=CODE&state=STATE

若用户禁止受权,则重定向后不会带上 code 参数,仅会带上 state 参数

redirect_uri?state=STATE

例如:登录一号店网站利用 https://passport.yhd.com/wechat/login.do 关上后,一号店会生成 state 参数,跳转到 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 微信用户应用微信扫描二维码并且确认登录后,PC 端会跳转到 https://passport.yhd.com/wechat/callback.do?code=CODE&state=3d6be0a4035d839573b04816624a415e

第二步:通过 code 获取 access\_token

通过 code 获取 access\_token

https://api.weixin.qq.com/sns…\_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"
}

谬误返回样例:

{"errcode":40029,"errmsg":"invalid code"}
  • Appsecret 是利用接口应用密钥,透露后将可能导致利用数据透露、利用的用户数据透露等高风险结果;存储在客户端,极有可能被歹意窃取(如反编译获取 Appsecret);
  • access\_token 为用户受权第三方利用发动接口调用的凭证(相当于用户登录态),存储在客户端,可能呈现歹意获取 access\_token 后导致的用户数据透露、用户微信相干接口性能被歹意发动等行为;
  • refresh\_token 为用户受权第三方利用的长效凭证,仅用于刷新 access\_token,但透露后相当于 access\_token 透露,危险同上。

倡议将 secret、用户数据(如 access\_token)放在 App 云端服务器,由云端直达接口调用申请。

第三步:通过 access\_token 调用接口

获取 access\_token 后,进行接口调用,有以下前提:

  1. access\_token 无效且未超时;
  2. 微信用户已受权给第三方利用帐号相应接口作用域(scope)。

对于接口作用域(scope),能调用的接口有以下:

2. 受权流程代码

因为微信开放平台的 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 就是用户受权批准后回调的地址
        String returnUrl = "http://heng.nat300.top/sell/wechat/qrUserInfo";

        // 疏导用户拜访这个链接,进行受权
        String url = wxOpenService.buildQrConnectUrl(returnUrl, WxConsts.QRCONNECT_SCOPE_SNSAPI_LOGIN, URLEncoder.encode(returnUrl));
        return "redirect:" + url;
    }

    // 用户受权批准后回调的地址,从申请参数中获取 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("【微信网页受权】{}", e);
            throw new SellException(ResultEnum.WECHAT_MP_ERROR.getCode(), e.getError().getErrorMsg());
        }
        // 从 token 中获取 openid
        String openId = wxMpOAuth2AccessToken.getOpenId();

        // 这个地址可有可无,反正只是为了拿到 openid,然而如果没有会报 404 谬误,为了难看轻易返回一个百度的地址
        String  returnUrl = "http://www.baidu.com";

        log.info("openid={}", openId);

        return "redirect:" + returnUrl + "?openid="+openId;
    }
}

申请门路:在浏览器关上

https://open.weixin.qq.com/co…\_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<String, Object> map) {

        //1. openid 去和数据库里的数据匹配
        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. 设置 token 至 redis
        String token = UUID.randomUUID().toString();
        // 设置 token 的过期工夫
        Integer expire = RedisConstant.EXPIRE;

        redisTemplate.opsForValue().set(String.format(RedisConstant.TOKEN_PREFIX, token), openid, expire, TimeUnit.SECONDS);

        //3. 设置 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<String, Object> 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 存入数据库

② 将受权后跳转的地址改为登录地址

 // 用户受权批准后回调的地址,从申请参数中获取 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("【微信网页受权】{}", e);
            throw new SellException(ResultEnum.WECHAT_MP_ERROR.getCode(), e.getError().getErrorMsg());
        }
        // 从 token 中获取 openid
        String openId = wxMpOAuth2AccessToken.getOpenId();

        // 受权胜利后跳转到卖家零碎的登录地址
        String  returnUrl = "http://heng.nat300.top/sell/seller/login";

        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

第三利用申请应用微信扫码登录,而不是应用本网站的明码:

用户批准受权后登入第三方利用的后盾管理系统:

4. Spring AOP 校验用户有没有登录

@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 阐明曾经登出或者基本没有登录
        if (cookie == null) {log.warn("【登录校验】Cookie 中查不到 token");
            // 校验不通过,抛出异样
            throw new SellerAuthorizeException();}

        // 去 redis 里查问
        String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
        // 如果 redis 中没有对应的 openid,同样示意登出或者基本没有登录
        if (StringUtils.isEmpty(tokenValue)) {log.warn("【登录校验】Redis 中查不到 token");
            throw new SellerAuthorizeException();}
    }
}

5. 拦挡登录校验不通过抛出的异样

拦挡及登录校验不通过的异样,让其跳转到登录页面,扫码登录

@ControllerAdvice
public class SellExceptionHandler {
    // 拦挡登录异样
    @ExceptionHandler(value = SellerAuthorizeException.class)
    public ModelAndView handlerAuthorizeException() {
        // 拦挡异样后,跳转到登录界面
        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() {}
}
正文完
 0