package com.ynxbd.wx.wxfactory; import com.ynxbd.common.bean.GMCUser; import com.ynxbd.common.bean.Patient; import com.ynxbd.common.bean.User; import com.ynxbd.common.helper.common.*; import com.ynxbd.common.helper.http.OkHttpHelper; import com.ynxbd.common.result.JsonResult; import com.ynxbd.common.result.JsonResultEnum; import com.ynxbd.common.result.Result; import com.ynxbd.common.service.GMCUserService; import com.ynxbd.common.service.PatientService; import com.ynxbd.wx.config.WeChatConfig; import com.ynxbd.wx.wxfactory.base.auth.models.AuthResultData; import com.ynxbd.wx.wxfactory.base.auth.models.AuthTokenData; import com.ynxbd.wx.wxfactory.base.auth.models.SnsOath2AccessToken; import com.ynxbd.wx.wxfactory.base.auth.models.SnsUserInfo; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.ehcache.Cache; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j public class WxAuthHelper { private static final int SESSION_MAX_INACTIVE_INTERVAL = 60 * 60; // session最大存活时间 1H private static final String P_PREFIX = "_@"; // 前缀 private static final String P_SUFFIX = ":"; // 后缀 private static final String AUTH_SESSION_ID_NAME = "SID"; private static final String GMC_ID = "GMCId"; private static final String OPENID = "openid"; public static String auth(HttpServletRequest request, HttpServletResponse response, boolean isUserInfo) { try { String code = request.getParameter("code"); String state = request.getParameter("state"); // base64 Map paramsMap = getParamsMap(request.getParameter("p")); log.info("[认证授权] code={}, state={}, paramsMap={}", code, state, JsonHelper.toJsonString(paramsMap)); state = state == null ? "" : URLDecoder.decode(Base64Helper.decode(state), "UTF-8"); String authSessionId = AesWxHelper.decode(paramsMap.get(AUTH_SESSION_ID_NAME)); log.info("[认证授权-解码] authSessionId={}, state={}", authSessionId, state); SnsOath2AccessToken snsToken = WxFactory.Base.OAuth().oauth2AccessToken(WeChatConfig.APP_ID, WeChatConfig.APP_SECRET, code); log.info("[认证授权]信息 snsToken={}", JsonHelper.toJsonString(snsToken)); if (snsToken != null) { String openid = snsToken.getOpenid(); String unionId = snsToken.getUnionid(); if (openid != null) { HttpSession session = request.getSession(); session.setMaxInactiveInterval(SESSION_MAX_INACTIVE_INTERVAL); session.setAttribute(OPENID, openid); WxCacheHelper.putOpenIdCacheToSessionIdCache(authSessionId, openid); Cache cache = WxCacheHelper.getUserCacheManager(); if (WeChatConfig.isDevUser(openid) || !cache.containsKey(openid)) { User user = new User(); user.setUnionId(unionId); user.setOpenid(openid); user.setIsSnapShotUser(snsToken.getIsSnapShotUser()); user.setPatientList(new PatientService().queryPatientList(openid, unionId, true)); if (isUserInfo) { SnsUserInfo snsUser = WxFactory.Base.OAuth().snsUserInfo(snsToken.getAccessToken(), openid, "zh_CN", 3); if (snsUser != null) { user.setCountry(snsUser.getCountry()); user.setAvatar(snsUser.getHeadImgUrl()); user.setNickName(snsUser.getNickname_emoji()); user.setProvince(snsUser.getProvince()); user.setGenderByInt(snsUser.getSex()); user.setCity(snsUser.getCity()); user.setLanguage(snsUser.getLanguage()); } } cache.put(openid, user); } else { if (isUserInfo) { User user = cache.get(openid); if (user != null && user.getAvatar() == null && user.getNickName() == null) { SnsUserInfo snsUser = WxFactory.Base.OAuth().snsUserInfo(snsToken.getAccessToken(), openid, "zh_CN", 3); if (snsUser != null) { user.setAvatar(snsUser.getHeadImgUrl()); user.setNickName(snsUser.getNickname_emoji()); user.setGenderByInt(snsUser.getSex()); } } } } } } if (WeChatConfig.HAS_HTTPS_BY_BASE_URL) { // 强制为https String httpsURL = URLHelper.URLToHttps(state); state = httpsURL == null ? "" : httpsURL; } if (state.contains(".html")) { // 网页授权配置 return state; } else { String baseUrl = WeChatConfig.getBaseURL(); if (baseUrl != null && state.contains(baseUrl)) { return state; } } } catch (Exception e) { ErrorHelper.println(e); log.error("[微信][获取重定向链接异常]{}", e.getMessage()); } return null; } public static Result isAuth(HttpServletRequest request, HttpServletResponse response) throws Exception { String token = request.getParameter("token"); // 前端缓存 String state = request.getParameter("state"); String isUserInfo = request.getParameter("isUserInfo"); String deState = URLDecoder.decode(Base64Helper.decode(state), "UTF-8"); HttpSession session = request.getSession(); session.setMaxInactiveInterval(SESSION_MAX_INACTIVE_INTERVAL); Object sessionOpenIdObj = session.getAttribute(OPENID); // 自身openid String sessionOpenId = sessionOpenIdObj == null ? null : sessionOpenIdObj.toString(); AuthTokenData authTokenData = new AuthTokenData(); String cacheTokenOpenId = authTokenData.decodeToken(token, WeChatConfig.APP_ID); log.warn("[授权is_auth] token={}, state={}, isUserInfo={}, deState={}", token, state, isUserInfo, deState); String authSessionId = null; if (WeChatConfig.IS_ENABLE_GMC) { // 开启医共体开关 & 是医共体主服务器 & 不是支付授权 try { if (WeChatConfig.IS_GMC_SERVER) { authSessionId = AesWxHelper.decode(request.getHeader(AUTH_SESSION_ID_NAME)); } else { // 请求转发 String serverDomain = WeChatConfig.getDomain(false, false); if (deState != null && serverDomain != null && !deState.contains(serverDomain)) { return Result.error("授权域名不匹配"); } return gmc_server_auth(request, session, state, isUserInfo, cacheTokenOpenId, token, authTokenData, sessionOpenId, authSessionId); } } catch (Exception e) { ErrorHelper.println(e); return Result.error(e); } } if (cacheTokenOpenId != null) { log.info("[微信token认证] token={}, cacheOpenId={}", token, cacheTokenOpenId); AuthResultData authResultData = getTokenData(cacheTokenOpenId, token, authTokenData); return Result.success(authResultData.toResultData()); } log.info("[微信认证]获取 openid={}, authSessionId={}", sessionOpenId, authSessionId); if (!ObjectUtils.isEmpty(authSessionId) && ObjectUtils.isEmpty(sessionOpenId)) { sessionOpenId = WxCacheHelper.findOpenIdBySessionIdCache(authSessionId); log.info("[微信AID认证]sessionOpenId={}", sessionOpenId); } AuthResultData authResultData = getCacheUserData(request, sessionOpenId, state, isUserInfo, authSessionId); if (authResultData.hasAuthUrl()) { return Result.success(authResultData.getAuthUrl()); } return Result.success(authResultData.toResultData()); } /** * 主体服务器认证 */ public static Result gmc_server_auth(HttpServletRequest request, HttpSession session, String state, String isUserInfo, String cacheTokenOpenId, String token, AuthTokenData tokenData, String sessionOpenId, String authSessionId) { String reqGMCDomain = WeChatConfig.getGMCAuthDomain(isHttpsWithProxy(request), true); // 医共体请求服务地址 // 自身认证 boolean hasTokenCache = !ObjectUtils.isEmpty(cacheTokenOpenId); String openId = hasTokenCache ? cacheTokenOpenId : sessionOpenId; if (ObjectUtils.isEmpty(openId)) { // 自身认证未完成 return Result.success(getAuthUrl(request, state, false, null, authSessionId)); } // log.info("cacheTokenOpenId={}, sessionOpenId={}", cacheTokenOpenId, sessionOpenId); // 判断是否有主体id Object sessionGmcOpenId = session.getAttribute(GMC_ID); String gmcOpenId = sessionGmcOpenId == null ? null : sessionGmcOpenId.toString(); if (gmcOpenId == null) { GMCUser gmcUser = new GMCUserService().queryInfoByOpenId(openId); if (gmcUser != null) { gmcOpenId = gmcUser.getGmcOpenId(); } } if (!ObjectUtils.isEmpty(gmcOpenId)) { if (sessionGmcOpenId == null) { session.setAttribute(GMC_ID, gmcOpenId); } // 查询返回数据 AuthResultData authResultData = null; if (hasTokenCache) { authResultData = getTokenData(openId, token, tokenData); } if (authResultData == null) { authResultData = getCacheUserData(request, sessionOpenId, state, isUserInfo, authSessionId); } if (authResultData.hasAuthUrl()) { // 授权链接 return Result.success(authResultData.getAuthUrl()); } // repeatPatients(reqGMCDomain, authResultData.getPatientList(), gmcOpenId); // 患者信息去重 return Result.success(authResultData.toResultData()); } String sessionId = session.getId(); log.info("[认证请求转发] [sessionId:{}]URL:[{}]", sessionId, reqGMCDomain); String resultJson = OkHttpHelper.postFormStr(reqGMCDomain + "wx_auth/is_auth", params -> { params.put("token", token); params.put("state", state); params.put("isUserInfo", "false"); }, headers -> { if (!ObjectUtils.isEmpty(sessionId)) { headers.add(AUTH_SESSION_ID_NAME, AesWxHelper.encode(WeChatConfig.APP_ID + ":" + sessionId)); } }); Result result = Result.dataToResult(resultJson, true); if (!result.isOK()) { return result; } String dataStr = result.toDataStr(); if (dataStr != null && !dataStr.contains("{")) { return Result.success(dataStr); } AuthResultData gmcAuthResultData = result.dataToBean(AuthResultData.class); // 医共体医生 String enGmcOpenId = gmcAuthResultData.getEnOpenId(); gmcOpenId = AesWxHelper.decode(enGmcOpenId); // 主体openid String gmcUUID = AesWxHelper.decode(gmcAuthResultData.getEnGmcUUID()); if (!ObjectUtils.isEmpty(gmcOpenId)) { // 授权主体完成认证 session.setAttribute(GMC_ID, gmcOpenId); boolean isOK = new GMCUserService().addInfo(sessionOpenId, gmcOpenId, null, gmcUUID); log.info("[认证联系]添加{} wxOpenId={}, gmcOpenId={}, gmcUUID={}", (isOK ? "成功" : "失败"), sessionOpenId, gmcOpenId, gmcUUID); AuthResultData authResultData = getCacheUserData(request, sessionOpenId, state, isUserInfo, authSessionId); authResultData.setEnGmcOpenId(enGmcOpenId); // 用于绑定传递数据,确保关系记录 return Result.success(authResultData.toResultData()); } return result; } /** * 从token获取数据 * * @param cacheOpenId token中的openid * @param token token * @param tokenData token解析出来的数据 * @return bean */ public static AuthResultData getTokenData(String cacheOpenId, String token, AuthTokenData tokenData) { if (cacheOpenId == null) { return null; } if (tokenData == null) { return null; } log.info("[微信token认证] token={}, cacheOpenId={}", token, cacheOpenId); User user = WxCacheHelper.getCacheUser(cacheOpenId); List patients; if (user == null) { patients = new PatientService().queryPatientList(cacheOpenId, null, true); Cache cache = WxCacheHelper.getUserCacheManager(); User addCache = new User(); addCache.setOpenid(cacheOpenId); addCache.setUnionId(tokenData.getUnionId()); addCache.setAvatar(tokenData.getAvatar()); addCache.setNickName(tokenData.getNickName()); addCache.setPatientList(patients); cache.put(cacheOpenId, addCache); } else { patients = user.getPatientList(); } AuthResultData authResultData = new AuthResultData(); authResultData.setDate(new Date()); authResultData.setOpenid(cacheOpenId); authResultData.setToken(token); authResultData.setEnOpenId(AesWxHelper.encode(cacheOpenId, true)); authResultData.setEnUnionId(AesWxHelper.encode(tokenData.getUnionId(), true)); authResultData.setNickName(tokenData.getNickName()); authResultData.setAvatar(tokenData.getAvatar()); authResultData.setPatientList(patients); authResultData.setEnParams(AesMicroHelper.encode(cacheOpenId)); authResultData.setEnGmcUUID(AesWxHelper.encode(CodeHelper.get32UUID())); return authResultData; } public static AuthResultData getCacheUserData(HttpServletRequest request, String openId, String state, String isUserInfo, String authSessionId) { log.info("[微信认证]openid={}", openId); AuthResultData authResultData = new AuthResultData(); boolean isFindUserInfo = ("true".equals(isUserInfo)); if (openId == null) { authResultData.setAuthUrl(getAuthUrl(request, state, isFindUserInfo, null, authSessionId)); return authResultData; } User user = WxCacheHelper.getCacheUser(openId); if (user == null) { authResultData.setAuthUrl(getAuthUrl(request, state, isFindUserInfo, null, authSessionId)); return authResultData; } else { if (ObjectUtils.isEmpty(openId)) { openId = user.getOpenid(); // sessionId认证openid补充 } } if (isFindUserInfo) { // 更换授权模式,需更新信息 if (user.getNickName() == null || user.getAvatar() == null) { authResultData.setAuthUrl(getAuthUrl(request, state, true, null, authSessionId)); return authResultData; } } authResultData.setDate(new Date()); authResultData.setOpenid(openId); authResultData.setToken(new AuthTokenData().createToken(WeChatConfig.APP_ID, openId, user.getUnionId(), user.getAvatar(), user.getNickName())); authResultData.setEnOpenId(AesWxHelper.encode(openId, true)); authResultData.setEnUnionId(AesWxHelper.encode(user.getUnionId(), true)); authResultData.setNickName(user.getNickName()); authResultData.setAvatar(user.getAvatar()); authResultData.setPatientList(user.getPatientList()); authResultData.setEnParams(AesMicroHelper.encode(openId)); authResultData.setEnGmcUUID(AesWxHelper.encode(CodeHelper.get32UUID())); return authResultData; } public List repeatPatients(String reqDomain, List patientList, String gmcOpenId) throws Exception { // 数据去重 JsonResult jsonResult = postForm(reqDomain + "patient/authGMCPatients", params -> { params.put("gmcOpenId", AesWxHelper.encode(gmcOpenId, true)); }, headers -> { headers.add("gmcOpenId", AesWxHelper.encode(gmcOpenId, true)); }); if (jsonResult.success()) { List gmcPatientList = jsonResult.dataMapGetNodeToList(Patient.class); // authResultData.setPatientList(patientList); } return patientList; } private static final String OAUTH_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + WeChatConfig.APP_ID + "&redirect_uri="; private static String getAuthUrl(HttpServletRequest request, String state, boolean isFindUserInfo, String UID, String SID) { // StringBuffer url = request.getRequestURL(); // String baseUrl = url.delete(url.length() - request.getRequestURI().length(), url.length()).append(request.getServletContext().getContextPath()).append("/").toString(); if (state == null) { return null; } String api = isFindUserInfo ? "u_auth" : "b_auth"; String scope = isFindUserInfo ? "snsapi_userinfo" : "snsapi_base"; String enUID = toURLParam(GMC_ID, AesWxHelper.encode(UID, true)); String enSID = toURLParam(AUTH_SESSION_ID_NAME, AesWxHelper.encode(SID, true)); String params = enSID + enUID; try { params = ObjectUtils.isEmpty(params) ? "" : URLEncoder.encode(params, "UTF-8"); log.warn("[认证链接参数] params={}", params); } catch (Exception e) { log.error(e.getMessage()); } state = OAUTH_URL + WeChatConfig.getBaseURL(WeChatConfig.HAS_HTTPS_BY_BASE_URL || isHttpsWithProxy(request)) + "wx_auth/" + api + "?p=" + params + "&response_type=code" + "&scope=" + scope + "&forcePopup=true" + "&state=" + state + // state位置固定 "#wechat_redirect"; log.warn("[认证授权链接]state={}", state); return Base64Helper.encode(state); } // 是否为https请求 public static boolean isHttpsWithProxy(HttpServletRequest request) { // 优先检查代理头(适用于反向代理场景) String forwardedProto = request.getHeader("X-Forwarded-Proto"); if (forwardedProto != null) { return "https".equalsIgnoreCase(forwardedProto); } // 未经过代理,直接检查原生请求 return request.isSecure(); } public static String toURLParam(String key, String val) { if (ObjectUtils.isEmpty(val)) return ""; return P_PREFIX + key + P_SUFFIX + val; } public static Map getParamsMap(String params) { Map paramsMap = new HashMap<>(); try { if (ObjectUtils.isEmpty(params)) { return paramsMap; } params = URLDecoder.decode(params, "UTF-8"); String[] paramsArr = params.split(P_PREFIX); String key, val; String[] keyValueArr; for (String paramItem : paramsArr) { if (paramItem.isEmpty()) { continue; } keyValueArr = paramItem.split(P_SUFFIX, 2); // 分割成最多两部分 if (keyValueArr.length == 2) { key = keyValueArr[0]; val = keyValueArr[1]; val = ParamHelper.filterParamNull(val, null); if (val != null) { paramsMap.put(key, val); } } } } catch (Exception e) { log.error(e.getMessage()); } return paramsMap; } public static JsonResult postForm(String url, OkHttpHelper.MapParams params, OkHttpHelper.Header header) { return OkHttpHelper.postForm(url, params, header, JsonResultEnum.SYS_MICRO); } // // 医共体开启 & 不是支付授权 // private static boolean isAuthGMC(boolean isPayOAuth) { // return WeChatConfig.IS_ENABLE_GMC && !isPayOAuth; // } // private static String getOAuthURL(HttpServletRequest request, boolean isPayOAuth) { // boolean isHttps = WeChatConfig.HAS_HTTPS_BY_BASE_URL || isHttpsWithProxy(request); // return isAuthGMC(isPayOAuth) // ? "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + WeChatConfig.GMC_AUTH_APP_ID + "&redirect_uri=" + WeChatConfig.getGMCAuthDomain(isHttps, true) // : "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + WeChatConfig.APP_ID + "&redirect_uri=" + WeChatConfig.getBaseURL(isHttps); // } // private static SnsOath2AccessToken getOath2AccessToken(String code, boolean isPayOAuth) { // if (isAuthGMC(isPayOAuth)) { // return WxFactory.Base.OAuth().oauth2AccessToken(WeChatConfig.GMC_AUTH_APP_ID, WeChatConfig.GMC_AUTH_APP_SECRET, code); // } // return WxFactory.Base.OAuth().oauth2AccessToken(WeChatConfig.APP_ID, WeChatConfig.APP_SECRET, code); // } // // 获取重定向链接 // private static String getAuthDomain(HttpServletRequest request, boolean isPayOAuth) { // if (isAuthGMC(isPayOAuth)) { // return WeChatConfig.getGMCAuthDomain(true, false); // 强制为https // } // return WeChatConfig.getDomain(WeChatConfig.HAS_HTTPS_BY_BASE_URL || isHttpsWithProxy(request)); // 配置中baseURL有"https"时优先级最高 ["@protocol=1"为https;"@protocol=0"为默认的http;] // } }