微信公衆號如何授權登錄、獲取用戶信息(openid)

首先看一下實現的效果,在公衆號中,用戶進入你的應用之前,會彈出一個授權頁面,當用戶點擊確認後,你就可以獲取用戶的信息
在這裏插入圖片描述

首先訪問微信測試號登錄頁面,通過打開自己手機的微信,掃一掃登錄
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
在這裏插入圖片描述
進入到測試號頁面後,分別看到如下信息
【測試號信息】
appID:開發者ID,是公衆號開發識別碼,配合開發者密碼可以調用微信公衆號接口,如獲取微信暱稱等
appsecret:開發者密碼,是檢驗公衆號開發者身份的密碼,具有極高的安全性。切記不要把密碼交給第三方開發者或者編寫到代碼裏
在這裏插入圖片描述
【接口配置信息】
URL: 是開發者用來接收微信消息和事件的接口URL,要用域名不能用ip
Token:由開發者可以任意填寫,用作生成簽名(該Token會和接口URL中包含的Token進行比對,從而驗證安全性)
在這裏插入圖片描述

當你填完URL和Token點擊提交後,微信會訪問你填寫的URL,所以要在後臺寫一個servlet來處理這個請求

處理請求的Controller

@Controller
@RequestMapping("wechat")
public class WeiXinController {
	@RequestMapping(method = { RequestMethod.GET })
	public void doGet(HttpServletRequest request, HttpServletResponse response) {
		// 微信加密簽名
		String signature = request.getParameter("signature");
		// 時間戳
		String timestamp = request.getParameter("timestamp");
		// 隨機數
		String nonce = request.getParameter("nonce");
		// 隨機字符串
		String echostr = request.getParameter("echostr");
		// 通過檢驗signature對請求進行校驗,若校驗成功則原樣返回echostr,表示接入成功,否則接入失敗
		PrintWriter out = null;
		try {
			out = response.getWriter();
			if (SignUtil.checkSignature(signature, timestamp, nonce)) {
				log.debug("weixin get success....");
				out.print(echostr);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (out != null)
				out.close();
		}
	}
}

微信請求校驗工具類

/**
 * 微信請求校驗工具類
 */
public class SignUtil {
	// 與接口配置信息中的Token要一致
	private static String token = "mytoken";

	/**
	 * 驗證簽名
	 * @param signature
	 * @param timestamp
	 * @param nonce
	 * @return
	 */
	public static boolean checkSignature(String signature, String timestamp, String nonce) {
		String[] arr = new String[] { token, timestamp, nonce };
		// 將token、timestamp、nonce三個參數進行字典序排序
		Arrays.sort(arr);
		StringBuilder content = new StringBuilder();
		for (int i = 0; i < arr.length; i++) {
			content.append(arr[i]);
		}
		MessageDigest md = null;
		String tmpStr = null;
		try {
			md = MessageDigest.getInstance("SHA-1");
			// 將三個參數字符串拼接成一個字符串進行sha1加密
			byte[] digest = md.digest(content.toString().getBytes());
			tmpStr = byteToStr(digest);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		content = null;
		// 將sha1加密後的字符串可與signature對比,標識該請求來源於微信
		return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
	}
	/**
	 * 將字節數組轉換爲十六進制字符串
	 * @param byteArray
	 * @return
	 */
	private static String byteToStr(byte[] byteArray) {
		String strDigest = "";
		for (int i = 0; i < byteArray.length; i++) {
			strDigest += byteToHexStr(byteArray[i]);
		}
		return strDigest;
	}
	/**
	 * 將字節轉換爲十六進制字符串
	 * @param mByte
	 * @return
	 */
	private static String byteToHexStr(byte mByte) {
		char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
		char[] tempArr = new char[2];
		tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
		tempArr[1] = Digit[mByte & 0X0F];
		String s = new String(tempArr);
		return s;
	}
}

請求的處理程序寫完後把項目重新打包發佈到服務器上去,再提交你填寫的URL和Token,接口配置信息就Ok啦

【JS接口安全域名】
域名:想調用jssdk(如想要通過微信公衆號js接口獲取地圖等工具)必須得填寫此域名,在此域名的範圍內才能調用jssdk工具,注意這裏必須是域名,不是帶有http之類的URL
在這裏插入圖片描述
【測試號二維碼】
裏面包含了測試號二維碼以及已經關注了的用戶信息
在這裏插入圖片描述
【體驗接口權限表】
這裏主要介紹【網頁服務】裏面的【網頁帳號】
網頁帳號主要用來設置OAuth2.0裏面的網頁授權域名,用戶在網頁授權頁同意授權給公衆號後,微信會將授權數據傳給一個回調頁面,回調頁面需在此域名下,以確保安全可靠。沙盒號回調地址支持域名和ip,正式公衆號回調地址只支持域名。
在這裏插入圖片描述
在這裏插入圖片描述
接下來需要編寫自己的程序以獲取關注此公衆號的用戶信息
需要編寫5個類 WechatLoginController.java,UserAccessToken.java,WechatUser.java,WechatUtil.java以及MyX509TrustManager.java
【WechatLoginController】主要用來獲取已關注此微信號的用戶信息並做相應處理

@Controller
@RequestMapping("wechatlogin")
/**
 * 獲取關注公衆號之後的微信用戶信息的接口,如果在微信瀏覽器裏訪問
 * https://open.weixin.qq.com/connect/oauth2/authorize?appid=您的appId&redirect_uri=http://o2o.yitiaojieinfo.com/o2o/wechatlogin/logincheck&role_type=1&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect
 * 則這裏將會獲取到code,之後再可以通過code獲取到access_token 進而獲取到用戶信息
 */
public class WechatLoginController {

