最近公司项目网站需要支持第三方登录,需要对接微信,场景是用户通过微信app扫二维码实现自动登录功能。
经过查阅文档,发现微信该功能属于微信开放平台(https://open.weixin.qq.com),所以需要注册相关的资质,等待微信方面的审核,审核通过以后获得相应的权限,拿到开发所需要的APP_ID和APP_SECRET。
需要注意的是,在微信开放平台也要设置授权回调域,以前做过微信相关开发的同学应该都知道这个。刚接触微信对接的同学会发现,在本地开发调试的时候,微信的回调不能通知到本地接口。这时就需要内网穿透,工具很多在此就不赘述了。以前我也写过相关的文章,有兴趣的同学可以翻一下。
工具界面如下,通过访问指定的域名就可以访问到本地项目了。
至此,开发前的准备工具基本告一段落,接下来开始代码编写。
二、代码编写
1、定义常量
public class Constant {
/**
* 验证是否来自微信服务器的请求.
*/
public static final String STATE = "xxxxxxxxxxxx";
/**
* 功能描述: 微信相关--微信网页登录地址.
*
* @param
* @return
* @author zz
* @date 2019/5/27 11:30
*/
public static final String WECHAT_QR_URL = "https://open.weixin.qq.com/connect/qrconnect?appid=";
/**
* 功能描述: 跳转路径.
*
* @param
* @return
* @author zz
* @date 2019/5/27 11:36
*/
public static final String REDIRECT_URI = "/api/authcenter/weChat/callback";
/**
* 功能描述: 绑定.
*
* @param null
* @return
* @author zz
* @date 2019/6/14 13:56
*/
public static final String REDIRECT_URI_BIND = "/api/authcenter/weChat/callback/bind";
/**
* 获取openId.
*/
public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=";
/**
* 公众号APPID.
*/
public static final String APP_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
/**
* APP秘钥.
*/
public static final String APP_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
/**
* 获取用户信息.
*/
public static final String GET_WECHAT_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?access_token=";
}
2、写了一个工具类,将部分方法封装起来,项目里直接去调用。
public class WeChatUtil {
/**
* 功能描述: 获取微信网页登录地址.
*
* @param redirectUri redirectUri
* @return url
* @author zz
* @date 2019/5/27 11:26
*/
public static String openWeChatLogin(String redirectUri) {
String url = Constant.WECHAT_QR_URL + Constant.APP_ID + "&redirect_uri=" + URLEncoder.encode(redirectUri) +
"&response_type=code&scope=snsapi_login&state=" + Constant.STATE + "#wechat_redirect";
return url;
}
/**
* 功能描述: 微信扫码绑定地址.
*
* @param redirectUri redirectUri
* @return url
* @author zz
* @date 2019/6/14 13:42
*/
public static String bind(String redirectUri, String userId) {
String url = Constant.WECHAT_QR_URL + Constant.APP_ID + "&redirect_uri=" + URLEncoder.encode(redirectUri) +
"&response_type=code&scope=snsapi_login&state=" + Constant.STATE + "_" + userId + "#wechat_redirect";
return url;
}
/**
* 功能描述: 微信授权获取openid和access_token.
*
* @param code code
* @return map
* @author zz
* @date 2019/5/24 17:15
*/
public static Map getOauth2(String code) {
Map map = new HashMap();
String url = Constant.ACCESS_TOKEN_URL + Constant.APP_ID + "&secret=" + Constant.APP_SECRET + "&code=" + code + "&grant_type=authorization_code";
String result = HttpUtil.httpRequest(url);
if (!Strings.isNullOrEmpty(result)) {
JSONObject json = JSON.parseObject(result);
if (json.get("errcode") != null) {
log.debug("code参数异常,获取accessToken失败");
return map;
}
String accessToken = json.getString("access_token");
String openid = json.getString("openid");
String unionid = json.getString("unionid");
if (!Strings.isNullOrEmpty(accessToken)) {
map.put("accessToken", accessToken);
}
if (!Strings.isNullOrEmpty(openid)) {
map.put("openid", openid);
}
if (!Strings.isNullOrEmpty(unionid)) {
map.put("unionid", unionid);
}
log.debug("获取到的参数--" + map.toString());
} else {
log.debug("请求失败,请重试");
}
return map;
}
/**
* 功能描述: 获取微信用户个人信息.
*
* @param openId openId
* @param accessToken accessToken
* @return java.conf.Map
* @author zz
* @date 2019/5/24 16:42
*/
public static Map getWeChatInfo(String openId, String accessToken) {
Map map = new HashMap();
if (!Strings.isNullOrEmpty(openId) && !Strings.isNullOrEmpty(accessToken)) {
String weChatUrl = Constant.GET_WECHAT_USER_INFO + accessToken + "&openid=" + openId + "&lang=zh_CN";
String userInfo = HttpUtil.httpRequest(weChatUrl);
if (!Strings.isNullOrEmpty(userInfo)) {
JSONObject json = JSONObject.parseObject(userInfo);
if (json.get("errcode") != null) {
log.debug("openid或accessToken参数异常,获取微信个人信息失败");
return map;
}
String nickName = json.getString("nickname");
String headimgurl = json.getString("headimgurl");
//普通用户性别,1为男性,2为女性
Integer sex = json.getInteger("sex");
String province = json.getString("province");
String city = json.getString("city");
String country = json.getString("country");
String unionid = json.getString("unionid");
if (!Strings.isNullOrEmpty(nickName)) {
map.put("nickName", nickName);
}
if (!Strings.isNullOrEmpty(headimgurl)) {
map.put("headimgurl", headimgurl);
}
if (sex != null) {
map.put("sex", sex);
}
if (!Strings.isNullOrEmpty(province)) {
map.put("province", province);
}
if (!Strings.isNullOrEmpty(city)) {
map.put("city", city);
}
if (!Strings.isNullOrEmpty(country)) {
map.put("country", country);
}
if (!Strings.isNullOrEmpty(unionid)) {
map.put("unionid", unionid);
}
log.debug("获取到的参数--" + map.toString());
} else {
log.debug("获取微信用户信息失败");
}
} else {
log.debug("请求参数不全");
}
return map;
}
/**
* 功能描述: 测试.
*
* @param
* @return
* @author zz
* @date 2019/5/28 10:34
*/
public static void main(String[] args) {
System.out.println("获取微信跳转地址--" + openWeChatLogin("http://tonyfreak.free.idcfengye.com"));
System.out.println("获取openid和access_token--" + getOauth2("222"));
System.out.println("获取微信个人信息--" + getWeChatInfo("1", "333").toString());
}
}
3、控制层
项目前后端分离,前端访问接口,后端将参数进行拼接,返回访问地址。
/**
* 功能描述: 获取微信第三方登录路径.
*
* @param
* @return
* @author zz
* @date 2019/5/24 16:15
*/
@ApiOperation("获取微信第三方登录路径")
@GetMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE)
public Result openWeChatLogin(HttpServletResponse httpServletResponse) throws Exception {
log.debug("微信跳转链接--" + WeChatUtil.openWeChatLogin(redirectUri + Constant.REDIRECT_URI));
//httpServletResponse.sendRedirect(WeChatUtil.openWeChatLogin(redirectUri + Constant.REDIRECT_URI));
return new Result(Constant.MSGSUCCESS, WeChatUtil.openWeChatLogin(redirectUri + Constant.REDIRECT_URI));
}
获取到的地址:
{
"code": 200,
"message": "操作成功",
"object": "https://open.weixin.qq.com/connect/qrconnect?appid=XXXXXXXX&redirect_uri=http%3A%2F%2Fdev.XXXXXX.com%2Fapi%2Fauthcenter%2FweChat%2Fcallback&response_type=code&scope=snsapi_login&state=XXXXXX#wechat_redirect",
"map": {}
}
访问该地址,跳转到开头提到的那张图,显示微信生成的二维码。
接着用户在手机上用微信app扫码,进行授权,点击同意后跳转相应页面。
如下图:
回调方法:
/**
* 功能描述: 微信扫码登录第三方网站回调接口.
*
* @param httpServletRequest httpServletRequest
* @return result
* @author zz
* @date 2019/5/24 16:23
*/
@ApiOperation("微信扫码登录第三方网站回调接口--(微信自己调用该接口,不需要前端手动调用)")
@GetMapping(value = "/callback")
public Result openWeChatCallback(HttpServletRequest httpServletRequest, HttpServletResponse response) throws Exception {
String code = httpServletRequest.getParameter("code");
String state = httpServletRequest.getParameter("state");
Object data = null;
String message = Constant.MSGSUCCESS;
// 缓存
Jedis jedis = new Jedis(host, port);
if (!Strings.isNullOrEmpty(code) && !Strings.isNullOrEmpty(state)) {
// 判断state是否合法
if (Constant.STATE.equals(state)) {
// 获取微信授权
Map oauth2 = WeChatUtil.getOauth2(code);
if (oauth2.size() > 0) {
String openid = oauth2.get("openid").toString();
String accessToken = oauth2.get("accessToken").toString();
SysUser user = sysUserService.findByOpenId(openid, Constant.Status.valid, 1);
if (user != null) {
// 获取老用户
SysUserVo sysUserVo = BeanMapper.map(user, SysUserVo.class);
String json = JSON.toJSONString(sysUserVo);
HttpSession session = httpServletRequest.getSession();
session.setAttribute("userinfo", json);
sysUserVo.setTokenId(session.getId());
sysUserVo.setRegisterStatus(Constant.Status.valid);
data = sysUserVo;
String newUrl = "http://XXXXXXXXX.com/#/Index?token=" + session.getId();
response.sendRedirect(newUrl);
return null;
} else {
// 创建新用户
Map weChatInfo = WeChatUtil.getWeChatInfo(openid, accessToken);
if (weChatInfo.size() > 0) {
SysUser newUser = SysUser.convert(oauth2, weChatInfo);
SysUserVo sysUserVo = BeanMapper.map(newUser, SysUserVo.class);
sysUserVo.setRegisterStatus(Constant.Status.invalid);
data = sysUserVo;
String uuid = UuidUtil.uuid2();
jedis.set(uuid, JSON.toJSONString(data));
String newUrl = "http://XXXXXX.com/#/Register?key=" + uuid;
response.sendRedirect(newUrl);
return null;
} else {
message = "获取微信个人信息失败";
log.debug(message);
response.sendRedirect("http://XXXXXXX.com/#/Register");
return null;
}
}
} else {
message = "获取微信授权失败";
log.debug(message);
response.sendRedirect("http://XXXXXXXXX.com/#/Register");
return null;
}
} else {
message = "非法参数,请重试";
log.debug(message);
response.sendRedirect("http://XXXXXXXX.com/#/Register");
return null;
}
} else {
message = "用户未授权,请授权";
log.debug(message);
response.sendRedirect("http://XXXXXXXX.com/#/Register");
return null;
}
}
这里的代码直接重定向到了页面,测试时返回的json如下:
到此就完成了微信扫码登录网站的功能,回调中的代码根据自己具体的业务需求去编写,整个流程大方向不变。
如有疑问可留言共同讨论。