Java—企业微信网页版登陆认证详解

一)背景

本来自己系统已经有了企业微信网页版登陆认证功能,但功能是别的同事写的,而且已经离职了。刚好,有另一个项目组同事来请教,所有就把该认证功能单独抽离出来,新写了一个springboot项目给那同事,并且已经联调好了。

注意:该认证功能比较依赖企业微信的配置,所以就把大致代码讲解一下,如果有真实配置,直接把代码挪用就好。

 

二)企业微信网页版登陆认证(官网API oauth流程图)

 

三)创建一个springboot项目(项目创建就不说了)

结构图如下:

 

四)pom.xml

配置如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.oysept.qyweixin</groupId>
    <artifactId>oysept-qyweixin</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
  
    <!-- springboot父级jar -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath/>
    </parent>
  
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- json处理jar -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.49</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <!-- 打成jar时,需要指定一个启动类 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.oysept.qyweixin.ApplicationEntry</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

 

五)ApplicationEntry启动类

代码如下:

package com.oysept.qyweixin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 * @author ouyangjun
 */
@SpringBootApplication
public class ApplicationEntry {

    public static void main(String[] args) {
        SpringApplication.run(ApplicationEntry.class, args);
    }
}

 

六)application.properties配置文件

注意:qyweixin.corpid:可以从企业微信中获取,由于用的是公司真实的,所以就不贴上来了

           qyweixin.corpsecret:可以从企业微信中获取,由于用的是公司真实的,所以就不贴上来了

           qyweixin.redirect_uri:localhost使用时一定要换成真实的可信域名,不然访问的时候,微信会提示域名不可信

                                                可信域名需要在企业微信—>应用程序中新增配置

server.port=7788

qyweixin.corpid=
qyweixin.corpsecret=

qyweixin.redirect_uri=http://localhost:7788/qyweixin/oauth?url=http://localhost:7788/qyweixin/home

 

七)logback.xml配置内容如下(日志配置主要是为了更好监控异常)

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

    <!-- 在根目录下/app/qyweixin/logs生成日志文件 -->
    <property name="rootPath" value="/app/qyweixin/logs" />
    <property name="baseFile" value="qyweixin"></property>
    <property name="log.root.level" value="INFO"></property>

    <!-- 文件输出日志 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${rootPath}/${baseFile}.log</File>
        <!-- 日志文件rolling策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${rootPath}/${baseFile}_%d{yyyy-MM-dd}.log.gz
            </FileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>

        <!-- 日志输出格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>%X{logthreadId} [%date{yyyy-MM-dd HH:mm:ss.SSS}] %level %logger{36} %line - %msg%n
            </Pattern>
        </encoder>
    </appender>

    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>10000</queueSize>
        <appender-ref ref="FILE" />
    </appender>

    <root level="INFO">
        <appender-ref ref="ASYNC" />
    </root>
</configuration>

 

八)HttpInvoker工具类代码如下(主要用于http请求)

package com.oysept.qyweixin.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;

/**
 * http请求工具类
 * @author ouyangjun
 *
 */
public class HttpInvoker {
	
    private final static Logger LOGGER = LoggerFactory.getLogger(HttpInvoker.class);
	
    /**
     * 微信官方允许一天刷2000次 所以拟定2分钟刷新一次 24*60 / 2
     * @param sUrl
     * @param sMethod
     * @param sOutput
     * @return
     */
    public static JSONObject exec(String sUrl, String sMethod, String sOutput) {
        JSONObject json = null;
        StringBuffer buffer = new StringBuffer();

        HttpURLConnection con = null;
        try {
            URL url = new URL(sUrl);
            con = (HttpURLConnection) url.openConnection();
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setUseCaches(false);
            con.setRequestMethod(sMethod);
            // connection.setConnectTimeout(60000);

            con.setReadTimeout(60000);
            con.setConnectTimeout(60000);

            if (sOutput != null) {
                OutputStream os = con.getOutputStream();
                try {
                    os.write(sOutput.getBytes("UTF-8"));
                } catch (Exception e) {
                    LOGGER.info("HttpInvoker exec error: {}", e);
                } finally {
                    if (os != null) {
                        try {
                            os.close();
                        } catch (IOException e) {
                            LOGGER.info("HttpInvoker exec error: {}", e);
                        }
                    }
                }
            }

            InputStream is = null;
            InputStreamReader inputReader = null;
            BufferedReader reader = null;
            try {
                is = con.getInputStream();
                inputReader = new InputStreamReader(is, "UTF-8");
                reader = new BufferedReader(inputReader);
                String temp;
                while ((temp = reader.readLine()) != null) {
                    buffer.append(temp);
                }
            } catch (Exception e) {
                LOGGER.info("HttpInvoker exec error: {}", e);
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        LOGGER.info("HttpInvoker exec error: {}", e);
                    }
                }
                if (inputReader != null) {
                    try {
                        inputReader.close();
                    } catch (IOException e) {
                        LOGGER.info("HttpInvoker exec error: {}", e);
                    }
                }
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        LOGGER.info("HttpInvoker exec error: {}", e);
                    }
                }
            }

            // con.disconnect();
            json = JSONObject.parseObject(buffer.toString());
            if (json != null) {
                LOGGER.info("OK, http连接Url: {}, 返回数据,json: {}", sUrl, json);
            } else {
                LOGGER.info("return json is null, http连接Url: {}, 返回数据,json: {}", sUrl, json);
            }
        } catch (IOException e) {
            LOGGER.info("HttpInvoker exec error: {}", e);
        } finally {
            if (con != null) {
                con.disconnect();
            }
        }
        return json;
    }
}

 

