010:基于策略模式快速整合微信联合登录
1 整合微信联合登录效果演示
今日课程任务
- 微信公众号开发授权联合登录的原理
- 基于策略模式如何快速整合微信联合登录
- 基于用户授权的令牌关联openId信息
- 前后端分离架构模式可能会存在的问题
2 微信联合登录获取用户信息原理
微信授权链接登录 遵循规范oauth2.0协议
- 生成授权链接(用户访问) appId、回调地址
- 根据授权code获取accessToken和openId
- 根据openId可以获取微信用户信息(微信名称、图片、地址等)
3 生成微信oatuh2.0授权链接地址
微信开放文档地址:
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
参考链接(请在微信客户端中打开此链接体验):
scope为snsapi_userinfo
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
尤其注意:跳转回调redirect_uri,应当使用https链接来确保授权code的安全性。
测试效果:
4 根据openid获取用户的基本信息
第二步:通过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获取用户信息等步骤,也必须从服务器发起。
请求方法
获取code后,请求以下链接获取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
第四步:拉取用户信息(需scope为 snsapi_userinfo)
如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。
请求方法
http:GET(请使用https协议)
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
测试效果:
5 基于策略模式快速整合微信联合登录
数据库插入数据
INSERT INTO `meite_member`.`meite_union_login`(`id`, `union_name`, `union_public_id`, `union_bean_id`, `app_id`, `app_key`, `redirect_uri`, `request_address`, `is_availability`) VALUES (2, '腾讯微信联合登陆', 'mayikt_weixin', 'weiXinUnionLoginStrategy', 'wx84fa688175f78964', '1451cd74xxxe99ff3a1f1e', 'http://www.itmayiedu.com:7070/login/oauth/callback?unionPublicId=mayikt_weixin', 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx84fa688175f78964&redirect_uri=http://www.itmayiedu.com:7070/login/oauth/callback?unionPublicId=mayikt_weixin&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect', 1);
微信登录策略实现类
@Component
public class WeiXinUnionLoginStrategy implements UnionLoginStrategy {
@Value("${mayikt.login.wx.accesstoken}")
private String weixinAccessTokenAddres;
@Autowired
private UserMapper userMapper;
@Override
public String unionLoginCallback(HttpServletRequest request, UnionLoginDO unionLoginDo) {
String code = request.getParameter("code");
if (StringUtils.isEmpty(code)) {
return null;
}
// 根据授权码获取accessToken和openid
String newWeixinAccessTokenAddres = weixinAccessTokenAddres.replace("APPID", unionLoginDo.getAppId() + "").replace("SECRET",
unionLoginDo.getAppKey()).replace("CODE", code);
JSONObject accessTokenResult = HttpClientUtils.httpGet(newWeixinAccessTokenAddres);
if (accessTokenResult == null) {
return null;
}
boolean errcode = accessTokenResult.containsKey("errcode");
if (errcode) {
return null;
}
// 获取openid
String openid = accessTokenResult.getString("openid");
if (StringUtils.isEmpty(openid)) {
return null;
}
return openid;
}
}
bootstrap.yml
mayikt:
login:
wx:
accesstoken: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
测试效果:
6 vue整合微信联合登录的设计原理
关联页面需要传递token令牌
- 先根据令牌从redis中解析出对应的openId
- 如果该openId关联过账户,直接实现自动登录,返回用户的token给客户端,vue直接跳转到首页页面;
- 如果该openId没有关联过账户,跳转到页面选择关联新账号或者已有账号,当选择关联已有账号,登录的时候会传递该openToken,数据库会更新该用户openId字段。
7 基于openidToken实现快速登录原理
@Api(tags = "基于OpenIdToken登录")
public interface MemberOpenIdTokenLogin {
@GetMapping("/openIdToken")
BaseResponse<JSONObject> openIdLoginToken(@RequestParam("openIdToken") String openToken);
}
@RestController
public class MemberOpenIdTokenLoginImpl extends BaseApiService implements MemberOpenIdTokenLogin {
@Autowired
private TokenUtil tokenUtil;
@Value("${mayikt.login.token.prefix}")
private String loginTokenPrefix;
@Autowired
private UnionLoginMapper unionLoginMapper;
@Autowired
private UserMapper userMapper;
@Override
public BaseResponse<JSONObject> openIdLoginToken(String openToken) {
if (StringUtils.isEmpty(openToken)) {
return null;
}
// 1.根据openToken 获取真实openid
String tokenValue = tokenUtil.getTokenValue(openToken);
if (StringUtils.isEmpty(tokenValue)) {
return setResultError("流程已经失效或者token错误");
}
// 2.根据openid 查询是否关联
JSONObject jsonObject = JSONObject.parseObject(tokenValue);
String unionPublicId = jsonObject.getString("unionPublicId");
// 3.根据该渠道id查询bean的id,从容器中获取
// 根据渠道id查询 联合基本信息 在缓存存一份
UnionLoginDO unionLoginDo = unionLoginMapper.selectByUnionLoginId(unionPublicId);
if (unionLoginDo == null) {
return setResultError("该渠道可能已经关闭或者不存在");
}
String unionBeanId = unionLoginDo.getUnionBeanId();
UnionLoginStrategy unionLoginStrategy = SpringContextUtils.
getBean(unionBeanId, UnionLoginStrategy.class);
if (unionLoginStrategy == null) {
return setResultError("没有查询到该策略");
}
String openid = jsonObject.getString("openId");
// UserDO userDO = null;
// switch (unionPublicId) {
// case "mayikt_qq":
// userDO = userMapper.selectByQQOpenId(openid);
// case "mayikt_weixin":
// userDO = userMapper.selectByOpenId(openid);
// }
// 策略模式重构,以上两种场景方法分别放子类中实现
UserDO userDo = unionLoginStrategy.getDbOpenId(openid);
if (userDo == null) {
return setResultError("当前用户没有关联,应该跳转到关联页面");
}
// 3.如果已经关联,自动帮助实现登录
//获取userId 这里可以和MemberLoginServiceImpl共同业务逻辑重构一个manage
Long userId = userDo.getUserId();
String userToken = tokenUtil.createToken(loginTokenPrefix, userId + "");
JSONObject data = new JSONObject();
data.put("userToken", userToken);
return setResultSuccess(data);
}
}
测试效果