微信公众号用户信息认证机制
又有一段时间没动笔了,确实工作后忙碌起来了,年末了,给大家拜年了,抽了这个空档把最近做的一个东西分享分享。
微信公众号,相信不少人已经有接触,其开发分为许多种,本次主要说的是,通过微信认证后的公众号(下图,),使用Oauth网页接口来获取用户信息的流程。
一、基础准备
首先,明确需求:有一个应用服务器,希望通过某个链接(菜单)进入此应用,在应用服务器端可以获得访问者的一些信息。
微信是不会直接让你在用户在访问应用服务器时直接取到用户信息的,而需要通过他们的专门的认证服务器来操作。作为开发者,目前有两类获取用户信息的方法,这里只介绍Oauth 2.0认证(其它还有一种CGI接口,是必须加了微信公众号关注后才可取到用户信息,而使用Oauth则可以不加关注也能取到),结合微信我整理了一个简单的流程:
1、用户点开了某个菜单或者链接,进入到了我们的后台应用服务器(原始请求:URL_ORG);
2、服务器发现没有用户认证信息(微信所谓的认证code,使用一次后失效),于是返回了一个rediret头给用户,并且让它去访问微信的认证服务器;
3、用户的浏览器会去访问微信的认证服务器(用户会看到闪一下,这是重定向进行中);
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=URL_ORG&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect
4、如果用户授权(微信会询问,是否授权,如果加了关注的用户则不会询问,默认授权),则微信认证服务器会返回一个redirect头给用户,同时包含了认证code,让用户浏览器去访问原始请求的链接;
5、这时用户的浏览器会带着code去访问我们的后台应用(此时还会再闪一下,重定向:URL_ORG?code=xxxxxx);
6、应用服务器拿到了code还没有用,还得由应用服务器去向微信认证服务器取token(ACCESS_TOKEN,一段时间内有效),在取得token时还会返回用户的openId(OEPNID);
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
7、在取到token、openId后,再发起一次请求,取得用户的个人信息;
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
上面的过程,看起来非常复杂, 没错,有两次重定向!但是好处在于,用户访问的原始链接肯定是干净的,比如www.abc.com/xxx,微信那边的链接或菜单在设置时不需要变动。如果大家愿意去维护菜单的链接地址,也可以使用微信官方文档的方法,省去上面的第1、2步,直接把原始链接设置为:微信认证接口地址+待回调原始地址,的模式,减少一次用户可感知的闪烁。
二、核心代码
转java后,我也没时间去写成其它语言了,大家就看java代码吧,第一个地方,对应用服务器需求使用用户信息的url地址使用filter:
1 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 2 HttpServletRequest request = (HttpServletRequest) servletRequest; 3 HttpServletResponse response = (HttpServletResponse) servletResponse; 4 HttpSession session = request.getSession(); 5 if (request != null) { 6 // 尝试公众号 7 Object openId = session.getAttribute(WxSession.WX_OPENID_KEY); 8 // 未完成认证 9 if (openId == null) { 10 String code = request.getParameter("code"); 11 if (code != null) { 12 // 第二步,通过code换取access_token和OpenId 13 JSONObject ret = _authService.getAccessToken(code); 14 String token = ret.getString("access_token"); 15 openId = ret.getString("openid"); 16 // 第三步,拉取用户信息 17 ret = _authService.getExternalUserInfo(token, openId.toString()); 18 String nickname = ret.getString("nickname"); 19 // 存session 20 session.setAttribute(WxSession.WX_OPENID_KEY, openId); 21 session.setAttribute(WxSession.WX_USER_NAME_KEY, nickname); 22 } else { 23 // 第一步,获取code 24 response.sendRedirect(_authService.getExternalAuthUrl(request.getRequestURL().toString())); 25 return; 26 } 27 } 28 } 29 filterChain.doFilter(servletRequest, servletResponse); 30 }
上面的代码有注释,非常明白了,先看有没有code,没有code就重定向去取,取到后用户再来;当有code时,服务器自己去取token和openid,取到后再取任何用户信息(上面只取了用户昵称)。
第二个地方,上面代码中的getExternalAuthUrl、getAccessToken、getExternalUserInfo三个方法:
1 /** 2 * 跳转到OAuth登录页面,可有用户的详细信息 3 */ 4 public String getExternalAuthUrl(String redirectUri) { 5 String encodeUrl = null; 6 try { 7 encodeUrl = URLEncoder.encode(redirectUri, "UTF-8"); 8 } catch (UnsupportedEncodingException e) { 9 _log.error(e); 10 } 11 String url = "https://open.weixin.qq.com/connect/oauth2/authorize"; 12 url += "?appid=" + Constant.Subscribe.AppId; 13 url += "&redirect_uri=" + encodeUrl; 14 url += "&response_type=code"; 15 url += "&scope=snsapi_userinfo"; 16 url += "&state=123"; 17 url += "#wechat_redirect"; 18 return url; 19 } 20 21 /** 22 * 获取OAuth的access_token,错误时返回null 23 */ 24 public JSONObject getAccessToken(String code) { 25 String url = "https://api.weixin.qq.com/sns/oauth2/access_token"; 26 url += "?appid=" + Constant.Subscribe.AppId; 27 url += "&secret=" + Constant.Subscribe.AppSecret; 28 url += "&code=" + code; 29 url += "&grant_type=authorization_code"; 30 try { 31 String responseStr = HttpInvork.getRequestWithoutHeader(url); 32 JSONObject json = JSONObject.fromObject(responseStr); 33 String access_token = json.getString("access_token"); 34 if (StringUtils.isNotEmpty(access_token)) { 35 return json; 36 } 37 } catch (Exception e) { 38 _log.error(e); 39 } 40 return null; 41 } 42 43 /** 44 * 获取OAuth微信用户信息,错误时返回null 45 */ 46 public JSONObject getExternalUserInfo(String accessToken, String openId) { 47 String url = "https://api.weixin.qq.com/sns/userinfo"; 48 url += "?access_token=" + accessToken; 49 url += "&openid=" + openId; 50 url += "&lang=zh_CN"; 51 String responseStr = HttpInvork.getRequestWithoutHeader(url); 52 try { 53 return JSONObject.fromObject(responseStr); 54 } catch (Exception e) { 55 _log.error(e); 56 } 57 return null; 58 }
第三个地方,上面的HttpInvork.getRequestWithoutHeader方法,我就不贴了,大家随便写/找个可以发送get请求的方法代替一下吧。
三、福利?
大家不满足于Oauth?那我把CGI代码也贴出来吧。先还是说明一下:使用cgi方式时,openid还是跟前面的类似,scope可改为snsapi_base(不改也无所谓啦,还有一种openid获取方法前面没有说,就是向公众号发了消息,然后通过微信的公众平台的服务器配置绑定到了后台应用,通过解析后也可以取到openid),然后还是要token才能要用户信息,只是这里就不能用Oauth的token了,而是普通的cgi token,代码:
1 /** 2 * 获取CGI的access_token,错误时返回null 3 */ 4 public String getCgiAccessToken() { 5 String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"; 6 url += "&appid=" + Constant.Subscribe.AppId; 7 url += "&secret=" + Constant.Subscribe.AppSecret; 8 try { 9 String responseStr = HttpInvork.getRequestWithoutHeader(url); 10 JSONObject json = JSONObject.fromObject(responseStr); 11 String access_token = json.getString("access_token"); 12 if (StringUtils.isNotEmpty(access_token)) { 13 return access_token; 14 } 15 } catch (Exception e) { 16 _log.error(e); 17 } 18 return null; 19 } 20 21 /** 22 * 通过CGI获取微信用户信息,错误时返回null 23 */ 24 public JSONObject getCgiUserInfo(String accessToken, String openId) { 25 String url = "https://api.weixin.qq.com/cgi-bin/user/info"; 26 url += "?access_token=" + accessToken; 27 url += "&openid=" + openId; 28 url += "&lang=zh_CN"; 29 String responseStr = HttpInvork.getRequestWithoutHeader(url); 30 try { 31 return JSONObject.fromObject(responseStr); 32 } catch (Exception e) { 33 _log.error(e); 34 } 35 return null; 36 }
说了好多,我建议是,大家申请一个实战演练演练,除了认证复杂了点,其它开发跟传统的web开发没啥太大区别了,微信JS-SDK都给你准备好了。过年快乐!