zoukankan      html  css  js  c++  java
  • 微信开发-回调模式

    前言

    越来越多的企业借助微信平台做开发,下面记录最近开发微信项目(企业号)一些关键设置、原理及代码

      一:添加应用

        关注企业微信号后,点击企业号,能看到该企业号下的应用列表,它类似我们常见后台中的模块或栏目,首先我们要创建自己的应用。

     二:设置应用为回调模式

          创建好应用后,把应用设置成回调模式,按要求设置回调URL及密钥。保存时它会访问URL,只有URL能正确访问信息时才能保存成功,否则一直会提示失败

      原理    

    以下摘自微信开发文档:

    验证URL有效性

    当你提交以上信息时,企业号将发送GET请求到填写的URL上,GET请求携带四个参数,企业在获取时需要做urldecode处理,否则会验证不成功。

    参数描述是否必带
    msg_signature 微信加密签名,msg_signature结合了企业填写的token、请求中的timestamp、nonce参数、加密的消息体
    timestamp 时间戳
    nonce 随机数
    echostr 加密的随机字符串,以msg_encrypt格式提供。需要解密并返回echostr明文,解密后有random、msg_len、msg、$CorpID四个字段,其中msg即为echostr明文 首次校验时必带

    企业通过参数msg_signature对请求进行校验,如果确认此次GET请求来自企业号,那么企业应用对echostr参数解密并原样返回echostr明文(不能加引号,不能带bom头,不能带换行符),则接入验证生效,回调模式才能开启。

          实操

    为了能正确验证URL有效性,还需要以下操作

    首先,我们需要一台服务器,并且有一个域名指向了该服务器,在该服务器上配置好网站。把验证URL的代码放置在该服务器上。wxpush代码如下:

    package com.bf.weixin;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.bf.meal.entity.App;
    import com.qq.weixin.mp.aes.AesException;
    import com.qq.weixin.mp.aes.WXBizMsgCrypt;
    
    public class wxpush extends HttpServlet {
    
        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
    
            WXBizMsgCrypt wxcpt = null;
            try {
                wxcpt = new WXBizMsgCrypt(App.getToken(), App.getEncodingAESKey(), App.getCorpID());
            } catch (AesException e1) {
                e1.printStackTrace();
            }
            
            // 解析出url上的参数值如下:
            String sVerifyMsgSig = request.getParameter("msg_signature");
            String sVerifyTimeStamp = request.getParameter("timestamp");
            String sVerifyNonce = request.getParameter("nonce"); 
            String sVerifyEchoStr = request.getParameter("echostr");
            App.logger.info("url:" + request.getQueryString());
            
            String sEchoStr; //需要返回的明文
            
            try {
                sEchoStr = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce, sVerifyEchoStr);
                App.logger.info("verifyurl echostr: " + sEchoStr);
                
                response.setContentType("text/html");
                PrintWriter out = response.getWriter();
                out.println(sEchoStr);
                out.flush();
                out.close();
            } catch (Exception e) {
                App.logger.error(e);
                e.printStackTrace();
            }
        }
    }
    View Code

    注:需要到微信官网上下载

    com.qq.weixin.mp.aes.AesException;

    com.qq.weixin.mp.aes.WXBizMsgCrypt;

    当然web.xml中还得有如下节点

      <servlet-mapping>
            <servlet-name>wxpush</servlet-name>
            <url-pattern>/servlet/wxpush</url-pattern>
        </servlet-mapping>

    配置好后,应该能正确的访问URL(http://*.*.com.cn/meal/servlet/wxpush),把该URL作为微信后台填写的值,此时应该能正确保存了。

    三:获取用户帐号信息

    要针对企业号关注用户做一些应用,最重要一点是要识别该用户,以便能正确区分不同的用户。

    根据文档OAuth2.0验证接口说明, 建议的方案

    1、企业应用中的URL链接直接填写企业自己的页面地址

    2、成员跳转到企业页面时,企业校验是否有代表成员身份的cookie,此cookie由企业生成

    3、如果没有获取到cookie,重定向到OAuth验证链接,获取成员身份后,由企业生成代表成员身份的cookie

    4、根据cookie获取成员身份,进入相应的页面

    我的做法是,配置Filter拦截所有jsp或.do文件的请求,在Filter中先去Cookie,如果Cookie不存在,则调用OAuth2.0获取信息,具体还是看关键代码

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
            
            String userId = getUserId(request, response);
    ……

          

    private String getUserId( HttpServletRequest request, HttpServletResponse response) throws IOException{
            Cookie cookieUser = CookieHelper.getCookieByName(request, App.COOKIE_USER);
            String userId = "";
            
            if (cookieUser != null)
            {
                userId = cookieUser.getValue(); 
            }
            else
            {    
                String code = request.getParameter("code"); //企业code
                
                if (code == null) //为空时,需 构造OAuth链接,OAuth返回时会带上code
                {
                    String url = request.getRequestURL().toString();
                    url = OAuthHelper.OAuth(java.net.URLEncoder.encode(url, "UTF-8"));
                    response.sendRedirect(url);
                    return "";
                }
                
                try {
                    userId = OAuthHelper.getUserByCode(code);
                } catch (Exception e) {
                }
                
                if (userId == null)
                {
                    return "";
                }
                
                CookieHelper.addCookie(response, App.COOKIE_USER, userId, App.Cookie_Age); 
            }
                  
            return userId;
        }

    下面为封装的工具类,与项目无关

    public class OAuthHelper {
        static Logger logger = Logger.getLogger(OAuthHelper.class.getName());
        private static String Token_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s";
        private static String OAuth_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=ATTENDANCE#wechat_redirect";
        private static String GetUserInfoUrl = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s";
        
        public static String getUserByCode(String code) throws Exception{
            String userId = null;
            String token = getToken();
            
            String url = String.format(GetUserInfoUrl, token, code);
            OAuth oauthInfo = HttpHelper.GetOAuthInfo(url);
            
            if (oauthInfo != null)
            {
                userId = oauthInfo.getUserId();
            }
                    
            return userId;
        }
        
        public static String OAuth(String url){
             String fullUrl = String.format(OAuth_URL, App.getCorpID(), url);
             
             return fullUrl;
        }
        
        private static String getToken() throws Exception{
            String url = String.format(Token_URL, App.getCorpID(), App.getCorpSecret());
            
                return HttpHelper.GetTokenInfo(url).getAccessToken();
        }
    }
    View Code

    下面最好是用泛型消除重复代码

    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    
    public class HttpHelper {
       
        public static OAuth GetOAuthInfo(String url) throws IOException{
            CloseableHttpClient httpclient = HttpClients.createDefault();
            HttpGet httpget = new HttpGet(url);
    
            ResponseHandler<OAuth> rh = new ResponseHandler<OAuth>() {
                @Override
                public OAuth handleResponse(final HttpResponse response) throws IOException {
                    StatusLine statusLine = response.getStatusLine();
                    HttpEntity entity = response.getEntity();
                    
                    if (statusLine.getStatusCode() >= 300) {
                        throw new HttpResponseException(
                                statusLine.getStatusCode(),
                                statusLine.getReasonPhrase());
                    }
                    
                    if (entity == null) {
                        throw new ClientProtocolException("Response contains no content");
                    }
                    
                    Gson gson = new GsonBuilder().create();
                    ContentType contentType = ContentType.getOrDefault(entity);
                    Reader reader = new InputStreamReader(entity.getContent(), contentType.getCharset());
                    return gson.fromJson(reader, OAuth.class);
                }
            };
            
            OAuth myjson = httpclient.execute(httpget, rh);
            return myjson;
        }
        
        public static WeChartToken GetTokenInfo(String url) throws Exception{
            SslHelper.ignoreSsl();
            CloseableHttpClient httpclient = HttpClients.createDefault();
            
            HttpGet httpget = new HttpGet(url);
    
            ResponseHandler<WeChartToken> rh = new ResponseHandler<WeChartToken>() {
                @Override
                public WeChartToken handleResponse(final HttpResponse response) throws IOException {
                    StatusLine statusLine = response.getStatusLine();
                    HttpEntity entity = response.getEntity();
                    
                    if (statusLine.getStatusCode() >= 300) {
                        throw new HttpResponseException(
                                statusLine.getStatusCode(),
                                statusLine.getReasonPhrase());
                    }
                    
                    if (entity == null) {
                        throw new ClientProtocolException("Response contains no content");
                    }
                    
                    Gson gson = new GsonBuilder().create();
                    ContentType contentType = ContentType.getOrDefault(entity);
                    Reader reader = new InputStreamReader(entity.getContent(), contentType.getCharset());
                    return gson.fromJson(reader, WeChartToken.class);
                }
            };
            
            WeChartToken myjson = httpclient.execute(httpget, rh);
            return myjson;
        }
    }
    View Code
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.X509TrustManager;
    
    public class SslHelper {
        private static void trustAllHttpsCertificates() throws Exception {
            TrustManager[] trustAllCerts = new TrustManager[1];
            TrustManager tm = new miTM();
            trustAllCerts[0] = tm;
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, null);
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        }
        
        static class miTM implements TrustManager,X509TrustManager {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            public boolean isServerTrusted(X509Certificate[] certs) {
                return true;
            }
            public boolean isClientTrusted(X509Certificate[] certs) {
                return true;
            }
            public void checkServerTrusted(X509Certificate[] certs, String authType)
                    throws CertificateException {
                return;
            }
            public void checkClientTrusted(X509Certificate[] certs, String authType)
                    throws CertificateException {
                return;
            }
        }
        
        /**
         * 忽略HTTPS请求的SSL证书,必须在openConnection之前调用
         * @throws Exception
         */
        public static void ignoreSsl() throws Exception{
            HostnameVerifier hv = new HostnameVerifier() {
                public boolean verify(String urlHostName, SSLSession session) {
                    System.out.println("Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost());
                    return true;
                }
            };
            trustAllHttpsCertificates();
            HttpsURLConnection.setDefaultHostnameVerifier(hv);
        }
    }
    View Code
    public class App {
        
        private static Properties config;
        
        private App()
        {}
        
        private static Properties getInstance(){
            if( config == null )
            {
                config = new Properties();
                
                try {
                    config.load(App.class.getClassLoader().getResourceAsStream("/config.properties"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            return config;
        }
        
        public static String getToken() {
            return getInstance().getProperty("Token");
        }
    
        public static String getCorpID() {
            return getInstance().getProperty("CORP_ID");
        }
        
        public static String getCorpSecret() {
            return getInstance().getProperty("CORP_SECRET");
        }
        
        public static String getEncodingAESKey() {
            return getInstance().getProperty("EncodingAESKey");
        }
        
        public static String getSERVICE_URL() {
            return getInstance().getProperty("SERVICE_URL");
        }
        
    }
    View Code

    最后是src/config.properties配置文件:

    Token = ***
    CORP_ID =  ***
    CORP_SECRET =  ***
    EncodingAESKey = ***

    以及实体类

    public class OAuth {
        private String UserId;
    
        public String getUserId() {
            return UserId;
        }
    
        public void setUserId(String userId) {
            UserId = userId;
        }
    }
    
    public class WeChartToken {
        private String access_token;
    
        public String getAccessToken() {
            return access_token;
        }
    
        public void setAccessToken(String access_token) {
            this.access_token = access_token;
        }
    }

     补充

      问题一:今天在做新的微信项目时,以上代码一直提示解密失败

      尝试获取参数方式为:

    String sVerifyMsgSig = URLDecoder.decode(request.getParameter("msg_signature"),"utf-8");
    String sVerifyTimeStamp = URLDecoder.decode(request.getParameter("timestamp"),"utf-8");
    String sVerifyNonce = URLDecoder.decode(request.getParameter("nonce"),"utf-8");
    String sVerifyEchoStr = URLDecoder.decode(request.getParameter("echostr"),"utf-8");
    

    本地显示解密成功后,上传到服务器提示解密失败。

    根据 http://blog.csdn.net/omsvip/article/details/40380465,去Oracle官网下载jce_policy-8.zip,把解压后得到的两个jar包,

    覆盖C:Program FilesJavajre1.8.0_65libsecurity目录下原有文件即可。

     问题二:redirect_uri参数错误

    加解密问题解决后,通过微信打开链接一直报"redirect_uri参数错误"。

    原因:没有设置好可信域名。

    注意:有两个地方可以设置可信域名,一个是在应用中心->自建应用,模式选择上面,还有一处是在设置->功能设置处,之所以出错是我在第二处设置了而非第一处。

    最后放出基于Spring MVC的web.xml

    <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
        version="2.5">
        
        <display-name>wim</display-name>
        <description>wim</description> 
        
        <filter>
            <filter-name>WeChartFilter</filter-name>
            <filter-class>com.XX.weixin.WeChartFilter</filter-class>
        </filter>
        
        <filter-mapping>
            <filter-name>WeChartFilter</filter-name>
            <url-pattern>*</url-pattern>   /*这里设置filter是要让每个页面访问之前,先去取微信用户ID*/
        </filter-mapping>
        
        <!-- For web context -->
        <servlet>
            <servlet-name>spring-dispatcher</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>/WEB-INF/spring-mvc-config.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>spring-dispatcher</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
        
        <servlet>
            <servlet-name>WeChatCallback</servlet-name>
            <servlet-class>com.XX.weixin.WeChatCallback</servlet-class>  /*回调地址设置*/
          </servlet>
    
        <servlet-mapping>
            <servlet-name>WeChatCallback</servlet-name>
            <url-pattern>/callback</url-pattern>
        </servlet-mapping>
        
        <!-- For root context -->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <listener>
            <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
        </listener>
        
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring-core-config.xml</param-value>
        </context-param>
    </web-app>


    签名:删除冗余的代码最开心,找不到删除的代码最痛苦!
  • 相关阅读:
    PrintWriter、PrintStream的苦头 缓冲区问题
    BufferedImage与byte[]互转
    求两个日期的间隔天数
    Timer和TimerTask详解
    Java连接Access数据库
    根据value字段对map进行排序
    java collections读书笔记(3)Arrays
    java collections读书笔记(4) stack
    运行时异常与一般异常有何异同?(转)
    java collections读书笔记(7) bitset
  • 原文地址:https://www.cnblogs.com/season2009/p/5613628.html
Copyright © 2011-2022 走看看