一、网页授权,是指用户点击微信公众号里的自定义菜单在打开页面中获取授权用户信息,可以用来实现免登录。
下图是需要引导用户点开的地址其中redirect_uri=指向你真正要跳转的地址,比如可以在自定义菜单跳转地址中改成这种地址形式,就可以在页面中获取到地址栏的code。
/**
* 获取微信用户信息或者用户openid
* @param code 微信给菜单地址重定向后带地址栏的参数
* @param type 1只返回openid,20返回用户信息
* @return
* @throws IOException
*/
@RequestMapping(value="/getWxUser", method=RequestMethod.POST)
@ResponseBody
public Object getWxUser(String code, Integer type) throws IOException{
log.debug(code);
log.debug(type);
if(Assert.isInvalidString(code) || type==null){
return null;
}
//获取access_token和openid
Map<String, Object> params = new HashMap<String, Object>();
params.put("appid", SysConfig.getValue(SysConstants.Config.WX_APPID));//微信公众号的Appid
params.put("secret", SysConfig.getValue(SysConstants.Config.WX_SECRET));//微信公众号的AppSecret
params.put("code", code);//微信给菜单地址重定向后带地址栏的参数
params.put("grant_type", "authorization_code");
HttpURLConnection conn = HttpUtil.doGet(SysConstants.Url.WX_OAUTH_ACCESS_TOKEN_URL, params, null);
JSONObject result = JSONObject.parseObject(HttpUtil.getString(conn));
log.debug(result);
if(type==1){
return result;
}else if(type==20){
//获取用户信息
params = new HashMap<String, Object>();
//这个access_token是网页授权专用的,和jssdk中的2小时时效,一天最多2000次的那个不一样,这个没有限制次数
params.put("access_token", result.get("access_token"));
params.put("openid", result.get("openid"));
params.put("lang", "zh_CN");
conn = HttpUtil.doGet(SysConstants.Url.WX_USERINFO_URL, params, null);
result = JSONObject.parseObject(HttpUtil.getString(conn));
log.debug(result);
return result;
}else{
return null;
}
}
二、获取微信JS-SDK权限
获取微信js-sdk权限可以通过微信来调用手机的一些功能,比如拍照。
这里要用到数据库保存access_token和jsapi_ticket,因为这两个都只有2小时时效,一天最多2000次的。
/**
* 获取wx.config的配置参数
* @param url
* @return
* @throws Exception
*/
@RequestMapping(value="/getWxJS", method=RequestMethod.POST)
@ResponseBody
public Object configWxJS(String url) throws Exception{
JSONObject json = new JSONObject();
String appId = SysConfig.getValue(SysConstants.Config.WX_APPID);
//1、数据库中查找jsapi_ticket,用修改时间与现在的时间比较是否过期(7200秒)。
//2、如果jsapi_ticket过期(超过7200秒),数据库中查找access_token,用修改时间与现在的时间比较是否过期(7200秒),如果jsapi_ticket没有过期直接用来加密签名
//3、如果access_token过期,先重新获取access_token,如果没有过期使用access_token获取jsapi_ticket
List<Config> configs = configService.findByProperty("configKey", "jsapi_ticket");//数据库中查找jsapi_ticket
Config jsapiticket = Assert.isValidCollection(configs)?configs.get(0):null;
Long timestamp = System.currentTimeMillis();//当前时间
if(jsapiticket==null || Assert.isInvalidString(jsapiticket.getConfigValue()) || (timestamp-jsapiticket.getModifyTime().getTime())/1000l>=jsapiticket.getExpiresIn()){//判断jsapi_ticket是否过期
//需要修改jsapi_ticket
configs = configService.findByProperty("configKey", "access_token");//数据库查找access_token
Config accesstoken = Assert.isValidCollection(configs)?configs.get(0):null;
if(accesstoken==null || Assert.isInvalidString(accesstoken.getConfigValue()) || (timestamp-accesstoken.getModifyTime().getTime())/1000l>=accesstoken.getExpiresIn()){//判断access_token是否过期
//需要修改access_token
Map<String, Object> params = new HashMap<String, Object>();
params.put("grant_type", "client_credential");
params.put("appid", appId);
params.put("secret", SysConfig.getValue(SysConstants.Config.WX_SECRET));
HttpURLConnection conn = HttpUtil.doGet(SysConstants.Url.WX_JS_ACCESS_TOKEN_URL, params, null);
JSONObject result = JSONObject.parseObject(HttpUtil.getString(conn));
log.debug(result.toString());
if(Assert.isValidString(result.getString("access_token"))){
accesstoken = accesstoken==null?new Config():accesstoken;
accesstoken.setConfigKey("access_token");
accesstoken.setConfigValue(result.getString("access_token"));
accesstoken.setExpiresIn(result.getLong("expires_in"));
accesstoken.setModifyTime(new Date(timestamp));
configService.saveOrUpdate(accesstoken);
}else{
throw new Exception("获取access_token出错。");
}
}
//使用access_token获取修改jsapi_ticket
Map<String, Object> params = new HashMap<String, Object>();
params.put("access_token", accesstoken.getConfigValue());
params.put("type", "jsapi");
HttpURLConnection conn = HttpUtil.doGet(SysConstants.Url.WX_JSAPI_TICKET, params, null);
JSONObject result = JSONObject.parseObject(HttpUtil.getString(conn));
log.debug(result.toString());
if(Assert.isValidString(result.getString("ticket"))){
jsapiticket = jsapiticket==null?new Config():jsapiticket;
jsapiticket.setConfigKey("jsapi_ticket");
jsapiticket.setConfigValue(result.getString("ticket"));
jsapiticket.setExpiresIn(result.getLong("expires_in"));
jsapiticket.setModifyTime(new Date(timestamp));
configService.saveOrUpdate(jsapiticket);
}else{
throw new Exception("获取jspapi_ticket出错。");
}
}
//使用jsapi_ticket加密签名
String nonceStr = UUID.randomUUID().toString().replaceAll("-", "").substring(10, 26);
String str = "jsapi_ticket="+jsapiticket.getConfigValue()+"&noncestr="+nonceStr+"×tamp="+timestamp+"&url="+url;
String signature = DigestUtils.shaHex(str);
json.put("appId", appId);
json.put("signature", signature);
json.put("nonceStr", nonceStr);
json.put("timestamp", String.valueOf(timestamp));
log.debug(json.toString());
return json;
}
通过这个接口就可以获取到页面中调用微信的js所需要的配置信息
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: ”, // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: ”, // 必填,生成签名的随机串
signature: ”,// 必填,签名,见附录1
jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
三、一些重要的常量
public class SysConstants {
private SysConstants(){}
public class Url{
/**
* 获取授权access_token的地址
* 参数 :?appid=${APPID}&secret=${SECRET}&code=${CODE}&grant_type=authorization_code
*/
public static final String WX_OAUTH_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";
/**
* 获取用户信息的地址
* 参数 :?access_token=${ACCESS_TOKEN}&openid=${OPENID}&lang=zh_CN
*/
public static final String WX_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo";
/**
* 获取基础接口的access_token地址
* 参数 :?grant_type=client_credential&appid=APPID&secret=APPSECRET
*/
public static final String WX_JS_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
/**
* 获取jsapiticket地址
* ?access_token=ACCESS_TOKEN&type=jsapi
*/
public static final String WX_JSAPI_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
}