微信 网页授权/第三方平台账户绑定/微信openid获取

 

前言

博主实际项目中应用场景为,扫描二维码或点击公众号底部菜单栏模块后跳转第三方指定功能模块,第三方系统中使用Filter过滤器进行权限认证,以扫码为例:获取当前扫码微信用户唯一标识,调用系统中相关的方法判断当前扫码用户是否与系统用户绑定,若已绑定则直接跳转功能模块页面,否则引导用户进行第三方系统登录操作,登录完成后将用户输入的系统用户名与用户唯一标识(openid)进行绑定,绑定后跳转功能模块页面,有朋友会问,跳转到功能模块后,前端怎么知道当前的用户对应的是系统的谁,其实选择有很多,可以在判断是否绑定或者登陆成功时,将用户第三方系统用户名存储到session、cookie亦或者直接拼接到顶部url中,此处博主选择的是cookie。

应注意:用户在微信内置浏览器中进行刷新或前端js执行window.reload()方法时请求再次进入拦截器时无需在重新获取用户微信唯一标识(openid),博主的处理方法为 在请求第一次进入拦截器时,进行“绑定认证” 或者“登录”后,在request的session作用域中存储一个标识,该标识存在的意义是在session未过期的时间内,再次进入拦截器中时如果标识存在并且为true时直接doFilter()跳转。

准备阶段:

  1. 测试公众号申请地址:  申请地址
  2. 微信公众平台技术文档:技术文档
  3. 微信web开发者工具:   下载地址
  4. 可被外网访问的服务器,为方便调试也可使用Sunny-Ngrok的内网穿透服务,将本地tomcat项目发布到外网环境,本文只提供官网地址,如需要可自行百度具体用法。

实施阶段:

  • 微信公众号: 因使用微信网页授权功能,所以需在测试公众号中 网页服务→网页账号中进行相关配置

微信 网页授权/第三方平台账户绑定/微信openid获取_第1张图片

 点击修改按钮,弹出对话框

微信 网页授权/第三方平台账户绑定/微信openid获取_第2张图片

 

对话框中填写服务器域名,若使用Sunny-Ngrok填写自定义的域名即可

微信 网页授权/第三方平台账户绑定/微信openid获取_第3张图片

网页授权时需要的参数信息:微信公众号管理→测试号信息栏

微信 网页授权/第三方平台账户绑定/微信openid获取_第4张图片

  • 网页授权 
  1. 静默授权:以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面);
  2. 手动授权:以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息
  • 授权流程
  1. 引导用户进入授权页面同意授权,获取code
  2. 通过code换取网页授权access_token(与基础支持中的access_token不同)
  3. 如果需要,开发者可以刷新网页授权access_token,避免过期
  4. 通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)
  • 操作步骤
  1. 请求微信code url:
    https://open.weixin.qq.com/connect/oauth2/authorize
    ?appid=公众号信息中的APPID
    &redirect_uri=授权后重定向的回调链接地址,urlEncode 对链接进行处理(注意重定向的回调地址需和公众号配置中的域名一致,简而言之重定向地址与配置的域名应为同一服务器下)
    &response_type=code(返回类型,请填写code)
    &scope=snsapi_base(此处区分授权方式:snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 ))
    &state=123(重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节,可用于传递参数)
    #wechat_redirect(无论直接打开还是做页面302重定向时候,必须带此参数)
  2. 接收code:用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE,直接在重定向的方法或拦截器中使用request.getParameter("code")获取即可。
    注意:code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟内未使用则自动过期;
  3. 请求微信openid url:
    https://api.weixin.qq.com/sns/oauth2/access_token
    ?appid=公众号信息中的APPID
    &secret=公众号的appsecret
    &code=填写第一步获取的code参数
    &grant_type=authorization_code

    注意:由于公众号的secret和获取到的access_token(微信服务器返回的code)安全级别都非常高,必须只保存在服务器,不允许传给客户端。后续刷新access_token、通过access_token获取用户信息等步骤,也必须从服务器发起。

  4. 接收openid,获取openid时服务器会返回多个参数,只需关注openid即可,openid即为微信用户的唯一标识,获取到微信openid完全可支撑“第三方平台绑定及相关功能”;

应用阶段

以博主项目代码为例


import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLEncoder;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


