微信开发之Author网页授权

smartlingyl 8年前
   <p>微信开发中,经常有这样的需求:获得用户头像、绑定微信号给用户发信息.. 那么实现这些的前提就是授权!</p>    <h3><strong>1.配置安全回调域名:</strong></h3>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/af5ee809ad85cfe91a91776965391560.png"></p>    <p>在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名,值得注意的是这里就是直接写全域名,如: www.liliangel.cn。然而我们开发h5中一般用的是二级域名,如:h5.liliangel.cn 也同样在安全回调域名中。</p>    <h3><strong>2.用户级授权和静默授权</strong></h3>    <p>1、以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页。</p>    <p>2、以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。 </p>    <h3><strong>3.网页授权access_token和普通access_token的区别</strong></h3>    <p>1、微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,公众号可以获取到一个网页授权特有的接口调用凭证(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,如获取用户基本信息; </p>    <p>2、其他微信接口,需要通过基础支持中的“获取access_token”接口来获取到的普通access_token调用。 </p>    <h3><strong>4.引导用户进入授权页面同意授权,获取code</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d91fef88818686c8d626b896d5664dee.png"></p>    <p>微信更新后,授权页也变化了。其实习惯了绿色的那个经典页面..</p>    <p>js:</p>    <pre>  <code class="language-javascript">var center = {          init: function(){              .....          },          enterWxAuthor: function(){              var wxUserInfo = localStorage.getItem("wxUserInfo");              if (!wxUserInfo) {                  var code = common.getUrlParameter('code');                  if (code) {                      common.getWxUserInfo();                      center.init();                  }else{                      //没有微信用户信息,没有授权-->> 需要授权,跳转授权页面                      window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='+ WX_APPID +'&redirect_uri='+ window.location.href +'&response_type=code&scope=snsapi_userinfo#wechat_redirect';                  }              }else{                  center.init();              }          }  }  $(document).ready(function() {       center.enterWxAuthor();  }        </code></pre>    <p>以scope=snsapi_userinfo为例,页面加载的时候进入授权方法,首先从缓存获取wxUserInfo对象,如果有说明之前已经授权过,直接进入初始化方法。如果没有,判断url是否包含code,有code说明是进入授权页回调后的页面,那么通过code换取用户信息即可。没有code,即用户第一次进入该页面,引导去授权页,redirect_uri为当前页面地址。</p>    <p>getWxUserInfo方法:</p>    <pre>  <code class="language-javascript">/**       * 授权后获取用户的基本信息       */      getWxUserInfo:function(par){          var code = common.getUrlParameter("code");                    if (par) code = par;                    $.ajax({              async: false,              data: {code:code},              type : "GET",              url : WX_ROOT + "wechat/authorization",              success : function(json) {                  if (json){                      try {                          //保证写入的wxUserInfo是正确的                          var data = JSON.parse(json);                          if (data.openid) {                              localStorage.setItem('wxUserInfo',json);//写缓存--微信用户信息                          }                      } catch (e) {                          // TODO: handle exception                      }                  }              }          });      },  </code></pre>    <h3>5.后台restful-- /wechat/authorization,根据code换取用户信息</h3>    <pre>  <code class="language-javascript">        /**       * 微信授权       * @param code 使用一次后失效       *        * @return 用户基本信息       * @throws IOException        */      @RequestMapping(value = "/authorization", method = RequestMethod.GET)      public void authorizationWeixin(              @RequestParam String code,              HttpServletRequest request,               HttpServletResponse response) throws IOException{          request.setCharacterEncoding("UTF-8");            response.setCharacterEncoding("UTF-8");             PrintWriter out = response.getWriter();          LOGGER.info("RestFul of authorization parameters code:{}",code);                    try {              String rs = wechatService.getOauthAccessToken(code);              out.write(rs);              LOGGER.info("RestFul of authorization is successful.",rs);          } catch (Exception e) {              LOGGER.error("RestFul of authorization is error.",e);          }finally{              out.close();          }      }  </code></pre>    <p>这里有一个授权access_token,切记:授权access_token非全局access_token ,需要使用缓存,这里我使用的redis,具体配置不多说后面写相关配置博文,当然也可以使用ehcache,关于ehcahe配置在我的第一篇博客中有详细介绍。</p>    <pre>  <code class="language-javascript">        /**       * 根据code 获取授权的token 仅限授权时使用,与全局的access_token不同       * @param code       * @return       * @throws IOException        * @throws ClientProtocolException        */      public String getOauthAccessToken(String code) throws ClientProtocolException, IOException{          String data = redisService.get("WEIXIN_SQ_ACCESS_TOKEN");          String rs_access_token = null;          String rs_openid = null;          String url = WX_OAUTH_ACCESS_TOKEN_URL + "?appid="+WX_APPID+"&secret="+WX_APPSECRET+"&code="+code+"&grant_type=authorization_code";          if (StringUtils.isEmpty(data)) {              synchronized (this) {                  //已过期,需要刷新                  String hs = apiService.doGet(url);                  JSONObject json = JSONObject.parseObject(hs);                  String refresh_token = json.getString("refresh_token");                        String refresh_url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid="+WX_APPID+"&grant_type=refresh_token&refresh_token="+refresh_token;                  String r_hs = apiService.doGet(refresh_url);                  JSONObject r_json = JSONObject.parseObject(r_hs);                  String r_access_token = r_json.getString("access_token");                  String r_expires_in = r_json.getString("expires_in");                  rs_openid = r_json.getString("openid");                                    rs_access_token = r_access_token;                  redisService.set("WEIXIN_SQ_ACCESS_TOKEN", r_access_token, Integer.parseInt(r_expires_in) - 3600);                  LOGGER.info("Set sq access_token to redis is successful.parameters time:{},realtime",Integer.parseInt(r_expires_in), Integer.parseInt(r_expires_in) - 3600);              }          }else{              //还没有过期              String hs = apiService.doGet(url);              JSONObject json = JSONObject.parseObject(hs);              rs_access_token = json.getString("access_token");              rs_openid = json.getString("openid");              LOGGER.info("Get sq access_token from redis is successful.rs_access_token:{},rs_openid:{}",rs_access_token,rs_openid);          }                    return getOauthUserInfo(rs_access_token,rs_openid);      }          /**       * 根据授权token获取用户信息       * @param access_token       * @param openid       * @return       */      public String getOauthUserInfo(String access_token,String openid){          String url = "https://api.weixin.qq.com/sns/userinfo?access_token="+ access_token +"&openid="+ openid +"⟨=zh_CN";          try {              String hs = apiService.doGet(url);                            //保存用户信息              saveWeixinUser(hs);                            return hs;          } catch (IOException e) {              LOGGER.error("RestFul of authorization is error.",e);          }          return null;      }  </code></pre>    <p>当时赶时间,代码命名较乱。可以看到,我用了一个同步的方法,先从缓存中获取key为WEIXIN_SQ_ACCESS_TOKEN,如果取到了说明没有过期,直接通过httpclient调用微信提供的接口,返回用户信息的字符串给前端。如果没有取到,说明没有或者已经过期,则根据refresh_token刷新access_token,再写缓存,由于access_token拥有较短的有效期,为了保险我这里设置了缓存的失效时间微信给的时间再减一个小时。回过头来看代码发现,上面的逻辑有点点小问题,这样写会导致第一次获取或者缓存失效后第一次获取access_token都会去刷新一次,暂时不影响使用,后面做优化修改 TODO。</p>    <h3><strong>6:保存用户信息</strong></h3>    <p>通常情况下,授权后我们会将用户信息保存数据库表,openid为唯一主键,外键关联起我们自己的用户表,这样一来,无论是后续要开展什么业务,还是做运营数据统计,都有了一个跟微信公众号的关联关系。 值得注意的是:我们获取到的headimgurl是微信提供的一个url地址,当用户修改头像后可能导致原来的地址失效,所以最好是通过将图片保存到本地服务器然后保存本地的地址url!</p>    <p>微信返回的值:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/0ed4b3356c616f4c9062816b839e2b6b.png"></p>    <p> </p>    <p>来自:http://www.cnblogs.com/ITer-up/p/6045201.html</p>    <p> </p>