微信授权登录——OAuth2.0
如果用户在微信客户端中访问我公司的网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。 整个流程是这样的:
在这个过程中 最重要的一步 是 获取openID和 unionId ,我把这次 请求返回的数据封装成一个对象(AccessTokenBean),然后根据openID到本地的数据库查询有没有这一条数据,如果没有就使用 unionID机制查询,这里需要详细的讲解一下,我当时就对这一点很迷惑。
(这些问答是我刚开始的时候遇到的,到后来慢慢解决了的)
Q: 首先 openID是什么?
A: openId 是 用户唯一标识,用户访问公众号的网页,产生一个用户和公众号唯一的OpenID,这个ID的存在表示这个用户在本网站登录过,并且用户同意微信授权登录。
Q: 什么是授权临时票据?
A:第三方通过code进行获取access_token的时候需要用到,code的超时时间为10分钟,一个code只能成功换取一次access_token即失效。code的临时性和一次保障了微信授权登录的安全性。第三方可通过使用https和state参数,进一步加强自身授权登录的安全性。
Q: 什么是unionID?
A: 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。详见:获取用户个人信息(UnionID机制) 比如说 我公司有两个app 产品(a产品,b产品), 我在公众号里面, 授权微信登录了a ,如果我想再登录b,这时候就需要查看数据库里面有没有AccessTokenBean 的unionId,如果有则直接静默登录,提高了用户体验度。
Q: 能从微信那里获取到哪些用户的信息
A: 用户的头像,昵称,省市,性别
Q: 为什么要使用 OAuth 2.0 ?
A: 用户既然已经使用微信访问到自己网站了,就可以用微信账户直接登录我的网站,省去了用户填写信息注册的步骤 ,提高了用户体验。
代码部分:
前端需要从微信获取 一个code,然后用这个code 来换取 AccessTokenBean 对象,这个对象包括,openId ,unionId 等其他信息。
// 前端有一个code ,可以用这个code 换取token 和openID, unionID ,返回的是一个用户的ID // 查询用户信息 @RequestMapping(value = "H5/getInformation", method = { RequestMethod.POST }) @ResponseBody public GeneralResponse getInformation(HttpServletRequest request) throws IOException { // 根据code 到微信查询 openid 和unionID String jsonParam = IOUtils.toString(request.getInputStream(), "UTF-8"); if (StringUtils.isBlank(jsonParam)) { return new GeneralResponse(900, "POST请求参数不能为空"); } Map<String,String> req = JacksonUtil.json2Obj(jsonParam, Map.class); if (null ==req.get("code")){ return new GeneralResponse(900, "code参数不能为空"); } AccessTokenBean accessTokenBean= OAuth2Service.getAccessToken(req.get("code")); if(null ==accessTokenBean){ return new GeneralResponse(900, "从微信获取openid 出错"); } CommonResponse response = CommonResponse.generateOKCommonResponse(); String openId = accessTokenBean.getOpenid(); // 这里可以获取token 和 unionID if (StringUtils.isNotBlank(openId)) { // 根据openID检查有没有存在数据库中 OauthUser oauthUser=new OauthUser(); oauthUser.setOpenId(openId); List<OauthUser> userList= oauthUserService.queryBySelective(oauthUser); if (!userList.isEmpty()) { String userId= userList.get(0).getUserId(); User user= userService.getUserByUserId(userId); response.setData(user); }else { // 如果 openid 没有找到 则使用 unionId 来找 oauthUser.setOpenId(null); oauthUser.setUnionId(accessTokenBean.getUnionId()); List<OauthUser> userList1= oauthUserService.queryBySelective(oauthUser); if (! userList1.isEmpty()){ String userId = userList1.get(0).getUserId(); // 如果找到了就,把这个openid 添加到数据库 addOauthUser(accessTokenBean.getOpenid(),accessTokenBean.getUnionId(),userId); User user= userService.getUserByUserId(userId); response.setData(user); }else{ //从微信获取用户信息,,然后再 跳转到 注册界面 WXUserBean wxUserBean= OAuth2Service.getUserInfo(accessTokenBean); User user=new User(); if (null != wxUserBean){ user.setPhotoUrl(wxUserBean.getHeadimgurl()); user.setAlias(wxUserBean.getNickname()); user.setSex(wxUserBean.getSex()); // 1 男 } response.setData(user); } } } return response; }
主要的类: OAuth2Service
import com.alibaba.fastjson.JSONObject; import com.hupu.smart.elgame.api.dto.user.oauthuser.AccessTokenBean; import com.hupu.smart.elgame.api.dto.user.oauthuser.WXUserBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; /** * Created by admin 2017/4/18. */ @Service public class OAuth2Service { @Value("#{configProperties['APP_ID']}") private static String APP_ID; @Value("#{configProperties['APP_SECRET']}") private static String APP_SECRET; /** * 获取微信用户信息 * @param atBean * @return */ public static WXUserBean getUserInfo(AccessTokenBean atBean) { if (!verify(atBean.getAccess_token(), atBean.getOpenid())) { atBean = refreshAccessToken(atBean.getRefresh_token()); } WXUserBean userBean = getUserInfoApi(atBean.getAccess_token(),atBean.getOpenid()); return userBean; } /** * 获取AccessToken * @param code * @return */ public static AccessTokenBean getAccessToken(String code) { JSONObject jObj = Get.json("https://api.weixin.qq.com/sns/oauth2/access_token?appid="+ APP_ID +"&secret="+ APP_SECRET +"&code="+ code +"&grant_type=authorization_code"); AccessTokenBean oBean = new AccessTokenBean(); if (null !=jObj.get("errcode")){ return null; } oBean.setAccess_token(jObj.getString("access_token")); oBean.setExpires_in(jObj.getLong("expires_in")); oBean.setRefresh_token(jObj.getString("refresh_token")); oBean.setOpenid(jObj.getString("openid")); oBean.setScope(jObj.getString("scope")); oBean.setUnionId(jObj.getString("unionid")); return oBean; } /** * 刷新AccessToken * @param refresh_token * @return */ private static AccessTokenBean refreshAccessToken(String refresh_token) { JSONObject jObj = Get.json("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid="+ APP_ID +"&grant_type=refresh_token&refresh_token="+ refresh_token); AccessTokenBean oBean = new AccessTokenBean(); oBean.setAccess_token(jObj.getString("access_token")); oBean.setExpires_in(jObj.getLong("expires_in")); oBean.setRefresh_token(jObj.getString("refresh_token")); oBean.setOpenid(jObj.getString("openid")); oBean.setScope(jObj.getString("scope")); return oBean; } /** * 验证AccessToken是否可用 * @param accessToken * @param openid * @return */ private static boolean verify(String accessToken,String openid) { JSONObject jObj = Get.json("https://api.weixin.qq.com/sns/auth?access_token="+ accessToken +"&openid=" + openid); return jObj.getInteger("errcode") == 0; } /** * 获取用户信息 * @param accessToken * @param openid * @return */ private static WXUserBean getUserInfoApi(String accessToken, String openid) { JSONObject jObj = Get.json("https://api.weixin.qq.com/sns/userinfo?access_token="+ accessToken +"&openid="+ openid +"&lang=zh_CN"); WXUserBean wxub = new WXUserBean(); wxub.setOpenid(jObj.getString("openid")); wxub.setNickname(jObj.getString("nickname")); wxub.setSex(jObj.getInteger("sex")); wxub.setProvince(jObj.getString("province")); wxub.setCity(jObj.getString("city")); wxub.setCountry(jObj.getString("country")); wxub.setHeadimgurl(jObj.getString("headimgurl")); wxub.setUnionid(jObj.getString("unionid")); return wxub; } }
最后还有两个实体类:
public class WXUserBean { private String openid; private String nickname; private int sex; private String province; private String city; private String country; private String headimgurl; private String unionid; (省去get,set 方法) }
public class AccessTokenBean { private String access_token; private String refresh_token; private String openid; private long expires_in; private String scope; private String unionId; (省去,get 和set 方法) }