1. 程式人生 > >Java微信公眾平臺開發之OAuth2.0網頁授權

Java微信公眾平臺開發之OAuth2.0網頁授權

col nts har 順序 pan getcount iba unionid syn

根據官方文檔點擊查看在微信公眾號請求用戶網頁授權之前,開發者需要先到公眾平臺官網中的“開發 - 接口權限 - 網頁服務 - 網頁帳號 - 網頁授權獲取用戶基本信息”的配置選項中,修改授權回調域名。請註意,這裏填寫的是域名(是一個字符串),而不是URL,因此請勿加 http:// 等協議頭,也不需要加具體的項目名,在域名空間的根目錄放一個txt文件才能驗證通過
一、兩種scope授權方式

  • 以snsapi_base為scope發起的網頁授權,是用來獲取進入頁面的用戶的openid的,並且是靜默授權並自動跳轉到回調頁的。用戶感知的就是直接進入了回調頁(往往是業務頁面)
  • 以snsapi_userinfo為scope發起的網頁授權,是用來獲取用戶的基本信息的。但這種授權需要用戶手動同意,並且由於用戶同意過,所以無須關註,就可在授權後獲取該用戶的基本信息

二、關於特殊場景下的靜默授權

  • 上面已經提到,對於以snsapi_base為scope的網頁授權,就靜默授權的,用戶無感知
  • 對於已關註公眾號的用戶,如果用戶從公眾號的會話或者自定義菜單進入本公眾號的網頁授權頁,即使是scope為snsapi_userinfo,也是靜默授權,用戶無感知

三、.用戶同意授權,獲取code

請求鏈接https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
package com.phil.wechatauth.model.req;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.TreeMap;

import com.phil.common.config.SystemConfig;
import com.phil.common.params.AbstractParams;
import com.phil.common.util.HttpReqUtil;

/**
 * 獲取授權code請求參數
 * 
 * @author phil
 * @date 2017年7月2日
 *
 */
public class AuthCodeParams extends AbstractParams implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	public static final String SCOPE_SNSAPIBASE = "snsapi_base"; // snsapi_base(不需要彈出授權頁面,只能獲取openid)
	public static final String SCOPE_SNSPAIUSERINFO = "snsapi_userinfo"; // 彈出授權頁面(獲取用戶基本信息)
	private String appid;
	private String redirect_uri;  // 使用urlencode對鏈接進行處理
	private String response_type = "code";
	private String scope;
	private String state;

	public AuthCodeParams() {
		super();
	}

	public AuthCodeParams(String appid, String redirect_uri, String response_type, String scope, String state) {
		super();
		this.appid = appid;
		this.redirect_uri = redirect_uri;
		this.response_type = response_type;
		this.scope = scope;
		this.state = state;
	}

	/**
	 * 參數組裝
	 * 
	 * @return
	 */
	public Map<String, String> getParams() throws UnsupportedEncodingException {
		Map<String, String> params = new TreeMap<String, String>();
		params.put("appid", this.appid);
		params.put("redirect_uri", HttpReqUtil.urlEncode(this.redirect_uri, SystemConfig.CHARACTER_ENCODING));
		params.put("response_type", this.response_type);
		params.put("scope", this.scope);
		params.put("state", this.state);
		return params;
	}

	public String getAppid() {
		return appid;
	}

	public void setAppid(String appid) {
		this.appid = appid;
	}

	public String getRedirect_uri() {
		return redirect_uri;
	}

	public void setRedirect_uri(String redirect_uri) {
		this.redirect_uri = redirect_uri;
	}

	public String getResponse_type() {
		return response_type;
	}

	public String getScope() {
		return scope;
	}

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

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}
}
組裝請求鏈接
/**
	 * 獲取授權請求path
	 * @param basic
	 * @param path
	 * @return
	 * @throws Exception
	 */
	public String getAuthPath(AbstractParams basic, String path) throws Exception{
		Map<String,String> params = basic.getParams();
		path = HttpRequestUtil.setParmas(params, path,"")+"#wechat_redirect";
		return path;
	}
尤其註意:由於授權操作安全等級較高,所以在發起授權請求時,微信會對授權鏈接做正則強匹配校驗,如果鏈接的參數順序不對,授權頁面將無法正常訪問 請在微信客戶端中打開此鏈接 如果用戶同意授權,頁面將跳轉至redirect_uri技術分享技術分享code=CODE&state=STATE ,可以用request.getParameter("code")直接獲取到code,在此之後檢驗state是否發生變化。code作為換取access_token的票據,每次用戶授權帶上的code將不一樣,code只能使用一次,5分鐘未被使用自動過期,可用redis、mc等緩存

四、通過code換取網頁授權access_token

這裏通過code換取的是一個特殊的網頁授權access_token,與基礎支持中的access_token(該access_token用於調用其他接口)不同。公眾號可通過下述接口來獲取網頁授權access_token。如果網頁授權的作用域為snsapi_base,則本步驟中獲取到網頁授權access_token的同時,也獲取到了openid,snsapi_base式的網頁授權流程即到此為止。