/**
 * 微信绑定: 
 *    1.引导用户触发绑定功能(微信扫一扫、公众号点击菜单进入、聊天对话框点击url进入)
 *    	请求被过滤器拦截后 首先判断微信code是否为空,如果为空则自动重定向到微信服务器获取微信code;
 *    2.获取当前用户微信唯一标识:
 *    ----------------------获取code----------------------------
 *    	(1)用户触发绑定将请求发送到微信服务器
 *      (2)微信服务器接收请求生成获取用户微信唯一标识的code并回调给本地服务器
 *    ----------------------获取openid--------------------------
 *      (3)获取微信返回的code发起请求将code传递到微信服务器
 *      (4)获取微信返回的openid
 *    3.判断该用户是否绑定(openid) 调用ehr中对应方法,若已绑定则直接跳转到第6步
 *    4.跳转登录页面,引导用户完成登录操作
 *    5.登录成功后获取当前登录人员万信号,调用ehr中对应方法进行绑定(传递:万信号、微信openid)。
 *    6.跳转初始页面
 */
public class CheckLoginFilter implements Filter {


	/**
	 * 公众号信息中的 appID 及 appsecret
	 */
	static final String APPID = "";
	static final String SECRET = "";


	/**
	 * filter入口
	 */
	@SuppressWarnings("static-access")
	public void doFilter(ServletRequest servletRequest,
						 ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
		
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		
		//设置是否登录 标识为 false
		boolean weixinFlag = false;
		
		//session不等于空 则 从域中获取weixinLogin标识
		if (request.getSession(false) != null) {
			HttpSession session = request.getSession(false);
			Boolean flag = (Boolean) session.getAttribute("weixinLogin");
			weixinFlag = flag != null ? flag : false;
		}
		//发起获取code请求 微信服务器重定向后 可直接获取code 
		String code = request.getParameter("code");
		if (code != null) {
			System.out.println("---------------------微信code:" + code);
		}
		
		//当用户执行登录状态后重定向 可直接获取openid 
		String openID = request.getParameter("openId");
		
		JSONObject jsonObj;
		
		//获取当前请求地址 
		String requestURL = request.getRequestURL().toString();
		//获取当前请求的参数串 只对get请求生效 
		String queryString = request.getQueryString()!=null?request.getQueryString():"";

		//对ajax请求单独处理,返回公众号信息中的APPID 这样前端就可以触发获取微信code操作 
		if(ajaxDofilterSessionNull(request, response)){
			//返回isLogin的意义在于 当request中的session失效后,前端则可直接触发重新认证操作 
			response.setHeader("isLogin",String.valueOf(weixinFlag));
			response.setHeader("APPID", APPID);
		} else if (weixinFlag) {
			//session生效时间内,登录过系统,则weixinFlag标识为true(session存活时间未指定 默认30分钟) 
			filterChain.doFilter(request, response);
		}
		
		//接收返回结果 
		String result;
		
		try {
				// 当前openid为空时 进行获取code的操作 
				if (openID == null) {
					
					//判断request请求来源是否为微信浏览器
					boolean isWeChat = isWeChat(request);
					
					//通过response重定向微信服 获取微信code 
					if (code == null && isWeChat) {
						//拼接回调路径 
						String url = requestURL + "?" + queryString;
						/*将请求发送到微信服务器,获得code*/
						String redirectURL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + APPID + "&redirect_uri=" + URLTool.encodeURL(url) + "&response_type=code&scope=snsapi_base#wechat_redirect";
						System.out.println("重定向微信服务器地址:" + redirectURL);
						response.sendRedirect(redirectURL);
						response.getWriter().close();
						return;
					}
					
					// 当前openid为空 code不为空时 进行获取微信openid操作
					if (code != null && !code.isEmpty()) {
						
						//拼接获取openid的请求地址
						String URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
						URL = URL.replace("APPID", APPID).replace("SECRET", SECRET).replace("CODE", code);
						
						//发起get请求 如果未正常返回,自动重新请求三次 若还未成功则返回null
						result = getUrl(URL, 0);
						
						//请求正常返回
						if (result != null) {
							System.out.println("result返回结果:" + result);
							jsonObj = new JSONObject(result);
							if (jsonObj != null && jsonObj.has("openid")) {
								System.out.println(" 获取微信openid ");
								//获取微信openid 
								openID = jsonObj.getString("openid");
							} else if (jsonObj != null && jsonObj.has("errcode")) {
								//code失效 - 重新获取-拿到新的code - 获取微信openid 
								String url = requestURL + "?" + queryString;
								String redirectURL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + APPID + "&redirect_uri=" + URLTool.encodeURL(url) + "&response_type=code&scope=snsapi_base#wechat_redirect";
								response.sendRedirect(redirectURL);
								response.getWriter().close();
								return;
							}
						} else {
							//重连三次后若继续链接失败 则引导用户使用原始登录方式进行验证 
							
							/* 自行补充相关代码*/
							filterChain.doFilter(request, response);
						}
					}
				}
			
				//openid不等于空 进行绑定验证
				if (openID != null && !openID.isEmpty()) {
					//验证用户是否绑定  自行补充相关代码
					String isExist = jsonObj.get("success").toString();
					
					//未绑定
					if (isExist.equals("false")) {
						//未绑定 调用系统绑定方法 ,须传递当前扫码人员的OPENID 
						
						//用户登录完成后 (博主项目中 登录模块多个系统共用,在登录完成后自动回调到相关业务页面)
						
						/*登录相关操作 自行补充*/
						
						//以获取当前登录人的username(用户名)
						String username = (String) request.getAttribute("username");
						if (username != null && !username.isEmpty()) {
							
							/*调用绑定方法 博主是将openid 及 username存储了起来  代码自行补充*/
							String isSave = jsonObj.get("success").toString();
							
							//判断是否绑定成功
							if (isSave.equals("true")) {
								filterChain.doFilter(request, response);
							}
						}
					} else if (isExist.equals("true")) {
						/*已绑定 则根据 openid 查询对应 系统中用户名 代码自行补充*/
						
						//获取用户名
						String name = jsonObj.getString("username").toString();
						
						//设置登录标识 保存到request的session域中
						HttpSession session = request.getSession();
						session.setAttribute("weixinLogin", true);
						
						try {
							//将用户名存储到cookie中 供前端获取
							CookiesUtil cookiesUtil = new CookiesUtil(request, response);
							cookiesUtil.set("", name.toLowerCase(), -1);
						} catch (Exception e) {
							e.printStackTrace();
						}
						
						filterChain.doFilter(request, response);
					}
				} else {
					//网页客户端  自行补充相关登录验证代码
					filterChain.doFilter(request, response);
				}
		} catch (JSONException e) {
			e.printStackTrace();
		}
	}

