如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。
微信开放文档地址:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
本文主要介绍微信网页授权的流程。
常见的微信公众号页面是内嵌的html页面。微信一个个功能模块跳转的页面是一个个的网页,只不过它们是以关注并进入微信公众号作为登录的方式,就像我们登录登录一个网站一样,我们需要首先通过登录页面。
访问微信公众号的网页主要有两种方式:
方式一:关注微信公众号
方式二:进行网页授权
下面主要讲解网页授权的过程:
关于网页授权回调域名的说明
1、在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
2、授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com 无法进行OAuth2.0鉴权
设置回调域名,说明此域名下面的页面都可以进行OAuth2.0鉴权,简单来讲,如果用户同意授权后,此域名下的页面都具有权限,即都能正常访问。
网页授权可以用测试号进行测试,域名使用内网穿透工具natapp。
测试号地址,需要先申请。
https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
这里简要说明测试号,这是帮助开发人员进行测试的,和真实的微信公众号的配置基本一致,有助于实时开发调试。
关于网页授权的两种scope的区别说明
1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)
2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。
用户进行网页授权有两种方式,一种方式为微信服务器返回响应拉起授权页面,用户同意后即为授权完毕,可以通过openid(标识用户的身份)请求微信服务器获取用户的基本信息。这种方式用户不需关注公众号即能进行授权。
另一种方式为静默授权,即不会弹出上图的页面, 只能获取到openid,只有在用户关注公众号后才能获取用户的详细信息。简单来讲,用户登录公众号后从公众号的菜单页面跳转网页为静默授权。
但我们如果基于微信公众号开发一个网页总不能每一次都从公众号的菜单页跳转吧,所以网页授权机制可以使我们更加方便的开发公众号网页。
网页授权基本流程
2 第二步:通过code换取网页授权access_token
4 第四步:拉取用户信息(需scope为 snsapi_userinfo)
下面我用一个类来表示:
public class WxConstants {
/* 1 第一步:用户同意授权,获取code
2 第二步:通过code换取网页授权access_token
3 第三步:刷新access_token(如果需要)
4 第四步:拉取用户信息(需scope为 snsapi_userinfo)
5 附:检验授权凭证(access_token)是否有效
*/
//1.授权, code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
public static final String AUTH_BASE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
//2.通过code换取的是一个特殊的网页授权access_token
public static final String ACCESS_TOKEN_BASE_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
//3.access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新,refresh_token有效期为30天,当refresh_token失效之后,需要用户重新授权。
public static final String ACCESS_TOKEN_REFRESH_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
//4.通过access_token和openid拉取用户信息
public static final String INFO_BASE_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
//允许的范围
public static final String SCOPE = "snsapi_userinfo";
//token
public static final String TOKEN = "与上面设置的token一致";
private WxConstants(){}
}
用上述url发起请求时,只需要使用replace将大写的参数修改即可。
详细代码如下:
@RequestMapping(GlobalConstant.WX_URL_PREFIX+"/WxAuthorization")
@Controller
@Slf4j
public class WxAuthorizationController {
@Autowired
private WxUserInfoService wxUserInfoService;
@Autowired
private HttpSession httpSession;
@Autowired
private WxConfig wxConfig;
//接口配置信息URL
@GetMapping("/wxLogin")
public void doWxLogin (HttpServletRequest request, HttpServletResponse response) throws IOException {
//回调地址
String redirectUrl = wxConfig.getServer()+ "/wx/WxAuthorization/callBack";
//获取用户授权,获取code
String authUrl = WxConstants.AUTH_BASE_URL.replace("APPID",wxConfig.getAppId())
.replace("REDIRECT_URI",URLEncoder.encode(redirectUrl, "UTF-8"))
.replace("SCOPE",WxConstants.SCOPE);
//重定向
log.info(authUrl);
response.sendRedirect(authUrl);
}
//回调信息
@GetMapping(value = "/callBack")
public String wxCallBack(Model model, HttpServletRequest request, HttpServletResponse response, @PathParam("code") String code) throws IOException {
log.info("code:=="+code);
String tokenUrl = WxConstants.ACCESS_TOKEN_BASE_URL.replace("APPID",wxConfig.getAppId())
.replace("SECRET",wxConfig.getAppSecret())
.replace("CODE",code);
//返回结果的json对象
JSONObject resultObject = HttpClientUtil.doGetJson(tokenUrl);
//获取access_token
String access_token = resultObject.getString("access_token");
log.info("access_token:==="+access_token);
String open_id = resultObject.getString("openid");
log.info("open_id:=="+open_id);
//需要的话可以刷新access_token,延长access_token的有效时间,即在多长时间内不需要再次授权
// String refresh_token = resultObject.getString("refresh_token");
// log.info("refresh_token:==="+refresh_token);
//
// //刷新access_token
// String refUrl = WxConstants.ACCESS_TOKEN_REFRESH_URL.replace("APPID",wxConfig.getAppId())
// .replace("REFRESH_TOKEN",refresh_token);
// JSONObject jsonObject = HttpClientUtil.doGetJson(refUrl);
// String access_token1 = jsonObject.getString("access_token");
// log.info("access_token1:==="+access_token1);
String infoUrl = WxConstants.INFO_BASE_URL.replace("ACCESS_TOKEN",access_token)
.replace("OPENID",open_id);
JSONObject resultInfo = HttpClientUtil.doGetJson(infoUrl);
//从数据库查询用户信息
WxUser wxUser = wxUserInfoService.getByOpenId(open_id);
if(wxUser==null){
//向数据库中插入用户信息
String nickname = resultInfo.getString("nickname");
String sex = resultInfo.getString("sex");
String province = resultInfo.getString("province");
String city = resultInfo.getString("city");
String country = resultInfo.getString("country");
String headimgurl = resultInfo.getString("headimgurl");
WxUser wxUser1 = new WxUser(open_id, nickname, Integer.parseInt(sex), province, city, country, headimgurl);
wxUserInfoService.save(wxUser1);
}
JsonObject resultInfo1 = new JsonObject();
resultInfo1.addProperty("openid",open_id);
resultInfo1.addProperty("nickname",resultInfo.getString("nickname"));
resultInfo1.addProperty("province",resultInfo.getString("province"));
resultInfo1.addProperty("city",resultInfo.getString("city"));
resultInfo1.addProperty("country",resultInfo.getString("country"));
resultInfo1.addProperty("headimgurl",resultInfo.getString("headimgurl"));
resultInfo1.addProperty("province",resultInfo.getString("province"));
resultInfo1.addProperty("sex",resultInfo.getString("sex"));
//将openId存在session中
httpSession.setAttribute("openId",open_id);
httpSession.setAttribute("userInfo",resultInfo1);
model.addAttribute("userInfo",resultInfo);
String sex = resultInfo.getString("sex").equals("1")?"男":"女";
model.addAttribute("sex",sex);
model.addAttribute("openId",open_id);
return "wxMainHome";
}
}
只需要关注每次请求的参数和返回的响应数据即可。