    private static Logger log = LoggerFactory.getLogger(WechatLoginController.class);

    @RequestMapping(value = "/logincheck", method = { RequestMethod.GET })
    public String doGet(HttpServletRequest request, HttpServletResponse response) {
        log.debug("weixin login get...");
        // 獲取微信公衆號傳輸過來的code,通過code可獲取access_token,進而獲取用戶信息
        String code = request.getParameter("code");
        // 這個state可以用來傳我們自定義的信息,方便程序調用,這裏也可以不用
        // String roleType = request.getParameter("state");
        log.debug("weixin login code:" + code);
        WechatUser user = null;
        String openId = null;
        if (null != code) {
            UserAccessToken token;
            try {
                // 通過code獲取access_token
                token = WechatUtil.getUserAccessToken(code);
                log.debug("weixin login token:" + token.toString());
                // 通過token獲取accessToken
                String accessToken = token.getAccessToken();
                // 通過token獲取openId
                openId = token.getOpenId();
                // 通過access_token和openId獲取用戶暱稱等信息
                user = WechatUtil.getUserInfo(accessToken, openId);
                log.debug("weixin login user:" + user.toString());
                request.getSession().setAttribute("openId", openId);
            } catch (IOException e) {
                log.error("error in getUserAccessToken or getUserInfo or findByOpenId: " + e.toString());
                e.printStackTrace();
            }
        }
        // ======todo begin======
        // 前面咱們獲取到openId後,可以通過它去數據庫判斷該微信帳號是否在我們網站裏有對應的帳號了,
        // 沒有的話這裏可以自動創建上,直接實現微信與咱們網站的無縫對接。
        // ======todo end======
        if (user != null) {
            // 獲取到微信驗證的信息後返回到指定的路由(需要自己設定)
            return "frontend/index";
        } else {
            return null;
        }
    }
}

【UserAccessToken】用戶AccessToken實體類,用來接收accesstoken以及openid等信息

/**
 * 用戶授權token
 *
 */
public class UserAccessToken {

    // 獲取到的憑證
    @JsonProperty("access_token")
    private String accessToken;
    // 憑證有效時間,單位:秒
    @JsonProperty("expires_in")
    private String expiresIn;
    // 表示更新令牌,用來獲取下一次的訪問令牌,這裏沒太大用處
    @JsonProperty("refresh_token")
    private String refreshToken;
    // 該用戶在此公衆號下的身份標識,對於此微信號具有唯一性
    @JsonProperty("openid")
    private String openId;
    // 表示權限範圍,這裏可省略
    @JsonProperty("scope")
    private String scope;

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public String getExpiresIn() {
        return expiresIn;
    }

    public void setExpiresIn(String expiresIn) {
        this.expiresIn = expiresIn;
    }

    public String getRefreshToken() {
        return refreshToken;
    }

    public void setRefreshToken(String refreshToken) {
        this.refreshToken = refreshToken;
    }

    public String getOpenId() {
        return openId;
    }

    public void setOpenId(String openId) {
        this.openId = openId;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    @Override
    public String toString() {
        return "accessToken:" + this.getAccessToken() + ",openId:" + this.getOpenId();
    }

}

【WechatUser】微信用戶實體類,用來接收暱稱 openid等用戶信息

/**
 * 微信用戶實體類
 */
public class WechatUser implements Serializable {
    private static final long serialVersionUID = -4684067645282292327L;
    // openId,標識該公衆號下面的該用戶的唯一Id
    @JsonProperty("openid")
    private String openId;
    // 用戶暱稱
    @JsonProperty("nickname")
    private String nickName;
    // 性別
    @JsonProperty("sex")
    private int sex;
    // 省份
    @JsonProperty("province")
    private String province;
    // 城市
    @JsonProperty("city")
    private String city;
    // 區
    @JsonProperty("country")
    private String country;
    // 頭像圖片地址
    @JsonProperty("headimgurl")
    private String headimgurl;
    // 語言
    @JsonProperty("language")
    private String language;
    // 用戶權限,這裏沒什麼作用
    @JsonProperty("privilege")
    private String[] privilege;
    public String getOpenId() {
        return openId;
    }
    public void setOpenId(String openId) {
        this.openId = openId;
    }

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getHeadimgurl() {
        return headimgurl;
    }

