OAuth2.0开放授权实现微信扫码登录第三方平台

2023/12/1

# OAuth2.0开放授权实现微信扫码登录第三方平台

# 开发前准备

微信公众平台 (qq.com) (opens new window)

需要注册微信测试号

  1. 测试号信息是自动生成的,注意后面需要替换WechatUtil里面的APPID和SECRET
  2. 接口配置信息,URL需要搞一个内网穿透,打通你的电脑和微信之间的网络,这个很简单,下载:https://www.cpolar.com/这个应用,里面配置把你的本地服务端口给代理出去,URL可能用https会有问题,换http的试试,https-443,http80,你的服务是80,所以可能会有问题。
  3. JS接口安全域名:这个可以不弄
  4. 找到网页账号,点击编辑,配置OAuth2授权域名

# 相关依赖

hutool和google.zxing在生成Qrcode要用到

其他都是一些常见的依赖

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
		
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.5</version>
        </dependency>
        
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.7</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.32</version>
        </dependency>
    </dependencies>

# 实体类

# 获取access_token返回参数实体类

@Data
public class TokenInfo {

    private String access_token;
    private int expires_in;
    private String refresh_token;
    private String openid;
    private String scope;
}

# 用户信息实体类

@Data
public class WeChatUserInfo {
    private String openid;
    private String nickname;
    private int sex;
    private String language;
    private String city;
    private String province;
    private String country;
    private String headimgurl;
    private List<String> privilege;
}

# Controller控制器

package com.lcc.wechatdemo.controller;

import cn.hutool.extra.qrcode.QrCodeUtil;
import com.lcc.wechatdemo.model.entity.WeChatUserInfo;
import com.lcc.wechatdemo.util.WechatUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.net.URLEncoder;


/**
 * @author: lichengcan
 * @date: 2023-12-01 11:04
 * @description 微信登录练习demo
 **/
@RestController
@RequestMapping()
@Slf4j
public class WeChatController {

    @Value("${wx.APPID}")
    String APPID;

    @Value("${wx.REDIRECTURL}")
    String REDIRECTURL;

    @Autowired
    RestTemplate restTemplate;
    @Autowired
    WechatUtil wechatUtil;

    @GetMapping("/wxCheck")
    public String wxSignatureCheck(@RequestParam(value = "signature") String signature,
                                   @RequestParam(value = "timestamp") String timestamp,
                                   @RequestParam(value = "nonce") String nonce,
                                   @RequestParam(value = "echostr") String echostr) {
        //这里需要检验signature,确认是来自微信服务器
        //1)将token、timestamp、nonce三个参数进行字典序排序
        //2)将三个参数字符串拼接成一个字符串进行sha1加密
        //3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
        //TODO 省略校验
        log.info("收到微信校验请求,echostr:{}" + echostr);
        return echostr;
    }

    /**
     * 微信登录,生成登录二维码
     * 如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
     *
     * @param response
     * @throws Exception
     */
    @GetMapping("/wxLogin")
    @ResponseBody
    public void wxLogin(HttpServletResponse response) throws Exception {
        //redirectUrl是回调地址,需要转成UrlEncode格式
        String redirectUrl = URLEncoder.encode(REDIRECTURL+"wxCallBack", "UTF-8");
        //构造生成二维码链接地址
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+APPID+"&redirect_uri="
                + redirectUrl + "&response_type=code&scope=snsapi_userinfo#wechat_redirect";
        //构造生成二维码,然后跳转上面的地址
        response.setContentType("image/png");
        QrCodeUtil.generate(url, 300, 300, "jpg", response.getOutputStream());
    }



    /**
     * 通过code换取网页授权access_token
     * 如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
     * code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
     *
     * @param code
     * @param state
     * @param request
     * @param response
     * @param session
     * @return
     */
    @RequestMapping(value = "/wxCallBack")
    @ResponseBody
    public WeChatUserInfo wxCallBack(String code,
                                     String state,
                                     HttpServletRequest request,
                                     HttpServletResponse response,
                                     HttpSession session) {
        WeChatUserInfo accessToken = wechatUtil.getUserInfo(code);
        //TODO 缓存用户信息,构造session
        return accessToken;
    }


}

# RestTemplate配置类

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

# 工具类

二维码生成工具类

public class QrCodeTest {


    /**
     * L、M、Q、H  由低到高。
     * 低级别的像素块更大,可以远距离识别,但是遮挡就会造成无法识别。
     * 高级别则相反,像素块小,允许遮挡一定范围,但是像素块更密集。
     * @param args
     */
    public static void main(String[] args) {
        QrConfig config = new QrConfig();
        //设置容错级别
        config.setErrorCorrection(ErrorCorrectionLevel.H);
        QrCodeUtil.generate("https://hutool.cn/",300,300, FileUtil.file("d:/qrcode.jpg"));
    }

}

微信接口工具类

@Component
public class WechatUtil {

    @Value("${wx.APPID}")
    String APPID;
    @Value("${wx.SECRET}")
    String SECRET;

    public WeChatUserInfo getUserInfo(String code) {
        //通过code换取网页授权access_token
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" +
                APPID + "&secret=" + SECRET + "&code=" + code + "&grant_type=authorization_code";
        RestTemplate restTemplate = new RestTemplate();
        //1.获取token
        String accessToken = restTemplate.getForObject(url, String.class);
        TokenInfo tokenInfo = JSON.parseObject(accessToken, TokenInfo.class);
        //2.获取用户信息
        String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=" + tokenInfo.getAccess_token() + "&openid=" + tokenInfo.getOpenid() + "&lang=zh_CN";
        String userInfo = restTemplate.getForObject(userInfoUrl, String.class);
        WeChatUserInfo weChatUserInfo = JSON.parseObject(userInfo, WeChatUserInfo.class);
        return weChatUserInfo;
    }
}

# 配置文件yml

wx:
  # 换成你自己的微信 ID、SECRET
  APPID: wxa90967105d3402f4
  SECRET: ab13896b831b85cba266ca4166f3d500
  # cpolor代理的域名
  REDIRECTURL: https://3524e8fe.r9.cpolar.top/

# 测试

访问域名+/wxLogin测试

获取到用户相关信息

# 问题记录

1使用微信扫码后没有授权页面,直接返回了用户信息

2返回的用户信息缺少了一部分,sex,language,city等都是空

相关文档:

https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index

https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/iOS_WKWebview.html

https://www.cpolar.com/