尤其註意:由於公眾號的secret和獲取到的access_token安全級別都非常高,必須只保存在服務器,不允許傳給客戶端。後續刷新access_token、通過access_token獲取用戶信息等步驟,也必須從服務器發起。

請求參數封裝

package com.phil.wechatauth.model.req;

import java.io.Serializable;
import java.util.Map;
import java.util.TreeMap;

import com.phil.common.params.AbstractParams;

/**
 * 獲取授權請求token的請求參數
 * @author phil
 * @date  2017年7月2日
 *
 */
public class AuthTokenParams extends AbstractParams implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = 4652953400751046159L;
	
	private String appid; //公眾號的唯一標識
	private String secret; //公眾號的appsecret
	private String code; //填寫第一步獲取的code參數
	private String grant_type = "authorization_code";

	public AuthTokenParams() {
		super();
	}

	public AuthTokenParams(String appid, String secret, String code, String grant_type) {
		super();
		this.appid = appid;
		this.secret = secret;
		this.code = code;
		this.grant_type = grant_type;
	}
	
	/**
	 * 參數組裝
	 * @return
	 */
	public Map<String, String> getParams() {
		Map<String, String> params = new TreeMap<String, String>();
		params.put("appid", this.appid);
		params.put("secret", this.secret);
		params.put("code", this.code);
		params.put("grant_type", this.grant_type);
		return params;
	}

	public String getAppid() {
		return appid;
	}

	public void setAppid(String appid) {
		this.appid = appid;
	}

	public String getSecret() {
		return secret;
	}

	public void setSecret(String secret) {
		this.secret = secret;
	}

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public String getGrant_type() {
		return grant_type;
	}
}

返回的json封裝

package com.phil.wechatauth.model.resp;

/**
 * 網頁授權access_token
 * 
 * @author phil
 * @date 2017年7月2日
 *
 */
public class AuthAccessToken extends AccessToken {

	private String refresh_token; // 用戶刷新access_token
	private String openid; // 用戶唯一標識,請註意,在未關註公眾號時,用戶訪問公眾號的網頁,也會產生一個用戶和公眾號唯一的OpenID
	private String scope; // 用戶授權的作用域,使用逗號(,)分隔

	public String getRefresh_token() {
		return refresh_token;
	}

	public void setRefresh_token(String refresh_token) {
		this.refresh_token = refresh_token;
	}

	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;
	}

}

獲取access_token方法

	/**
	 * 獲取網頁授權憑證
	 * @param basic
	 * @param path
	 * @return
	 */
	public AuthAccessToken getAuthAccessToken(AbstractParams basic, String path) {
		AuthAccessToken authAccessToken = null;
		//獲取網頁授權憑證
		try {
			String result = HttpReqUtil.HttpsDefaultExecute(HttpReqUtil.GET_METHOD, path, basic.getParams(), null);
			
			if(result != null){		
				authAccessToken = JsonUtil.fromJson(result, AuthAccessToken.class);
			}
		} catch (Exception e) {
			authAccessToken = null;
			logger.info("error"+e.getMessage());
		}
		return authAccessToken;
	}

獲取授權進入頁面

// 獲取token的鏈接
private final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";

@RequestMapping(value = "bindWxPhone", method = { RequestMethod.GET })
public String prize(HttpServletRequest request, HttpServletResponse response, String url) throws Exception {
	AuthAccessToken authAccessToken = null;
	// 用戶同意授權後可以獲得code,檢驗state
	String code = request.getParameter("code");
	if(code==null){
		return null;
	}
	String state = request.getParameter("state");
	if(state.equals(MD5Util.MD5Encode("ceshi", ""))){
		AuthTokenParams authTokenParams = new AuthTokenParams();
		authTokenParams.setAppid("");
		authTokenParams.setSecret("");
		authTokenParams.setCode(code);
		authAccessToken = oAuthService.getAuthAccessToken(authTokenParams, ACCESS_TOKEN_URL);	
	}
	if(authAccessToken!=null){
		logger.info("網頁授權的accessToken=" + authAccessToken.getAccess_token());
		logger.info("正在綁定的openid=" + authAccessToken.getOpenid());
	}
	return "/system/wxuserprize/bindPhone";
}

五、刷新access_token(如果需要)

由於access_token擁有較短的有效期,當access_token超時後,可以使用refresh_token進行刷新,refresh_token有效期為30天,當refresh_token失效之後,需要用戶重新授權。

請求參數封裝

package com.phil.wechatauth.model.req;

import java.io.Serializable;
import java.util.Map;
import java.util.TreeMap;

import com.phil.common.params.AbstractParams;

/**
 * 刷新token請求
 * @author phil
 * @date  2017年7月2日
 *
 */
