博主实际项目中应用场景为,扫描二维码或点击公众号底部菜单栏模块后跳转第三方指定功能模块,第三方系统中使用Filter过滤器进行权限认证,以扫码为例:获取当前扫码微信用户唯一标识,调用系统中相关的方法判断当前扫码用户是否与系统用户绑定,若已绑定则直接跳转功能模块页面,否则引导用户进行第三方系统登录操作,登录完成后将用户输入的系统用户名与用户唯一标识(openid)进行绑定,绑定后跳转功能模块页面,有朋友会问,跳转到功能模块后,前端怎么知道当前的用户对应的是系统的谁,其实选择有很多,可以在判断是否绑定或者登陆成功时,将用户第三方系统用户名存储到session、cookie亦或者直接拼接到顶部url中,此处博主选择的是cookie。
应注意:用户在微信内置浏览器中进行刷新或前端js执行window.reload()方法时,请求再次进入拦截器时无需在重新获取用户微信唯一标识(openid),博主的处理方法为 在请求第一次进入拦截器时,进行“绑定认证” 或者“登录”后,在request的session作用域中存储一个标识,该标识存在的意义是在session未过期的时间内,再次进入拦截器中时如果标识存在并且为true时直接doFilter()跳转。
点击修改按钮,弹出对话框
对话框中填写服务器域名,若使用Sunny-Ngrok填写自定义的域名即可
网页授权时需要的参数信息:微信公众号管理→测试号信息栏
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重定向时候,必须带此参数)
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获取用户信息等步骤,也必须从服务器发起。
接收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;
}
}
博主在编写过程中主要以项目中实际需求为主,逻辑和部分方法并不适用读者,若在阅读或者开发过程中有疑问或者问题欢迎交流,能实际的帮助到各位,博主心里是开心的。