九)WeChatAccessTokenUtils代码内容如下(主要用于获取access_token)

package com.oysept.qyweixin.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;

/**
 * 获取access_token工具类
 * @author ouyangjun
 *
 */
public class WeChatAccessTokenUtils {
	
    private final static Logger LOGGER = LoggerFactory.getLogger(WeChatAccessTokenUtils.class);
	
    /**
     * @param sCorpId 企业号的标识
     * @param sSecret 管理组凭证密钥
     * @return
     * @throws @description: 获取AccessToken
     * @author: ouyangjun
     * @date: 
     */
    public static String getAccessToken(String corpid, String corpsecret) {
        //获取路径,然后替换具体的参数
        String GET_ACCESS_TOKEN_URL = WeChatUtils.QY_WEIXIN_ACCESS_TOKEN;
        String requestUrl = GET_ACCESS_TOKEN_URL.replace("CORPID", corpid).replace("SECRECT", corpsecret);
        JSONObject json = HttpInvoker.exec(requestUrl, "GET", null);

        String token = null;
        if (json != null) {
            try {
                token = json.getString("access_token");

                // 打印消息
                String message = String.format("获取token成功  access token: %s, expire_in: %s", json.getString("access_token"),json.getInteger("expires_in"));
                LOGGER.info(message);
           } catch (Exception e) {
                String error = String.format("获取token失败    errcode: %s ,errmsg: %s", json.getInteger("errcode"),json.getString("errmsg"));
                LOGGER.info(error);
            }
        }
        return token;
    }
}

 

十)WeChatUtils代码内容如下(主要配置了企业微信一些请求url)

package com.oysept.qyweixin.utils;

/**
 * weixin url工具类
 * @author ouyangjun
 */
public class WeChatUtils {
	
    // access_token获取地址
    public final static String QY_WEIXIN_ACCESS_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=CORPID&corpsecret=SECRECT";
	
    // 根据appid获取code
    public final static String QY_WEIXIN_OAUTH_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&agentid=AGENTID&state=STATE#wechat_redirect";
	
    // 根据access_token和code获取用户基本信息
    public final static String QY_WEIXIN_USERINFO_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE";
	
}

 

十一)WeixinEntryController接口代码

package com.oysept.qyweixin.controller;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSONObject;
import com.oysept.qyweixin.utils.HttpInvoker;
import com.oysept.qyweixin.utils.WeChatAccessTokenUtils;
import com.oysept.qyweixin.utils.WeChatUtils;

/**
 * 由于微信请求需要转发,所以就不用@RestController
 * 请求地址: http://localhost:7788/qyweixin/entry
 *         http://127.0.0.1:7788/qyweixin/entry
 * @author ouyangjun
 */
@Controller
@RequestMapping(value = "/qyweixin")
public class WeixinEntryController {
	
    // 日志打印工具类
    private final static Logger LOGGER = LoggerFactory.getLogger(WeixinEntryController.class);
	
    // @Value注解是用于读取application.properties文件中的配置
    // 重定向地址
    @Value("${qyweixin.corpid:NaN}")
    private String corpid;
	
    // 重定向地址
    @Value("${qyweixin.corpsecret:NaN}")
    private String corpsecret;
		
    // 重定向地址
    @Value("${qyweixin.redirect_uri:NaN}")
    private String redirectURI;
	
    /**
     * 该系统首页测试接口
     * @return
     */
    @RequestMapping(value = "/home")
    @ResponseBody
    public String home(){
        return "oysept qyweixin home!";
    }