public class RefreshTokenParams extends AbstractParams implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -7200815808171378571L;
	
	private String appid;
	private String grant_type = "refresh_token";
	private String refresh_token;

	public RefreshTokenParams(String appid, String grant_type, String refresh_token) {
		super();
		this.appid = appid;
		this.grant_type = grant_type;
		this.refresh_token = refresh_token;
	}

	/**
	 * 參數組裝
	 * 
	 * @return
	 */
	public Map<String, String> getParams() {
		Map<String, String> params = new TreeMap<String, String>();
		params.put("appid", this.appid);
		params.put("grant_type", this.grant_type);
		params.put("refresh_token", this.refresh_token);
		return params;
	}

	public String getAppid() {
		return appid;
	}

	public void setAppid(String appid) {
		this.appid = appid;
	}

	public String getGrant_type() {
		return grant_type;
	}

	public String getRefresh_token() {
		return refresh_token;
	}

	public void setRefresh_token(String refresh_token) {
		this.refresh_token = refresh_token;
	}
}
	/**
	 * 刷新網頁授權驗證
	 * @param basic  參數
	 * @param path 請求路徑
	 * @return
	 */
	public String refreshAuthAccessToken(AbstractParams basic, String path) {	
		AuthAccessToken authAccessToken = null;
		//刷新網頁授權憑證
		try {
			String result = HttpReqUtil.HttpsDefaultExecute(HttpReqUtil.GET_METHOD, path, basic.getParams(), null);
			if(result != null){				
				authAccessToken = JsonUtil.fromJson(result, AuthAccessToken.class);
			}
		} catch (Exception e) {
			authAccessToken = null;
			logger.info("error"+e.getMessage());
			
		}
		return authAccessToken==null?null:authAccessToken.getAccess_token();
	}

六、拉取用戶信息

如果網頁授權作用域為snsapi_userinfo,則此時可以通過access_token和openid拉取用戶信息了

通過網頁授權獲取的用戶信息

/**
 * 通過網頁授權獲取的用戶信息
 * 
 * @author phil
 */
public class AuthUserInfo {
	// 用戶標識
	private String openid;
	// 用戶昵稱
	private String nickname;
	// 性別(1是男性,2是女性,0是未知)
	private String sex;
	// 國家
	private String country;
	// 省份
	private String province;
	// 城市
	private String city;
	// 用戶頭像鏈接
	private String headimgurl;
	// 用戶特權信息
	private List<String> privilege;
	// 只有在用戶將公眾號綁定到微信開放平臺帳號後,才會出現該字段
	private String unionid;

	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 String getSex() {
		return sex;
	}

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

	public String getCountry() {
		return country;
	}

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

	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 getHeadimgurl() {
		return headimgurl;
	}

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

	public List<String> getPrivilege() {
		return privilege;
	}

	public void setPrivilege(List<String> privilege) {
		this.privilege = privilege;
	}

	public String getUnionid() {
		return unionid;
	}

	public void setUnionid(String unionid) {
		this.unionid = unionid;
	}
}

獲取方法

//獲取授權用戶信息
private final String USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo";
/**
 * 通過網頁授權獲取用戶信息
 * @param accessToken
 * @param openid
 * @return
 */
public AuthUserInfo getAuthUserInfo(String accessToken, String openid) {
	AuthUserInfo authUserInfo = null;	
	//通過網頁授權獲取用戶信息
	Map<String, String> params = new TreeMap<String, String>();
	params.put("openid", openid);
	params.put("access_token", accessToken);
	String result = HttpReqUtil.HttpsDefaultExecute(HttpReqtUtil.GET_METHOD, USERINFO_URL, params, null);		
	if(null != result){
		try {
			authUserInfo = JsonUtil.fromJson(result, AuthUserInfo.class);
		} catch (JsonSyntaxException e) {
			logger.info("transfer exception");				
		} 
	}
	return authUserInfo;
}

七、檢驗授權憑證(access_token)是否有效

//判斷用戶accessToken是否有效
private final String AUTH_URL = "https://api.weixin.qq.com/sns/auth";

/**
 *  檢驗授權憑證(access_token)是否有效
 * @param accessToken 網頁授權接口調用憑證
 * @param openid		用戶的唯一標識
 * @return  { "errcode":0,"errmsg":"ok"}表示成功     { "errcode":40003,"errmsg":"invalid openid"}失敗
 */
public ResultState authToken(String accessToken,String openid){
	ResultState state = null;
	Map<String,String> params = new TreeMap<String,String>();
	params.put("access_token",accessToken);
	params.put("openid", openid);
	String json = HttpReqUtil.HttpsDefaultExecute(HttpReqUtil.GET_METHOD, AUTH_URL, params,"");
	if(json!=null){
		state = JsonUtil.fromJson(json, ResultState.class);
	}
	return state;
}

Java微信公眾平臺開發之OAuth2.0網頁授權