企業微信接入自有應用實現免登錄
阿新 • • 發佈:2019-03-06
expire 必須 初始 錯誤碼 end 單個 port 地理位置 on()
公司要將移動端審批流程接入企業微信,員工通過企業微信的自建審批應用就可以在微信端審批單據,要審批單據就先得讓企業微信跟用戶綁定起來,實現無感自動登錄系統內。由於企業的員工已經被手動的從微信拉入企業微信內,這樣就不可以通過企業微信的創建人員接口將人員推到微信內。只能在用戶第一次登錄審批系統的時候去微信獲取用戶ID(userId)並與審批系統的人員關聯起來。綁定好後就可以實現自動登錄。
企業微信API:https://work.weixin.qq.com/api/doc#90001/90143/91201
員工點擊應用後自動登錄步驟如下:
1.攔截用戶是否登錄
在過濾器或者攔截器內檢查用戶是否登錄,如果沒有登錄跳轉到微信獲取用戶的身份信息
企業微信API地址:https://work.weixin.qq.com/api/doc#90001/90143/91120
獲取企業微信授權信息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_redirec
頁面跳轉到企業微信後企業微信處理完成後,頁面將重新跳轉至 redirect_uri參數指定的回調地址並帶上參數code=CODE&state=STATE,企業可根據code參數獲得員工的userid。code長度最大為512字節。
public static final String OAUTH_CODE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=%s#wechat_redirect"; Integer serverPort = request.getServerPort(); String port = (null != serverPort && serverPort == 80) ? "" : ":" + serverPort.toString(); String url = "http://" + request.getServerName() // 服務器地址 + port // 端口號 + request.getContextPath(); // 項目名稱 url = url + "/weixin/auth/weixinLogin/oauthLogin.html"; try{ url = URLEncoder.encode(url, "utf-8"); }catch (UnsupportedEncodingException e){ e.printStackTrace(); } String weixinUrl = String.format(OAUTH_CODE_URL, "企業ID", url, "state""); response.sendRedirect(weixinUrl);
2.獲取用戶信息並登錄
微信回調接收到CODE值並用CODE去交換真實的用戶信息,如果用戶與系統綁定就表示登錄成功,登錄成功後將用戶信息放入SESSION中。這樣就可以實現自動登錄審批系統了。
/** * 微信回調接口 * @return */ public String oauthLogin(){ try{ Map<String, Object> returnMap = WeixinUtil.getUserInfo(code, Constants.APPROVAL); if (null != returnMap){ //得到微信用戶ID String userid = MapUtils.getString(returnMap, "UserId"); if (!StringUtil.isRealEmpty(userid)){ //用戶與微信ID綁定實體 UserWx userWx = userWxManager.findByWXID(userid); if (userWx != null){ if (StringUtil.isRealEmpty(userWx.getAvatar())){ WxUser wxUser = WeixinUtil.getWxUser(userid, Constants.APPROVAL); if (null != wxUser && !StringUtil.isRealEmpty(wxUser.getAvatar())){ // +64取小頭像 ;+0取大頭像 userWx.setAvatar(wxUser.getAvatar()); userWxManager.save(userWx); } } //登錄成功,將用戶信息放入SESSION等後續操作 ....... return null; }else{ //顯示未綁定的邏輯 return customMethod("unbind"); } }else{ return customMethod("error"); } }else{ return customMethod("error"); } }catch (Exception e){ return customMethod("error"); } }
WeixinUtil的代碼如下:
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; import java.net.URL; import java.sql.Timestamp; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import org.apache.commons.collections.MapUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.alibaba.fastjson.JSON; import cn.boxbank.WxUser; public class WeixinUtil { private static Log log = LogFactory.getLog(WeixinUtil.class); /** * 發起https請求並獲取結果 * * @param requestUrl 請求地址 * @param requestMethod 請求方式(GET、POST) * @param outputStr 提交的數據 * @return JSONObject(通過JSONObject.get(key)的方式獲取json對象的屬性值) */ public static Map<String, Object> HttpRequest(String requestUrl, String requestMethod, String outputStr) { // log.error("發起https請求並獲取結果 :"+requestUrl+","+requestMethod+","+outputStr); Map<String, Object> map = new HashMap<String, Object>(); StringBuffer buffer = new StringBuffer(); try { // 創建SSLContext對象,並使用我們指定的信任管理器初始化 TrustManager[] tm = { new EwX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 從上述SSLContext對象中得到SSLSocketFactory對象 SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); httpUrlConn.setUseCaches(false); httpUrlConn.setConnectTimeout(60 * 1000); httpUrlConn.setReadTimeout(60 * 1000); // 設置請求方式(GET/POST) httpUrlConn.setRequestMethod(requestMethod); httpUrlConn.connect(); // if ("GET".equalsIgnoreCase(requestMethod)) // 當有數據需要提交時 if (null != outputStr) { OutputStream outputStream = httpUrlConn.getOutputStream(); // 註意編碼格式,防止中文亂碼 outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } // 將返回的輸入流轉換成字符串 InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); // 釋放資源 inputStream.close(); inputStream = null; httpUrlConn.disconnect(); // 返回map // map = JsonUtil.json2Map(buffer.toString(), Map.class, String.class, Object.class); map = (Map<String, Object>) JSON.parseObject(buffer.toString(), Map.class); } catch (ConnectException ce) { System.out.println("Connection Timed Out......"); } catch (Exception e) { String result = String.format("Https Request Error:%s", e); System.out.println(result); } return map; } /** * 根據code調微信接口獲取USERID * * @param code * @return */ public static Map<String, Object> getUserInfo(String code, String agentFlag) { String OAUTH_GETUSERINFO_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s"; String url = String.format(OAUTH_GETUSERINFO_URL, getAccessToken(agentFlag), code); Map<String, Object> result = HttpRequest(url, "GET", null); return result; } /** * 從配置參數中獲取 * @param agentFlag * @return */ public static String getAccessToken(String agentFlag) { //這裏是獲取應用的ACCESS_TOKEN,可以將獲取ACCESS_TOKEN放到定時線程內,每2個小時去重新獲取一次。 //從數據庫或者緩存中拿出應用的ACCESS_TOKEN ...... } /** * 通過userid 從微信獲取用戶 * * @param userid * @return */ public static WxUser getWxUser(String userid, String agentFlag) { String GET_USER_URL = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s"; WxUser user = null; String url = String.format(Constants.GET_USER_URL, getAccessToken(agentFlag), userid); Map<String, Object> returnMap = HttpRequest(url, "GET", null); int errcode = MapUtils.getIntValue(returnMap, "errcode"); if (errcode == 0) { user = JSON.toJavaObject((JSON) JSON.toJSON(returnMap), WxUser.class); } return user; } /** * 調用微信接口獲取accessToken * * @param cropid 企業號ID * @param cropsecret 企業號管理組對應的secret * @return AccessToken */ public static void getAccessToken(String cropid, String cropsecret) { String ACCESS_TOKEN_URL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"; // 添加URL中的cropid和cropsecret的值 String url = String.format(ACCESS_TOKEN_URL, cropid, cropsecret); // 根據url通過https請求獲取accesstoken Map<String, Object> returnMap = WeixinUtil.HttpRequest(url, "GET", null); if (null != returnMap && returnMap.size() > 0) { // access_token String access_token = MapUtils.getString(returnMap, "access_token"); // accessToken有效時間expires_in:7200秒 Integer expires_in = MapUtils.getInteger(returnMap, "expires_in"); //可將取到的ACCESS_TOKEN存入數據庫或者緩存中。每2個小時更新一次 ........ } else { log.info("accesstoken獲取失敗"); } } /** * 微信接口返回代碼轉換信息 * * @param errorcode * @return */ public static String convertErrorCode2Msg(int errorcode) { String errmsg = ""; switch (errorcode) { case -1: errmsg = "系統繁忙 "; break; case 0: errmsg = "請求成功 "; break; case 40001: errmsg = "獲取access_token時Secret錯誤,或者access_token無效 "; break; case 40002: errmsg = "不合法的憑證類型"; break; case 40003: errmsg = "不合法的UserID"; break; case 40004: errmsg = "不合法的媒體文件類型 "; break; case 40005: errmsg = "不合法的文件類型 "; break; case 40006: errmsg = "不合法的文件大小"; break; case 40007: errmsg = "不合法的媒體文件id"; break; case 40008: errmsg = "不合法的消息類型 "; break; case 40013: errmsg = "不合法的corpid "; break; case 40014: errmsg = "不合法的access_token"; break; case 40015: errmsg = "不合法的菜單類型"; break; case 40016: errmsg = "不合法的按鈕個數"; break; case 40017: errmsg = "不合法的按鈕類型"; break; case 40018: errmsg = "不合法的按鈕名字長度"; break; case 40019: errmsg = "不合法的按鈕KEY長度"; break; case 40020: errmsg = "不合法的按鈕URL長度 "; break; case 40021: errmsg = "不合法的菜單版本號 "; break; case 40022: errmsg = "不合法的子菜單級數"; break; case 40023: errmsg = "不合法的子菜單按鈕個數"; break; case 40024: errmsg = "不合法的子菜單按鈕類型"; break; case 40025: errmsg = "不合法的子菜單按鈕名字長度"; break; case 40026: errmsg = "不合法的子菜單按鈕KEY長度"; break; case 40027: errmsg = "不合法的子菜單按鈕URL長度"; break; case 40028: errmsg = "不合法的自定義菜單使用員工"; break; case 40029: errmsg = "不合法的oauth_code"; break; case 40031: errmsg = "不合法的UserID列表"; break; case 40032: errmsg = "不合法的UserID列表長度"; break; case 40033: errmsg = "不合法的請求字符,不能包含\\uxxxx格式的字符 "; break; case 40035: errmsg = "不合法的參數 "; break; case 40038: errmsg = "不合法的請求格式 "; break; case 40039: errmsg = "不合法的URL長度"; break; case 40040: errmsg = "不合法的插件token"; break; case 40041: errmsg = "不合法的插件id"; break; case 40042: errmsg = "不合法的插件會話"; break; case 40048: errmsg = "url中包含不合法domain"; break; case 40054: errmsg = "不合法的子菜單url域名"; break; case 40055: errmsg = "不合法的按鈕url域名 "; break; case 40056: errmsg = "不合法的agentid"; break; case 40057: errmsg = "不合法的callbackurl"; break; case 40058: errmsg = "不合法的紅包參數 "; break; case 40059: errmsg = "不合法的上報地理位置標誌位 "; break; case 40060: errmsg = "設置上報地理位置標誌位時沒有設置callbackurl"; break; case 40061: errmsg = "設置應用頭像失敗"; break; case 40062: errmsg = "不合法的應用模式"; break; case 40063: errmsg = "紅包參數為空"; break; case 40064: errmsg = "管理組名字已存在"; break; case 40065: errmsg = "不合法的管理組名字長度"; break; case 40066: errmsg = "不合法的部門列表"; break; case 40067: errmsg = "標題長度不合法 "; break; case 40068: errmsg = "不合法的標簽ID"; break; case 40069: errmsg = "不合法的標簽ID列表"; break; case 40070: errmsg = "列表中所有標簽(用戶)ID都不合法 "; break; case 40071: errmsg = "不合法的標簽名字,標簽名字已經存在 "; break; case 40072: errmsg = "不合法的標簽名字長度"; break; case 40073: errmsg = "不合法的openid"; break; case 40074: errmsg = "news消息不支持指定為高保密消息"; break; case 41001: errmsg = "缺少access_token參數 "; break; case 41002: errmsg = "缺少corpid參數"; break; case 41003: errmsg = "缺少refresh_token參數"; break; case 41004: errmsg = "缺少secret參數"; break; case 41005: errmsg = "缺少多媒體文件數據"; break; case 41006: errmsg = "缺少media_id參數"; break; case 41007: errmsg = "缺少子菜單數據"; break; case 41008: errmsg = "缺少oauth code"; break; case 41009: errmsg = "缺少UserID"; break; case 41010: errmsg = "缺少url"; break; case 41011: errmsg = "缺少agentid"; break; case 41012: errmsg = "缺少應用頭像mediaid"; break; case 41013: errmsg = "缺少應用名字"; break; case 41014: errmsg = "缺少應用描述"; break; case 41015: errmsg = "缺少Content"; break; case 41016: errmsg = "缺少標題"; break; case 41017: errmsg = "缺少標簽ID"; break; case 41018: errmsg = "缺少標簽名字 "; break; case 42001: errmsg = "access_token超時 "; break; case 42002: errmsg = "refresh_token超時"; break; case 42003: errmsg = "oauth_code超時 "; break; case 42004: errmsg = "插件token超時"; break; case 43001: errmsg = "需要GET請求"; break; case 43002: errmsg = "需要POST請求"; break; case 43003: errmsg = "需要HTTPS"; break; case 43004: errmsg = "需要接收者關註"; break; case 43005: errmsg = "需要好友關系"; break; case 43006: errmsg = "需要訂閱"; break; case 43007: errmsg = "需要授權"; break; case 43008: errmsg = "需要支付授權"; break; case 43009: errmsg = "需要員工已關註"; break; case 43010: errmsg = "需要處於回調模式"; break; case 43011: errmsg = "需要企業授權"; break; case 44001: errmsg = "多媒體文件為空"; break; case 44002: errmsg = "POST的數據包為空"; break; case 44003: errmsg = "圖文消息內容為空"; break; case 44004: errmsg = "文本消息內容為空"; break; case 45001: errmsg = "多媒體文件大小超過限制"; break; case 45002: errmsg = "消息內容超過限制"; break; case 45003: errmsg = "標題字段超過限制"; break; case 45004: errmsg = "描述字段超過限制"; break; case 45005: errmsg = "鏈接字段超過限制"; break; case 45006: errmsg = "圖片鏈接字段超過限制"; break; case 45007: errmsg = "語音播放時間超過限制"; break; case 45008: errmsg = "圖文消息超過限制"; break; case 45009: errmsg = "接口調用超過限制"; break; case 45010: errmsg = "創建菜單個數超過限制"; break; case 45015: errmsg = "回復時間超過限制"; break; case 45016: errmsg = "系統分組,不允許修改"; break; case 45017: errmsg = "分組名字過長"; break; case 45018: errmsg = "分組數量超過上限"; break; case 45024: errmsg = "賬號數量超過上限"; break; case 46001: errmsg = "不存在媒體數據"; break; case 46002: errmsg = "不存在的菜單版本"; break; case 46003: errmsg = "不存在的菜單數據"; break; case 46004: errmsg = "不存在的員工"; break; case 47001: errmsg = "解析JSON/XML內容錯誤"; break; case 48002: errmsg = "Api禁用"; break; case 50001: errmsg = "redirect_uri未授權"; break; case 50002: errmsg = "員工不在權限範圍"; break; case 50003: errmsg = "應用已停用"; break; case 50004: errmsg = "員工狀態不正確(未關註狀態) "; break; case 50005: errmsg = "企業已禁用"; break; case 60001: errmsg = "部門長度不符合限制"; break; case 60002: errmsg = "部門層級深度超過限制"; break; case 60003: errmsg = "部門不存在"; break; case 60004: errmsg = "父親部門不存在"; break; case 60005: errmsg = "不允許刪除有成員的部門"; break; case 60006: errmsg = "不允許刪除有子部門的部門"; break; case 60007: errmsg = "不允許刪除根部門"; break; case 60008: errmsg = "部門名稱已存在"; break; case 60009: errmsg = "部門名稱含有非法字符"; break; case 60010: errmsg = "部門存在循環關系"; break; case 60011: errmsg = "管理員權限不足,(user/department/agent)無權限"; break; case 60012: errmsg = "不允許刪除默認應用"; break; case 60013: errmsg = "不允許關閉應用"; break; case 60014: errmsg = "不允許開啟應用"; break; case 60015: errmsg = "不允許修改默認應用可見範圍"; break; case 60016: errmsg = "不允許刪除存在成員的標簽"; break; case 60017: errmsg = "不允許設置企業"; break; case 60102: errmsg = "UserID已存在"; break; case 60103: errmsg = "手機號碼不合法"; break; case 60104: errmsg = "手機號碼已存在"; break; case 60105: errmsg = "郵箱不合法"; break; case 60106: errmsg = "郵箱已存在"; break; case 60107: errmsg = "微信號不合法"; break; case 60108: errmsg = "微信號已存在"; break; case 60109: errmsg = "QQ號已存在"; break; case 60110: errmsg = "部門個數超出限制"; break; case 60111: errmsg = "UserID不存在"; break; case 60112: errmsg = "成員姓名不合法"; break; case 60113: errmsg = "身份認證信息(微信號/手機/郵箱)不能同時為空 "; break; case 60114: errmsg = "性別不合法"; break; default: errmsg = "沒有此錯誤碼! "; break; } return errmsg; } }
WxUser類的結構如下,省get\set
public class WxUser { // 必填, 成員UserID。對應管理端的帳號,企業內必須唯一。不區分大小寫,長度為1~64個字節 private String userid; // 必填,成員名稱。長度為1~64個字節 private String name; // 必填,成員所屬部門id列表,不超過20個 private String[] department; private String deptId; // 職位信息。長度為0~64個字節 private String position; // 手機號碼。企業內必須唯一,mobile/weixinid/email三者不能同時為空 private String mobile; // 性別。1表示男性,2表示女性 private String gender; // 郵箱。長度為0~64個字節。企業內必須唯一 private String email; // 微信號。企業內必須唯一。(註意:是微信號,不是微信的名字) private String weixinid; // 成員頭像的mediaid,通過多媒體接口上傳圖片獲得的mediaid private String avatar_mediaid; private String avatar; private String status; }
企業微信接入自有應用實現免登錄