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>


    签名:删除冗余的代码最开心,找不到删除的代码最痛苦!
  • 相关阅读:
    rs
    stm32f767 usoc3
    stm32f767 RTT 日志
    stm32f767 标准库 工程模板
    stm32f767 HAL 工程模板
    docker tab 补全 linux tab 补全
    docker anconda 依赖 下载 不了
    docker run 常用 指令
    linux scp 命令
    Dockerfile 常用参数说明
  • 原文地址:https://www.cnblogs.com/season2009/p/5613628.html
Copyright © 2011-2022 走看看