    /**
     * 微信入口,该地址可以配置在企业微信中,从企业微信中点击菜单访问该接口
     * 作用: 根据企业微信corpid得到获取code的url
     * @return
     */
    @RequestMapping(value = "/entry")
    public String weixinEntry(HttpServletRequest request, HttpServletResponse response){
        LOGGER.info("weixinEntry corpid: {}, redirectURI: {}, ", this.corpid, this.redirectURI);
		
        // 重定向的地址,需要是一个可以信任的域名,不然提示域名不可信
        String redirect_uri = "";
        try {
            // redirect_uri
            redirect_uri = URLEncoder.encode(this.redirectURI, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            LOGGER.info("weixinEntry redirect_uri error: {}", e);
        }
		
        // 微信认证地址,该地址会获取一个code,并且该地址只能在微信客户端中打开
        String oauthUrl = WeChatUtils.QY_WEIXIN_OAUTH_URL
                .replace("CORPID", this.corpid)
                .replace("REDIRECT_URI", redirect_uri);
        // 需要把该地址复制到微信客户端中打开链接, 微信会根据redirect_uri参数自动跳转地址
        LOGGER.info("weixinEntry oauthUrl: {}", oauthUrl);
		
        // 重定向,该地址一定要在微信客户端里才能打开
        return "redirect:" + oauthUrl;
    }
	
    /**
     * 用户信息认证登陆接口
     * @param url
     * @param code
     * @return
     */
    @RequestMapping(value = "/oauth")
    public String oauthWeiXin(@RequestParam(value="url") String url,
            @RequestParam(value="code") String code,
            HttpServletRequest request, HttpServletResponse response){
        LOGGER.info("oauthWeiXin corpid: {}, corpsecret: {}, ", this.corpid, this.corpsecret);
// 在url后面拼接参数,randomNumber是为了防止微信缓存
        if (!StringUtils.isEmpty(url)) {
            url += "?userId=USERID&randomNumber="+System.currentTimeMillis();
        }
        LOGGER.info("oauthWeiXin url: {}, code: {}", url, code);
		
        // 获取access_token json字符串
        String token = WeChatAccessTokenUtils.getAccessToken(this.corpid, this.corpsecret);
        LOGGER.info("oauthWeiXin token: {}", token);
		
        String userId = "";
        // 根据access_token和code获取用户基本信息
        if (!StringUtils.isEmpty(token)) {
			
            // 获取用户基本信息url,code只能用一次,一次之后就会失效
            String userinfo_url = WeChatUtils.QY_WEIXIN_USERINFO_URL
                    .replace("ACCESS_TOKEN", token)
                    .replace("CODE", code);
			
            // 获取用户信息
            JSONObject userJson = HttpInvoker.exec(userinfo_url, "GET", null);
            LOGGER.info("oauthWeiXin userJson: {}", userJson);
            if (!StringUtils.isEmpty(userJson)) {
                // 企业微信返回的json中有UserId参数
                userId = String.valueOf(userJson.get("UserId"));
				
                // 该判断是为了兼容,容错
                if (userId == null && userJson.containsKey("userId")) {
                    userId = userJson.getString("userId");
                }
                LOGGER.info("oauthWeiXin userId: {}", userId);
            }
        }
		
        // 用户ID用Base64编码,是为了防止用户中出现的特殊字符
        String redirect_uri = url.replace("USERID", Base64.encodeBase64String(userId.getBytes()));
        LOGGER.info("oauthWeiXin redirect_uri: {}", redirect_uri);
        // 重定向首页地址
        return "redirect:" + redirect_uri;
    }
}

 

十二、具体访问方式

方式一:如果直接请求http://localhost:7788/qyweixin/entry地址,浏览器会提示“请在微信客户端打开”。

              所以需要把该地址复制微信中打开。

               在微信中打开成功之后,微信可能会提示“redirect_uri域名不可信”,该原因是因为域名为在企业微信配置。

              如果都OK,参数中url就是认证成功需要跳转的页面。

              注意: 通过本地调试只能看流程走向,具体还需要真实的配置来支撑。

方式二:把WeChatUtils中QY_WEIXIN_OAUTH_URL地址替换成真实的直接访问,也会遇到“redirect_uri域名不可信”。

              注意:由于根据该地址,会返回一个code,这个code是微信自动生成,并且该code使用一次就会失效。

备注:由于企业微信限制太多,我是用了真实配置测试过代码的,有真实配置的,可以直接使用代码。

 

源码下载地址: https://gitee.com/ouyangjun_xm/java/attach_files下oysept-qyweixin.rar压缩包

                      码云账户: oyj812438109@163.com     密码: oyj812438109@163.com

                      请勿恶意操作,谢谢!

 

识别二维码关注个人微信公众号

本章完结,待续,欢迎转载!
 
本文说明:该文章属于原创,如需转载,请标明文章转载来源!

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值