又有一段時間沒動筆了,確實工作后忙碌起來了,年末了,給大家拜年了,抽了這個空檔把最近做的一個東西分享分享。
微信公眾號,相信不少人已經有接觸,其開發分為許多種,本次主要說的是,通過微信認證后的公眾號(下圖,),使用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都給你准備好了。過年快樂!