1. 为什么要第三方登录?
服务方希望用户注册, 而用户懒得填注册时的各种信息(主要是为了保证用户的唯一性,各种用户名已占用,密码格式限制).
而像微信, QQ, 微博等几乎每个人都会安装的应用中用户肯定会在其中某一个应用中已经注册过,证明该用户在已经注册的应用中的唯一性.
第三方登录的实质就是在授权时获得第三方应用提供的代表了用户在第三方应用中的唯一性的openid.并将openid储存在第三方服务控制的本地储存.
2. 整体登录模型流程
-
注册流程
-
登录流程
3. 第三方登录前后端细节区分
4. 这里有坑,在不同平台我们需要对 openid 和 unionid,需要进行整合,
区别:
- 微信openid和unionid长度是不一样的
openid=28,unionid=29 -
openid同一用户同一应用唯一,unionid同一用户不同应用唯一。(这是不同端做关联的依据)
区分用户在小程序登录和app第三方登录是否是一个来自通过第三方同一个用户
里的不同应用是指在同一微信开发平台下的不同应用
为了识别用户,每个用户针对每个公众号会产生一个安全的openid。
如果需要在多公众号、移动应用之间做用户共通,则需要前往微信开放平台,将这些公众号和应用绑定到一个开放平台账号下,绑定后,一个用户虽然对多个公众号和应用有多个不同的openid,但他对所有这些同一开放平台账号下的公众号和应用,只有一个unionid。一个微信开放平台只可以绑定10个公众号。
对于在pc端和客户端
我们在使用微信网页版本的时候,生成微信二维码用户扫描登录,此时获取的openid和微信客户端的openid是不一样的,但是unionId是一样的。这是因为,网页二维码扫描登录是网站应用,客户端是公众号,两者属于不同应用。获取用户的openid是无需用户同意的,获取用户的基本信息则需要用户同意
5. 核心代码
- controller 层
/**
* ApiAcc用户控制器
*/
@Log4j2
@RestController
@RequestMapping("/api/acc/user")
public class ApiAccUserController extends BaseController {
@Autowired
private JwtConfig jwtConfig;
@Autowired
private IAccUserService iAccUserService;
@Autowired
private IAccUserCredentialService userCredentialService;
@Autowired
@Autowired
private ISmsService smsService;
@Autowired
private IOcsUserCommunicationService ocsUserCommunicationService;
@Autowired
private IOcsBalanceService ocsBalanceService;
@Value("${IMG_PREFIX}")
private String IMG_PATH;
/**
* 用户注册
*/
@PostMapping("/register")
@EncryptIgnore
public AjaxResult register(@Valid @RequestBody AccUserRegistReq registReq) {
boolean flag = smsService.checkIsCorrectCode(registReq.getPhone(), registReq.getCode());
if (!flag) {
return AjaxResult.error("短信验证码校验失败");
}
AccUser user = new AccUser();
user.setUsername(registReq.getPhone());
List<AccUser> list = iAccUserService.selectAccUserList(user);
if (!list.isEmpty()) {
return AjaxResult.error("用户手机号已经存在");
}
user.setId(IDGeneratorUtils.getLongId());
if (registReq.getFid() == null) {
user.setPassword(Md5Encrypt.MD5UserPwd(registReq.getPassword(), "ShyD"));
user.setFid(user.getId());
} else {
user.setFid(registReq.getFid());
}
user.setPhone(registReq.getPhone());
user.setNickname(registReq.getNickname());
user.setRealname(registReq.getNickname());
user.setUsername(registReq.getPhone());
int count = iAccUserService.insertAccUser(user);
if (count < 1) {
return AjaxResult.error("添加失败!请重试");
}
return AjaxResult.success(JsonUtils.beanToBean(user, AccUserAttributeRes.class));
}
/**
* 身份验证令牌
*
* @param request 请求
* @param loginAuthReq 登录身份验证要求的事情
* @return {@link AjaxResult}
*/
@PostMapping("/token")
@EncryptIgnore
@DecryptIgnore
public AjaxResult authToken(HttpServletRequest request, @Valid @RequestBody LoginAuthReq loginAuthReq) {
if (0 == loginAuthReq.getLoginType() && StringUtils.isBlank(loginAuthReq.getCredential())) {
return AjaxResult.error("用户密码不能为空!!");
}
//记录token和第三方登录信息
String token = null;
String reOpenId = null;
String sessionKey = null;
AccUser user = new AccUser();
Map<String, String> result = new HashMap<>();
switch (loginAuthReq.getLoginType()) {
//手机号和密码登录: 只是校验数据库用户表的账户和密码
case 0:
user.setUsername(loginAuthReq.getAccount());
List<AccUser> userList = iAccUserService.selectAccUserList(user);
if (null == userList || userList.isEmpty()) {
return AjaxResult.error("用户不存在!请注册!");
}
user = userList.get(0);
String password = Md5Encrypt.MD5UserPwd(loginAuthReq.getCredential(), "ShyD");
if (!password.equals(user.getPassword())) {
return AjaxResult.error("密码错误!请重新输入密码!");
}
token = jwtConfig.createTokenByUserId(user);
break;
//手机号和验证码登录:只是校验数据库的用户表的账户
case 1:
//验证码登录
boolean flag = smsService.checkIsCorrectCode(loginAuthReq.getAccount(), loginAuthReq.getCredential());
if (!flag) {
return AjaxResult.error("短信验证码校验失败");
}
user.setPhone(loginAuthReq.getAccount());
List<AccUser> list = iAccUserService.selectAccUserList(user);
if (null == list || list.isEmpty()) {
return AjaxResult.error("用户不存在!请注册之后再登录!");
}
user = list.get(0);
token = jwtConfig.createTokenByUserId(user);
break;
case 2:
//微信登录 如果没有用户 注册一个
UserInfoData userInfoData = null;
if ("xcx".equals(loginAuthReq.getCredential())) {
userInfoData = new WeiXinAuthService().checkLogin(loginAuthReq.getAccount(), 1);
reOpenId = userInfoData.getOpenId();
sessionKey = userInfoData.getSessionKey();
} else {
userInfoData = new WeiXinAuthService().checkLogin(loginAuthReq.getAccount(), 0);
}
if (null == userInfoData) {
return AjaxResult.error("微信OpenID不正确");
}
System.out.println("微信信息获取成功");
String openId = userInfoData.getOpenId();
String unionId = userInfoData.getUnionId();
AccUserCredential userCredential = new AccUserCredential();
//根据联合ID查询
userCredential.setLoginAccount(unionId);
userCredential.setLoginType(1);
List<AccUserCredential> userCredentials = userCredentialService.selectAccUserCredentialList(userCredential);
if (null == userCredentials || userCredentials.isEmpty()) {
//如果查不到,在根据OpenID查询
userCredential.setLoginAccount(openId);
userCredentials = userCredentialService.selectAccUserCredentialList(userCredential);
if (null == userCredentials || userCredentials.isEmpty()) {
//如果均为空,说明这个微信没有登录过,开始注册
user.setId(IDGeneratorUtils.getLongId());
// user.setFid(user.getId());
if (StringUtils.isNotBlank(userInfoData.getIcon())) {
JSONObject avatar = FtpFileUploadUtils.uploadRemoteFile("avatar", userInfoData.getIcon());
if (avatar != null) {
user.setAvatar(IMG_PATH + avatar.getString("url"));
}
}
if (StringUtils.isNotBlank(userInfoData.getName())) {
user.setNickname(userInfoData.getName());
}
System.out.println(userInfoData.getGender());
if (null != userInfoData.getGender()) {
user.setSex(userInfoData.getGender().equals("1") ? 1 : (userInfoData.getGender().equals("2") ? 2 : 0));
}
userCredential.setId(IDGeneratorUtils.getLongId());
userCredential.setUserId(user.getId());
userCredential.setLoginAccount(userInfoData.getUnionId());
userCredential.setCredential(userInfoData.getOpenId());
//插入到 第三方形式登录的用户
iAccUserService.insertThirdLoginType(user, userCredential);
token = jwtConfig.createTokenByUserId(user);
} else {
//更新原来的OPENID为UNIONID
userCredential = userCredentials.get(0);
userCredential.setLoginAccount(unionId);
userCredential.setCredential(openId);
userCredentialService.updateById(userCredential);
user.setId(userCredentials.get(0).getUserId());
token = jwtConfig.createTokenByUserId(user);
}
//如果微信没有登录过,token返回为字符串“token”
} else {
System.out.println("用户存在 直接生成");
Long userId = userCredentials.get(0).getUserId();
user.setId(userCredentials.get(0).getUserId());
token = jwtConfig.createTokenByUserId(user);
}
break;
case 3:
//QQ登录 如果没有用户 注册一个
AccUserCredential qqCredential = new AccUserCredential();
qqCredential.setLoginAccount(loginAuthReq.getAccount());
qqCredential.setLoginAccount("2");
List<AccUserCredential> userCredentialList = userCredentialService.selectAccUserCredentialList(qqCredential);
if (null == userCredentialList || userCredentialList.isEmpty()) {
QQUserInfo userInfo = QQAuthService.getUserInfo(loginAuthReq.getCredential(), loginAuthReq.getAccount());
//注册就成了
user.setId(IDGeneratorUtils.getLongId());
user.setFid(user.getId());
JSONObject avatar = FtpFileUploadUtils.uploadRemoteFile("avatar", userInfo.getFigureurl());
if (avatar != null) {
user.setAvatar(IMG_PATH + avatar.getString("url"));
}
user.setNickname(userInfo.getNickName());
user.setSex(userInfo.getGender().equals("男") ? 1 : (userInfo.getGender().equals("女") ? 2 : 0));
qqCredential.setId(IDGeneratorUtils.getLongId());
qqCredential.setUserId(user.getId());
qqCredential.setCredential("");
iAccUserService.insertThirdLoginType(user, qqCredential);
token = jwtConfig.createTokenByUserId(user);
} else {
user.setId(userCredentialList.get(0).getUserId());
token = jwtConfig.createTokenByUserId(user);
}
break;
default:
//appleId 登陆
user = iAccUserService.appleLogin(loginAuthReq);
token = jwtConfig.createTokenByUserId(user);
break;
}
user.setLastLoginTime(new Date());
user.setLastLoginIp(IpUtils.getIpAddr(request));
// 添加用户账户信息
ocsBalanceService.addBalance(user.getId());
iAccUserService.updateById(user);
result.put("token", token);
result.put("openId", reOpenId);
result.put("sessionKey", sessionKey);
return AjaxResult.success(result);
}
@PostMapping("/info")
public AjaxResult selectUserInfoByToken(HttpServletRequest request) {
String id = jwtConfig.getIdByToken(request.getHeader("Authorization"));
return AjaxResult.success(JsonUtils.beanToBean(iAccUserService.selectAccUserById(Long.parseLong(id)), AccUserBasicInformationRes.class));
}
@PostMapping("/info-by-id")
public AjaxResult selectUserInfoById(@Valid @RequestBody BaseVo baseVo) {
return AjaxResult.success(JsonUtils.beanToBean(iAccUserService.selectAccUserById(baseVo.getId()), AccUserBasicInformationRes.class));
}
/**
* 获取用户基础信息
*
* @return
*/
@PostMapping("/basic-info")
private AjaxResult getUserBasicInformation(@RequestBody @Valid BaseVo baseVo) {
return AjaxResult.success(JsonUtils.beanToBean(iAccUserService.selectAccUserById(baseVo.getId()), AccUserBasicInformationRes.class));
}
/**
* 微信绑定手机号
*
* @param weChatPhoneReq
* @return
*/
@PostMapping("/we-chat/bind-phone")
private AjaxResult weChatBindingPhone(@Valid @RequestBody WeChatPhoneReq weChatPhoneReq) {
boolean flag = smsService.checkIsCorrectCode(weChatPhoneReq.getPhone(), weChatPhoneReq.getCode());
if (!flag) {
return AjaxResult.error("短信验证码校验失败");
}
String code = weChatPhoneReq.getCode();
if ("xcx".equals(code)) {
String phone = WeChatDecrypt.wxDecrypt(weChatPhoneReq.getEncryptedData(), weChatPhoneReq.getSessionKey(), weChatPhoneReq.getIv());
if (StringUtils.isNotBlank(phone)) {
weChatPhoneReq.setPhone(phone);
} else {
return AjaxResult.error("获取关联手机号失败");
}
} else {
boolean flag = smsService.checkIsCorrectCode(weChatPhoneReq.getPhone(), code);
if (!flag) {
return AjaxResult.error("短信验证码校验失败");
}
}
Map<String, Long> m = new HashMap<>();
Long i = iAccUserService.weChatPhone(weChatPhoneReq);
if (i == 0L) {
return AjaxResult.error("微信绑定手机号失败");
} else if (i == 2L) {
return AjaxResult.error("手机号已存在账号,请用手机号登录膳食一度,并绑定此微信");
// } else if (i > 3L) {
// return AjaxResult.success(JsonUtils.beanToBean(iAccUserService.selectAccUserById(i), AccUserBasicInformationRes.class));
} else if (i == 1L) {
return AjaxResult.success("绑定成功");
}
//返回id
return AjaxResult.success("绑定手机号成功");
}
/**
* 解绑微信
*
* @param baseVo
* @return
*/
@PostMapping("/remove-bind-wechat")
private AjaxResult weChatRemoveBindingPhone(@Valid @RequestBody BaseVo baseVo) {
QueryWrapper<AccUserCredential> w = new QueryWrapper<>();
w.eq("user_id", baseVo.getId());
boolean i = userCredentialService.remove(w);
if (i) {
return AjaxResult.success("解绑成功");
} else {
return AjaxResult.error("解绑失败");
}
}
/**
* 绑定微信
*
* @param weChatBind
* @return
*/
@PostMapping("/bind-wechat")
private AjaxResult weChatBinding(@Valid @RequestBody WeChatBind weChatBind) {
UserInfoData userInfoData = new WeiXinAuthService().checkLogin(weChatBind.getAccount(), 0);
AccUserCredential userCredentialCheck = new AccUserCredential();
//根据联合ID查询
userCredentialCheck.setLoginAccount(userInfoData.getUnionId());
userCredentialCheck.setLoginType(1);
AccUserCredential userCredential = new AccUserCredential();
List<AccUserCredential> userCredentials = userCredentialService.selectAccUserCredentialList(userCredentialCheck);
if (null == userCredentials || userCredentials.isEmpty()) {
if (userInfoData != null) {
userCredential.setId(IDGeneratorUtils.getLongId());
userCredential.setLoginType(1);
userCredential.setUserId(weChatBind.getId());
userCredential.setLoginAccount(userInfoData.getUnionId());
userCredential.setCredential(userInfoData.getOpenId());
boolean i = userCredentialService.save(userCredential);
if (i) {
return AjaxResult.success("绑定成功");
}
}
} else {
Long weChatId = userCredentials.get(0).getUserId();
AccUser accUser = iAccUserService.getById(weChatId);
if (accUser.getUsername() != null && !"".equals(accUser.getUsername())) {
return AjaxResult.error("该微信已被绑定");
}
iAccUserService.removeById(weChatId);
userCredential.setId(userCredentials.get(0).getId());
userCredential.setUserId(weChatBind.getId());
boolean i = userCredentialService.updateById(userCredential);
if (i) {
return AjaxResult.success("成功");
}
}
return AjaxResult.error("绑定失败");
}
}
6. 辅助登录的工具类
敬请期待