Java微信公众平台开发之OAuth2.0网页授权
根据官方文档在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头,也不需要加具体的项目名,在域名空间的根目录放一个微信需要的txt文件才能验证通过,测试号不需要
一、两种scope授权方式
- 以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)
- 以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息
二、关于特殊场景下的静默授权
- 上面已经提到,对于以snsapi_base为scope的网页授权,就静默授权的,用户无感知
- 对于已关注公众号的用户,如果用户从公众号的会话或者自定义菜单进入本公众号的网页授权页,即使是scope为snsapi_userinfo,也是静默授权,用户无感知
三、用户同意授权,获取code
我这边是snsapi_userinfo发起的网页授权,redirect_uri必须用urlEncode处理下
public abstract class BasicAuthParam implements Serializable {
private static final long serialVersionUID = 3375127810872852675L;
public abstract Map<String, String> getParams();
}
AuthCodeParams.java
/**
* FileName: AuthTokenParam
* Author: Phil
* Date: 12/4/2018 2:11 PM
* Description:
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
package com.phil.wechat.auth.model.request;
import com.phil.wechat.auth.model.BasicAuthParam;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.Map;
import java.util.TreeMap;
/**
* 〈一句话功能简述〉<br>
* 〈授权请求token的请求参数〉
*
* @author Phil
* @create 12/4/2018 2:11 PM
* @since 1.0
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AuthTokenParam extends BasicAuthParam {
private static final long serialVersionUID = 4652953400751046159L;
private String appid; //公众号的唯一标识
private String secret; //公众号的appsecret
private String code; //填写第一步获取的code参数
private String grant_type = "authorization_code";
/**
* 参数组装
*
* @return
*/
public Map<String, String> getParams() {
Map<String, String> params = new TreeMap<>();
params.put("appid", this.appid);
params.put("secret", this.secret);
params.put("code", this.code);
params.put("grant_type", this.grant_type);
return params;
}
}
注意:由于授权操作安全等级较高,所以在发起授权请求时,微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无正常访问;所以我都是用TreeMap拼接参数
请求链接的组装
/**
* 获取授权请求url
*
* @param basic
* @param url
* @return
* @throws Exception
*/
@Override
public String getAuthUrl(BasicAuthParam basic, String url) {
Map<String, String> params = basic.getParams();
try {
return HttpUtil.setParmas(url, params, "") + "#wechat_redirect";
} catch (Exception e) {
log.debug("error" + e.getMessage());
}
return null;
}
如果用户同意授权,页面将跳转至redirect_uri/?code=CODE&state=STATE ,然后再进行redirect_uri的逻辑,可以用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_toen的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为
/**
* FileName: AuthCodeParam
* Author: Phil
* Date: 12/4/2018 2:12 PM
* Description:
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改时间 版本号 描述
*/
package com.phil.wechat.auth.model.request;
import com.phil.modules.util.EncodeUtils;
import com.phil.wechat.auth.model.BasicAuthParam;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.Map;
import java.util.TreeMap;
/**
* 〈一句话功能简述〉<br>
* 〈授权code请求参数〉
*
* @author Phil
* @create 12/4/2018 2:12 PM
* @since 1.0
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AuthCodeParam extends BasicAuthParam {
private static final long serialVersionUID = 6313379843885615765L;
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;
/**
* 参数组装
*
* @return
*/
public Map<String, String> getParams() {
Map<String, String> params = new TreeMap<>();
params.put("appid", this.appid);
params.put("redirect_uri", EncodeUtils.urlEncode(this.redirect_uri));
params.put("response_type", this.response_type);
params.put("scope", this.scope);
params.put("state", this.state);
return params;
}
}
@Getter
@Setter
public class AccessToken implements Serializable {
private static final long serialVersionUID = 5806078615354556552L;
// 获取到的凭证
@SerializedName("access_token")
private String accessToken;
// 凭证有效时间,单位:秒
private int expires_in;
}
@Getter
@Setter
public class AuthAccessToken extends AccessToken {
private static final long serialVersionUID = -525656415464372637L;
@SerializedName("refresh_token")
private String refreshToken; // 用户刷新access_token
private String openid; // 用户唯一标识,请注意,在未关注公众号时,用户访问公众号的网页,也会产生一个用户和公众号唯一的OpenID
private String scope; // 用户授权的作用域,使用逗号(,)分隔
}
获取网页凭证
/**
* 获取网页授权凭证
*
* @param basic
* @param url
* @return
*/
@Override
public AuthAccessToken getAuthAccessToken(BasicAuthParam basic, String url) {
try {
String result = HttpUtil.doGet(wechatAuthConfig.getGetOauthTokenUrl(), basic.getParams());
return JsonUtil.fromJson(result, AuthAccessToken.class);
} catch (Exception e) {
log.debug("error" + e.getMessage());
}
return null;
}
五、刷新access_token(如果需要)
由于access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新,refresh_token有效期为30天,当refresh_token失效之后,需要用户重新授权。
/**
* 刷新网页授权验证
*
* @param basic 参数
* @param url 请求路径
* @return 新的网页授权验证
*/
@Override
public AuthAccessToken refreshAuthAccessToken(BasicAuthParam basic, String url) {
try {
String result = HttpUtil.doGet(wechatAuthConfig.getRefreshOauthTokenUrl(), basic.getParams());
return JsonUtil.fromJson(result, AuthAccessToken.class);
} catch (Exception e) {
log.debug("error" + e.getMessage());
}
return null;
}
六、拉取用户信息
如果网页授权作用域为snsapi_userinfo,则此时可以通过access_token和openid拉取用户信息了
@Getter
@Setter
public class AuthUserInfo implements Serializable {
private static final long serialVersionUID = 5325219651225561359L;
// 用户标识
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;
}
/**
* 通过网页授权获取用户信息
*
* @param accessToken 网页授权接口调用凭证
* @param openid 用户唯一标识
* @return 用户信息
*/
@Override
public AuthUserInfo getAuthUserInfo(String accessToken, String openid) {
// 通过网页授权获取用户信息
Map<String, String> params = new HashMap<>();
params.put("access_token", accessToken);
params.put("openid", openid);
String result = HttpUtil.doGet(wechatAuthConfig.getSnsUserinfoUrl(), params);
try {
return JsonUtil.fromJson(result, AuthUserInfo.class);
} catch (JsonSyntaxException e) {
log.debug("transfer exception");
}
return null;
}
七、检验授权凭证(access_token)是否有效
/**
* 检验授权凭证(access_token)是否有效
*
* @param accessToken 网页授权接口调用凭证
* @param openid 用户唯一标识
* @return { "errcode":0,"errmsg":"ok"}表示成功 { "errcode":40003,"errmsg":"invalid
* openid"}失败
*/
@Override
public ResultState authToken(String accessToken, String openid) {
Map<String, String> params = new HashMap<>();
params.put("access_token", accessToken);
params.put("openid", openid);
String jsonResult = HttpUtil.doGet(
wechatAuthConfig.getCheckSnsAuthStatusUrl(), params);
return JsonUtil.fromJson(jsonResult, ResultState.class);
}
具体源码:https://github.com/philjing/my_wechat/tree/master/src/main/java/com/phil/wechat/auth