	public void destroy() {
	}

	public void init(FilterConfig filterConfig) throws ServletException {
	}

	/**
	 * @Author 狂野男孩
	 * @Description: TODO 判断是否为微信浏览器
	 * @param request
	 * @Date 14:10 2019-9-10
	 * @return boolean
	 **/
	private static boolean isWeChat(HttpServletRequest request) {
		//判断 是否是微信浏览器
		String userAgent = request.getHeader("user-agent").toLowerCase();
		if (userAgent.indexOf("micromessenger") > -1) {//微信客户端
			return true;
		} else {
			return false;
		}
	}

	/**
	 * @Author 狂野男孩
	 * @Description: TODO 判断是否是ajax请求
	 * @param request
	 * @param response
	 * @Date 14:10 2019-9-10
	 * @return boolean
	 **/
	private static boolean ajaxDofilterSessionNull(HttpServletRequest request, HttpServletResponse response) {
		boolean isAjax = false;
		if (request.getHeader("x-requested-with") != null && request.getHeader("x-requested-with").equals("XMLHttpRequest")) {
			// ajax请求
			isAjax = true;
		}
		return isAjax;
	}
	
	/**
     * 调用对方接口方法
     * @param path 对方或第三方提供的路径
     * @param data 向对方或第三方发送的数据,大多数情况下给对方发送JSON数据让对方解析
     */
    public static String getUrl(String path,int requestNumber) {
    	//连接超时后 重连次数
    	if(requestNumber<=3){
    		requestNumber++;
    		try {
                URL url = new URL(path);
                //打开和url之间的连接
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");//GET和POST必须全大写
                conn.setConnectTimeout(3000); // 3秒 连接主机的超时时间(单位:毫秒)
                conn.setReadTimeout(3000);
                conn.connect(); 
                //获取URLConnection对象对应的输入流
                InputStream is = conn.getInputStream();
                //获取响应
                BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                String line;
                StringBuffer sb = new StringBuffer(""); 
                while ((line = reader.readLine()) != null){
                	sb.append(line);  
                }
                reader.close();
                is.close();
                conn.disconnect();
                return sb.toString();
            }catch(ConnectException e){
            	System.out.println("链接超时异常-自动重连"+requestNumber+"次");
            	getUrl(path, requestNumber);
            }catch (SocketTimeoutException e) {
            	System.out.println("请求超时异常-自动重连"+requestNumber+"次");
            	getUrl(path, requestNumber);
    		}catch (Exception e) {
                e.printStackTrace();
            }
    	}
		return null;
    }
}

后记

博主在编写过程中主要以项目中实际需求为主,逻辑和部分方法并不适用读者,若在阅读或者开发过程中有疑问或者问题欢迎交流,能实际的帮助到各位,博主心里是开心的。

 

 

你可能感兴趣的:(微信,微信,微信授权,第三方授权,微信公众号,微信用户唯一标识)