微信网页登录授权、APP登录授权、JS-SDK接口调用
温馨提示:实践某项功能请至少读完准备工作,可以避免很多的坑
https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Development_Guide.html
[https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html)
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.htmlhttps://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html)
开发阶段可用此工具获取域名
测试账号拥有微信几乎所有接口的能力,可以先通过测试账号提前验证
获取测试号信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BthVzoAY-1634971407266)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211023123859614.png)]
填写接口配置信息,此信息需要你有自己的服务器资源,填写的URL需要正确响应微信发送的Token验证,请阅读消息接口使用指南。
字段 | 说明 |
---|---|
URL | 开发者用来接收微信消息和事件的接口URL(可以外网访问自己服务器IP:端口号也可以) |
TOKEN | 自己随意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性) |
验证接口实现:
@ApiOperation("微信公众号认证入口")
@GetMapping(value = "/wechat/api/wxServerValdation")
public String wxServerValdation(String signature, String timestamp, String nonce, String echostr){
if (Objects.isNull(signature)|| Objects.isNull(timestamp) || Objects.isNull(nonce) || Objects.isNull(echostr)){
return "fail";
}
ArrayList<String> list= new ArrayList<>();
list.add(nonce);
list.add(timestamp);
//这是第5步中你设置的Token
list.add(WxMpConfig.token);
Collections.sort(list);
String sha1Singnature = DigestUtils.sha1Hex(list.get(0)+list.get(1)+list.get(2));
if (sha1Singnature.equals(signature)){
return echostr;
}else {
return "fail";
}
}
此时的URL可以设置为 :http://IP:端口号/wechat/api/wxServerValdation
也可以填写为域名+/wechat/api/wxServerValdation
验证字段说明
参数 | 描述 |
---|---|
signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 随机字符串 |
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
验证工具:
http://mp.weixin.qq.com/debug/
JS接口安全域名
设置JS接口安全域后,通过关注该测试号,开发者即可在该域名下调用微信开放的JS接口,请阅读微信JSSDK开发文档。
请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
微信根据此域名检查回调地址域名
请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
正式的微信账号分为订阅号、服务号、企业号,认证都需要300元,但是使用授权和接口功能必须认证。
但是订阅号不能直接获取到用户的OpenID,所以尽量申请一个服务号。(这点微信也是不会提醒用户的,但是当你看了一下测试账号是订阅号,就直接申请了一个订阅号结果就不言而喻了,接口处就会提醒用户,只有服务号才能使用授权接口。)
不过订阅号也是有一个漏洞的,如果你是在公众号内使用,是可以迂回获取到用户信OpenID的。可以参考下面的文章:https://blog.csdn.net/vbirdbest/article/details/51217478
接口配置主要有一下三点,其中IP白名单要加入所有访问微信接口服务器的IP
基本配置
服务器配置
功能设置
接入微信公众平台开发,开发者需要按照如下步骤完成:
1、填写服务器配置
2、验证服务器地址的有效性
3、依据接口文档实现业务逻辑
网页授权获取用户基本信息:通过该接口,可以获取用户的基本信息(获取用户的OpenID是无需用户同意的,获取用户的基本信息则需用户同意)
关于网页授权access_token和普通access_token的区别
1、微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,公众号可以获取到一个网页授权特有的接口调用凭证(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,如获取用户基本信息;
2、其他微信接口,需要通过基础支持中的“获取access_token”接口来获取到的普通access_token调用。
网页授权流程分为四步:
1、引导用户进入授权页面同意授权,获取code(拉起授权页面)
2、通过code换取网页授权access_token(与基础支持中的access_token不同)
3、如果需要,开发者可以刷新网页授权access_token,避免过期
4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)
5 附:检验授权凭证(access_token)是否有效
调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台帐号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台帐号) 和 会话密钥 session_key。
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份
微信JS-SDK:是开发者在网页上通过JavaScript代码使用微信原生功能的工具包,开发者可以使用它在网页上录制和播放微信语音、监听微信分享、上传手机本地图片、拍照等许多能力。
这块可能会出现前端调用失败的问题,或者分享的时候微信不能分享的问题,这个很有可能时版本的问题,请先倒回之前的版本,再进行测试。
获取Access token
官方接口测试工具
https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index&type=%E5%9F%BA%E7%A1%80%E6%94%AF%E6%8C%81&form=%E8%8E%B7%E5%8F%96access_token%E6%8E%A5%E5%8F%A3%20/token
获得jsapi_ticket
(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
微信 JS 接口签名校验工具
yml 可以把地址也存放到配置里
#微信开放平台账号
wechat:
#服务号
mp:
appId:
secret:
token:
aesKey:
WxMpConfig 配置类
@Component
@ConfigurationProperties("wechat.mp")
public class WxMpConfig {
/**
* 设置微信公众号的appid
*/
public static String appId;
/**
* 设置微信公众号的app secret
*/
public static String secret;
/**
* 设置微信公众号的token
*/
public static String token;
/**
* js 回调地址
*/
public static String jsUrl;
/**
* 微信认证路径
*/
public static String url;
/**
* 微信认证路径
*/
public static String aesKey;
public void setAppId(String appId) {
WxMpConfig.appId = appId;
}
public void setSecret(String secret) {
WxMpConfig.secret = secret;
}
public void setToken(String token) {
WxMpConfig.token = token;
}
public void setJsUrl(String jsUrl) {
WxMpConfig.jsUrl = jsUrl;
}
public void setUrl(String url) {
WxMpConfig.url = url;
}
public void setAesKey(String aesKey) {
WxMpConfig.aesKey = aesKey;
}
}
WxAuthUtil
@Slf4j
public class WxAuthUtil {
/**
* APPID
*/
public final static String APP_ID = "APP_ID";
/**
* SECRET
*/
public final static String SECRET = "SECRET";
public final static String CODE = "CODE";
public final static String OPEN_ID = "OPEN_ID";
public final static String ACCESS_TOKEN = "ACCESS_TOKEN";
public final static String REFRESH_TOKEN = "REFRESH_TOKEN";
public final static String WX_ACCESS_TOKEN = "WX_ACCESS_TOKEN";
public final static String WX_JSAPI_TICKET = "WX_JSAPI_TICKET";
/**
* 公用获取access_token访问地址
*/
public final static String JS_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
/**
* 获取jsApiTicket访问地址
*/
public final static String JS_API_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
/**
* 网页授权 获取access_token访问地址
*/
public final static String H5_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APP_ID&secret=SECRET&code=CODE&grant_type=authorization_code";
/**
* 检查access_token是否失效地址
*/
public final static String CHECK_H5_URL = "https://api.weixin.qq.com/sns/auth?access_token="+ACCESS_TOKEN+"&openid="+OPEN_ID;
/**
* 刷新access_token地址
*/
public final static String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + OPEN_ID
+ "&grant_type=refresh_token&refresh_token=" + REFRESH_TOKEN;
/**
* 拉取用户信息
*/
public final static String USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=" + ACCESS_TOKEN + "&openid=" + OPEN_ID
+ "&lang=zh_CN";
/**
* 获取accessToken
* @return Map
* @throws IOException
*/
public static JSONObject getAccessToken() throws IOException {
String requestUrl = JS_ACCESS_TOKEN_URL.replace("APPID", WxMpConfig.appId).replace("APPSECRET",WxMpConfig.secret);
//向微信发送get请求
log.info("requestUrl: {}", requestUrl);
JSONObject callBack = doGet(requestUrl);
log.info("获取accessToken:{}", callBack);
return callBack;
}
/**
* 微信请求
* @param url
* @return Object
* @throws IOException
*/
public static JSONObject doGet(String url) throws IOException {
JSONObject jsonObject = null;
HttpClient client = HttpClientBuilder.create().build();
final HttpGet httpGet = new HttpGet(url);
HttpResponse response = client.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
// 返回结果转化为JSON对象
final String result = EntityUtils.toString(entity, "UTF-8");
jsonObject = JSON.parseObject(result);
}
return jsonObject;
}
}
这里只详细介绍网页授权,其他方式大同小异,官方文档可以查看
APP 与 H5 获取 code 的区别:h5 是前端通过后台拿到授权 url,然后前端请求该 url 得到 code 再请求后台;APP 则是前端配合使用微信开放平台提供的 SDK 进行授权登录请求,用户同意授权后得到 code 再去请求后台;
获取code请求路径
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx6fb2b539e0663d3b&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_re
获取code 需要先设置REDIRECT_URI 回调路径
如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
code说明 : code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
获取code后,请求以下链接获取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
参数 | 是否必须 | 说明 |
---|---|---|
appid | 是 | 公众号的唯一标识 |
secret | 是 | 公众号的appsecret |
code | 是 | 填写第一步获取的code参数 |
grant_type | 是 | 填写为authorization_code |
需要用缓存存储access_token
https://api.weixin.qq.com/sns/userinfo?access_token=" + ACCESS_TOKEN + "&openid=" + OPEN_ID + "&lang=zh_CN";
检查token是否失效
@ApiOperation("网页拉起微信授权页面,返回微信校验信息")
@GetMapping("/auth")
public R<String> callBack(@ApiParam(value = "*code",required = true)String code) throws IOException {
/**
* 获取到授权标志code(用户同意微信授权之后产生的code)
*/
log.info("进入微信回调code={}",code);
String url = WxAuthUtil.H5_ACCESS_TOKEN_URL.
replace(WxAuthUtil.APP_ID,WxMpConfig.appId).
replace(WxAuthUtil.SECRET, WxMpConfig.secret).
replace(WxAuthUtil.CODE,code);
JSONObject jsonObject = WxAuthUtil.doGet(url);
log.info("授权结果{}",jsonObject);
return R.ok(jsonObject.getString("openid"));
}
@ApiOperation("拉取微信用户信息,每天获取token的次数受限,需要将accessToken缓存(目前不需要获取用户信息)")
@GetMapping("/userInfo")
public R<WxUserVO> getWeiChatUserInfo(@RequestParam("code")String openid,
@RequestParam("accessToken")String accessToken,
@RequestParam("refreshToken")String refreshToken) throws IOException {
/**
* 校验access_token是否失效
*/
String checkoutUrl = WxAuthUtil.CHECK_H5_URL.
replace(WxAuthUtil.ACCESS_TOKEN, accessToken).
replace(WxAuthUtil.OPEN_ID,openid);
JSONObject checkoutInfo = WxAuthUtil.doGet(checkoutUrl);
log.info("校验信息-----{}",checkoutInfo.toString());
if (!"0".equals(checkoutInfo.getString("errcode"))) {
// 刷新access_token
String refreshTokenUrl = WxAuthUtil.REFRESH_TOKEN_URL
.replace(WxAuthUtil.APP_ID,WxMpConfig.appId)
.replace(WxAuthUtil.REFRESH_TOKEN, refreshToken);
JSONObject refreshInfo = WxAuthUtil.doGet(refreshTokenUrl);
System.out.println(refreshInfo.toString());
accessToken = refreshInfo.getString("access_token");
}
/**
* 使用access_token拉取用户信息
*/
String infoUrl = WxAuthUtil.USER_INFO_URL
.replace(WxAuthUtil.ACCESS_TOKEN, accessToken)
.replace(WxAuthUtil.OPEN_ID, openid);
JSONObject userInfo = WxAuthUtil.doGet(infoUrl);
WxUserVO wxUserVO = JSONObject.parseObject(String.valueOf(userInfo), WxUserVO.class);
log.info("用户数据-----{}", userInfo);
return R.ok(wxUserVO);
}
@ApiOperation("生成微信JS-SDK签名")
@PostMapping( "/signature")
public R<Map<String, String>> makeWxSignature(@RequestBody @Validated WxH5VO wxH5VO){
//获取jsapiTicket
String jsapiTicket = getJsapiTicket();
Map<String, String> ret = new HashMap<>(6);
String nonceStr = RandomUtils.getRandomStr();
long timestamp = System.currentTimeMillis()/1000L;
//加密
String signature = SHA1.genWithAmple(
"jsapi_ticket=" + jsapiTicket, "noncestr=" + nonceStr, "timestamp=" + timestamp, "url=" + wxH5VO.getUrl());
ret.put("url", wxH5VO.getUrl());
ret.put("jsapi_ticket", jsapiTicket);
ret.put("nonceStr", nonceStr);
ret.put("timestamp", Long.toString(timestamp));
ret.put("signature", signature);
ret.put("appId", WxMpConfig.appId);
return R.ok(ret);
}
/**
* 1、缓存中获取 jsApiTicket
* 2、没有 已过期 重新获取
* 2、获取缓存中的access_token
* 3、没有 证明已经过期 重新获取
* 获取jsapiTicket
* @return jsapiTicket
*/
private String getJsapiTicket(){
String jsapiTicket = null;
try{
//根据access_token 获取jsapiTicket
jsapiTicket = redisService.getCacheObject(WxAuthUtil.WX_JSAPI_TICKET);
if(Objects.isNull(jsapiTicket)){
//获取access_token
String accessToken = redisService.getCacheObject(WxAuthUtil.WX_ACCESS_TOKEN);
if(Objects.isNull(accessToken)){
JSONObject accessTokenMap = WxAuthUtil.getAccessToken();
accessToken = accessTokenMap.getString("access_token");
log.info("访问获取access_token数据{}", accessTokenMap);
redisService.setCacheObject(WxAuthUtil.WX_ACCESS_TOKEN, accessToken, accessTokenMap.getLong("expires_in"), TimeUnit.SECONDS);
}
String requestUrl = WxAuthUtil.JS_API_TICKET_URL.replace("ACCESS_TOKEN", accessToken);
//向微信发送get请求
JSONObject callBack = WxAuthUtil.doGet(requestUrl);
log.info("访问获取jsApiTicket数据{}", callBack);
jsapiTicket = callBack.getString("ticket");
redisService.setCacheObject(WxAuthUtil.WX_JSAPI_TICKET, jsapiTicket,callBack.getLong("expires_in"), TimeUnit.SECONDS);
}
log.info("获取jsApiTicket:{}", jsapiTicket);
}catch (IOException e){
log.info("获取签名异常:" + e.getMessage());
}
return jsapiTicket;
}