在微信客户端中访问第三方网页(自己开发的),需要先通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。
授权获取openId和用户信息
开发步骤:网页授权 | 微信开放文档
首先要根据前端传来的 code 去获取 openId
/**
* 根据code获取openId&access_token
*/
public GetWxOpenId getOpenIdByCode(String code) {
try {
String getUrlUri = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type=authorization_code";
String url = MessageFormat.format(getUrlUri, projectAppVo.getAppId(), projectAppVo.getAppSecret(), code);
JSONObject json = RequestUtil.get(url);
if (ObjectUtil.isNotEmpty(json.get("openid"))) {
GetWxOpenId wxOpenId = JSON.to(GetWxOpenId.class, json);
return wxOpenId;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Data
public class GetWxOpenId {
@ApiModelProperty(name = "access_token", value = "token")
@JsonProperty("access_token")
private String accessToken;
@ApiModelProperty(name = "expires_in", value = "过期时间")
@JsonProperty("expires_in")
private Integer expiresIn;
@ApiModelProperty(name = "openid", value = "用户openId")
private String openid;
}
拿到了 openId 后,可以根据 openId 去获取改用户的信息
/**
* 获取微信用户信息
*/
public Map<String, Object> wxUserInfo(String openId) {
Map<String, Object> params = new HashMap<>(2);
try {
String getUrlUri = "https://api.weixin.qq.com/sns/userinfo?access_token={0}&openid={1}&lang=zh_CN";
String url = MessageFormat.format(getUrlUri, token, openId);
JSONObject json = RequestUtil.get(url);
if (ObjectUtil.isNotEmpty(json.get("nickname")) || ObjectUtil.isNotEmpty(json.get("headimgurl"))) {
params.put("nickName", json.get("nickname"));
params.put("headImgUrl", json.get("headimgurl"));
return params;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
JS-SDK使用
微信JS-SDK是微信公众平台,面向网页开发者提供的基于微信内的网页开发工具包。
借助这个工具包,开发这可以快速方便调用微信上的一些功能,例如拍照,选图,扫一扫等……
前端参考:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#1
后端参考:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62
前端要想使用 JS-SDK,第一步要先验签,验签因为需要用到 appsecret,为了安全要放在服务器上去计算签名,通过接口返回给到前端。
验签步骤
/**
* 获取JS调用凭证刷新Ticket
*/
public String getTicket(String appId, String appSecret) {
Long defaultExpire = 600L;
String accessToken = this.getAccessToken(appId, appSecret);
String ticketKey = "wx:ticket" ;
String val = redisUtil.get(ticketKey);
Long expire = redisUtil.getExpire(ticketKey);
if (Objects.nonNull(val) && expire > defaultExpire) {
return val;
}
return this.refreshTicket(ticketKey, accessToken);
}
/**
* 刷新Ticket
*/
public String refreshTicket(String ticketKey, String accessToken) {
String ticketUri = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi";
try {
String url = MessageFormat.format(ticketUri, accessToken);
JSONObject ticket = RequestUtil.get(url);
WeChatTicket weChatTicket = JSON.to(WeChatTicket.class, ticket);
if (Objects.nonNull(weChatTicket) && Objects.equals(weChatTicket.getErrcode(), 0)) {
redisUtil.set(ticketKey, weChatTicket.getTicket(), weChatTicket.getExpiresIn());
return weChatTicket.getTicket();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Data
public class WeChatTicket {
@ApiModelProperty(name = "ticket", value = "js凭证")
private String ticket;
@ApiModelProperty(name = "expires_in", value = "过期时间")
@JsonProperty("expires_in")
private Integer expiresIn;
@ApiModelProperty(name = "errcode", value = "状态码")
private int errcode;
}
//最终获取到的签名方法
public JsSignVo wxSignature(String url) {
if (StringUtils.isEmpty(url)) {
return Result.error("url不能为空");
}
String ticket = weChatUtil.getTicket(appId, appSecret);
if (StringUtils.isNotBlank(ticket)) {
try {
//前端传的地址编码了,所以需要先解码
url = URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String nonceStr = UUID.randomUUID().toString();
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String content = MessageFormat.format("jsapi_ticket={1}&noncestr={0}×tamp={2}&url={3}", nonceStr, ticket, timestamp, url);
String signature = sha1(content);
if (StringUtils.isNotBlank(signature)) {
JsSignVo jsSignVo = new JsSignVo();
jsSignVo.setNonceStr(nonceStr);
jsSignVo.setTimestamp(timestamp);
jsSignVo.setSignature(signature);
return jsSignVo;
}
}
return null;
}
@Data
public class JsSignVo {
@ApiModelProperty(name = "nonceStr", value = "随机字符串")
private String nonceStr;
@ApiModelProperty(name = "timestamp", value = "时间戳")
private String timestamp;
@ApiModelProperty(name = "signature", value = "签名")
private String signature;
}
public static String sha1(String str) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
签名后可以通过官方提供的工具去生成比对,https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
签名比对一致后,前端就可以正常去使用 JS-SDK 工具包中的功能。