    public void setHeadimgurl(String headimgurl) {
        this.headimgurl = headimgurl;
    }

    public String getLanguage() {
        return language;
    }

    public void setLanguage(String language) {
        this.language = language;
    }

    public String[] getPrivilege() {
        return privilege;
    }

    public void setPrivilege(String[] privilege) {
        this.privilege = privilege;
    }

    @Override
    public String toString() {
        return "openId:" + this.getOpenId() + ",nikename:" + this.getNickName();
    }
}

【WechatUtil】主要用來提交https請求給微信獲取用戶信息

/**
 * 微信工具類
 */
public class WechatUtil {

    private static Logger log = LoggerFactory.getLogger(WechatUtil.class);

    /**
     * 獲取UserAccessToken實體類
     * @param code
     * @return
     * @throws IOException
     */
    public static UserAccessToken getUserAccessToken(String code) throws IOException {
        // 測試號信息裏的appId
        String appId = "您的appId";
        log.debug("appId:" + appId);
        // 測試號信息裏的appsecret
        String appsecret = "您的appsecret";
        log.debug("secret:" + appsecret);
        // 根據傳入的code,拼接出訪問微信定義好的接口的URL
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + appsecret
                + "&code=" + code + "&grant_type=authorization_code";
        // 向相應URL發送請求獲取token json字符串
        String tokenStr = httpsRequest(url, "GET", null);
        log.debug("userAccessToken:" + tokenStr);
        UserAccessToken token = new UserAccessToken();
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            // 將json字符串轉換成相應對象
            token = objectMapper.readValue(tokenStr, UserAccessToken.class);
        } catch (JsonParseException e) {
            log.error("獲取用戶accessToken失敗: " + e.getMessage());
            e.printStackTrace();
        } catch (JsonMappingException e) {
            log.error("獲取用戶accessToken失敗: " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            log.error("獲取用戶accessToken失敗: " + e.getMessage());
            e.printStackTrace();
        }
        if (token == null) {
            log.error("獲取用戶accessToken失敗。");
            return null;
        }
        return token;
    }

    /**
     * 獲取WechatUser實體類
     * @param accessToken
     * @param openId
     * @return
     */
    public static WechatUser getUserInfo(String accessToken, String openId) {
        // 根據傳入的accessToken以及openId拼接出訪問微信定義的端口並獲取用戶信息的URL
        String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId
                + "&lang=zh_CN";
        // 訪問該URL獲取用戶信息json 字符串
        String userStr = httpsRequest(url, "GET", null);
        log.debug("user info :" + userStr);
        WechatUser user = new WechatUser();
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            // 將json字符串轉換成相應對象
            user = objectMapper.readValue(userStr, WechatUser.class);
        } catch (JsonParseException e) {
            log.error("獲取用戶信息失敗: " + e.getMessage());
            e.printStackTrace();
        } catch (JsonMappingException e) {
            log.error("獲取用戶信息失敗: " + e.getMessage());
            e.printStackTrace();
        } catch (IOException e) {
            log.error("獲取用戶信息失敗: " + e.getMessage());
            e.printStackTrace();
        }
        if (user == null) {
            log.error("獲取用戶信息失敗。");
            return null;
        }
        return user;
    }

    /**
     * 發起https請求並獲取結果
     * @param requestUrl
     *            請求地址
     * @param requestMethod
     *            請求方式(GET、POST)
     * @param outputStr
     *            提交的數據
     * @return json字符串
     */
    public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
        StringBuffer buffer = new StringBuffer();
        try {
            // 創建SSLContext對象,並使用我們指定的信任管理器初始化
            TrustManager[] tm = { new MyX509TrustManager() };
            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);
            // 設置請求方式(GET/POST)
            httpUrlConn.setRequestMethod(requestMethod);

            if ("GET".equalsIgnoreCase(requestMethod))
                httpUrlConn.connect();

            // 當有數據需要提交時
            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();
            log.debug("https buffer:" + buffer.toString());
        } catch (ConnectException ce) {
            log.error("Weixin server connection timed out.");
        } catch (Exception e) {
            log.error("https request error:{}", e);
        }
        return buffer.toString();
    }
}

【MyX509TrustManager】主要繼承X509TrustManager做https證書信任管理器

/**
 * 證書信任管理器(用於https請求)
 * 
 */
public class MyX509TrustManager implements X509TrustManager {

    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
}

之後重新打包一個新的war包併發布到服務器tomcat webapps目錄下
發佈成功後,關注你自己的測試號(即掃描測試號的那個二維碼),然後在手機微信裏面或者微信開發者工具裏訪問相應鏈接:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=您的appId&redirect_uri=WechatLoginController對應的地址&role_type=1&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章