VUE实现QQ联合登录

说明:本篇博客本人安装小白思路进行书写,以下内容及第三方跳转环境主要来源于余胜军,请大家在转载的时候说明来源出处。

一、实现思路

1.在登录页面,从后台查询出可以使用的联合登录接口(含第三方登录头像、requestAddress

2、点击头像,进入QQ扫码界面

3、QQ扫码后,根据requestAddress中的回调地址,进入后台联合登录自定义的回调方法unionLoginCallback

4、回调方法中获取用户的openId 组装后返回到定义的前台关联页面(根据openId查询是否用户以关联,如果关联就把用户信息传递到关联账号页面,用户点击时直接跳转至首页,以下流程是没有进行关联的情况)

5、点击《关联到已有账号》跳转到关联账号页面,并把用户openId传递到该页面

6、用户在关联账号页面输入该平台的登录账号密码,并把该用户的openId一起传递到后台,该调用方法为该平台的登录接口,如果输入的账号密码正确,则将该用户的openId添加到该用户对应的数据库数据中并跳转至首页。

二、开发代码

(一)数据库数据

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50647
 Source Host           : localhost:3306
 Source Schema         : cyb

 Target Server Type    : MySQL
 Target Server Version : 50647
 File Encoding         : 65001

 Date: 12/04/2020 02:42:17
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for meite_user
-- ----------------------------
DROP TABLE IF EXISTS `meite_user`;
CREATE TABLE `meite_user`  (
  `USER_ID` int(12) NOT NULL AUTO_INCREMENT COMMENT 'user_id',
  `MOBILE` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '手机号',
  `PASSWORD` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
  `USER_NAME` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `SEX` tinyint(1) NULL DEFAULT 0 COMMENT '性别  1男  2女',
  `AGE` tinyint(3) NULL DEFAULT 0 COMMENT '年龄',
  `CREATE_TIME` timestamp(0) NULL DEFAULT NULL COMMENT '注册时间',
  `IS_AVALIBLE` tinyint(1) NULL DEFAULT 1 COMMENT '是否可用 1正常  2冻结',
  `PIC_IMG` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户头像',
  `QQ_OPENID` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'QQ联合登陆id',
  `WX_OPENID` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '微信公众号关注id',
  PRIMARY KEY (`USER_ID`) USING BTREE,
  UNIQUE INDEX `MOBILE_UNIQUE`(`MOBILE`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 87 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户会员表' ROW_FORMAT = Compact;

-- ----------------------------
-- Records of meite_user
-- ----------------------------
INSERT INTO `meite_user` VALUES (86, '18774833827', 'E10ADC3949BA59ABBE56E057F20F883E', '1', 1, 0, '2020-03-15 22:34:45', 0, '1', '3EAB229E0EAAB047174224A5845B224E', 'oOX38w3WD3JUjL5ORcr4OADNqfSw');

SET FOREIGN_KEY_CHECKS = 1;

(二)联合登录接口

import com.cyb.base.BaseResponse;
import com.cyb.member.api.dto.resp.UnionLoginDto;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

@Api(tags = "联合登陆接口")
public interface MemberUnionLoginService {


    /**
     * 根据不同的联合登陆id
     *
     * @param unionPublicId
     * @return
     */
    @GetMapping("/unionLogin")
    BaseResponse unionLogin(@RequestParam("unionPublicId") String unionPublicId);

    /**
     * 联合登陆回调接口
     *
     * @return
     */
    @GetMapping("/login/oauth/callback")
    String unionLoginCallback(@RequestParam("unionPublicId") String unionPublicId);

    /**
     * 查询当前开通的渠道
     *
     * @return
     */
    @GetMapping("/unionLoginList")
    @ResponseBody
    BaseResponse> unionLoginList();
}

(三)联合登录实现类

import com.alibaba.fastjson.JSONObject;
import com.cyb.base.BaseApiService;
import com.cyb.base.BaseResponse;
import com.cyb.bean.CybBeanUtils;
import com.cyb.member.api.dto.resp.UnionLoginDto;
import com.cyb.member.api.service.MemberUnionLoginService;
import com.cyb.member.impl.entitydo.UnionLoginDo;
import com.cyb.member.impl.mapper.UnionLoginMapper;
import com.cyb.member.impl.strategy.UnionLoginStrategy;
import com.cyb.utils.SpringContextUtils;
import com.cyb.utils.TokenUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.List;


//@RestController
@Controller
@CrossOrigin
public class MemberUnionLoginServiceImpl extends BaseApiService implements MemberUnionLoginService {

    @Autowired
    private UnionLoginMapper unionLoginMapper;
    @Autowired
    private TokenUtils tokenUtils;
    @Value("${cyb.login.vue.bindingurl}")
    private String bindingurl;

    @Override
    public BaseResponse unionLogin(String unionPublicId) {
        if (StringUtils.isEmpty(unionPublicId)) {
            return setResultError("unionPublicId不能为空");
        }
        // 根据渠道id查询 联合基本信息
        UnionLoginDo unionLoginDo = unionLoginMapper.selectByUnionLoginId(unionPublicId);
        if (unionLoginDo == null) {
            return setResultError("该渠道可能已经关闭或者不存在");
        }
        String state = tokenUtils.createToken("member.unionLogin", "");
        String requestAddres = unionLoginDo.getRequestAddress() + "&state=" + state;
        JSONObject dataObjects = new JSONObject();
        dataObjects.put("requestAddres", requestAddres);
        return setResultSuccess(dataObjects);

    }

    @Override
    public String unionLoginCallback(String unionPublicId) {
        // 根据渠道id查询 联合基本信息
        UnionLoginDo unionLoginDo = unionLoginMapper.selectByUnionLoginId(unionPublicId);
        String unionBeanId = unionLoginDo.getUnionBeanId();
        //  从Spring容器中根据beanid 查找到我们的策略类
        UnionLoginStrategy unionLoginStrategy = SpringContextUtils.getBean(unionBeanId, UnionLoginStrategy.class);
        // 根据当前线程获取request对象
        HttpServletRequest request = ((ServletRequestAttributes)
                (RequestContextHolder.currentRequestAttributes())).getRequest();
        String openId = unionLoginStrategy.unionLoginCallback(request, unionLoginDo);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("openId", openId);
        jsonObject.put("unionPublicId", unionPublicId);
        String openToken =
                tokenUtils.createToken("mayikt.unionLogin.", jsonObject.toJSONString());
        return "redirect:" + bindingurl + openToken;
    }

    @Override
    public BaseResponse> unionLoginList() {
        List unionLoginList = unionLoginMapper.selectByUnionLoginList();
        if (unionLoginList == null) {
            return setResultError("当前没有可用渠道");
        }
        List unionLoginDtos = CybBeanUtils.doToDtoList(unionLoginList, UnionLoginDto.class);
        return setResultSuccess(unionLoginDtos);
    }
}

 

(四)联合登录策略模式接口

    /*
    * 联合登录回调
    * @Author 陈远波
    * @Date 2020-04-12
     * @param null
    * @return
    */
    String unionLoginCallback(HttpServletRequest request, UnionLoginDo unionLoginDo);

    /*
    * 根据用户openID获取渠道信息
    * @Author 陈远波
    * @Date 2020-04-12
     * @param openId 用户openID
    * @return
    */
    UserDo getDbOpenId(String openId);

    /*
    * 根据用户id修改用户的openID
    * @Author 陈远波
    * @Date 2020-04-12
     * @param userId   用户id
     * @param openId   用户openId
    * @return
    */
    int updateUseOpenId(Long userId,String openId);
}

(五)QQ联合登录策略模式实现类

@Component
public class QQUnionLoginStrategy implements UnionLoginStrategy {
    @Value("${cyb.login.qq.accesstoken}")
    private String qqAccessTokenAddres;
    @Value("${cyb.login.qq.openid}")
    private String qqOpenIdAddres;
    @Autowired
    private UserMapper userMapper;
    @Override
    public String unionLoginCallback(HttpServletRequest request, UnionLoginDo unionLoginDo) {
        String code = request.getParameter("code");
        if (StringUtils.isEmpty(code)) {
            return null;
        }
        //1.根据授权码获取accessToken
        // 1.根据授权码获取accessToken
        String newQQAccessTokenAddres = qqAccessTokenAddres.replace("{client_id}"
                , unionLoginDo.getAppId()).replace("{client_secret}", unionLoginDo.getAppKey()).
                replace("{code}", code).replace("{redirect_uri}", unionLoginDo.getRedirectUri());
        String resultAccessToken = HttpClientUtils.httpGetResultString(newQQAccessTokenAddres);
        boolean contains = resultAccessToken.contains("access_token=");
        if (!contains) {
            return null;
        }
        String[] split = resultAccessToken.split("=");
        String accessToken = split[1];
        if (StringUtils.isEmpty(accessToken)) {
            return null;
        }
        // 2.根据accessToken获取用户的openid
        String resultQQOpenId = HttpClientUtils.httpGetResultString(qqOpenIdAddres + accessToken);
        if (StringUtils.isEmpty(resultQQOpenId)) {
            return null;
        }
        boolean openid = resultQQOpenId.contains("openid");
        if (!openid) {
            return null;
        }

        String array[] = resultQQOpenId.replace("callback( {", "").replace("} );",
                "").replace("\"", "").trim().split(":");
        String openId = array[2];

        return openId;
    }

    @Override
    public UserDo getDbOpenId(String openId) {
       return userMapper.selectByQQOpenId(openId);
    }

    @Override
    public int updateUseOpenId(Long userId, String openId) {
        return userMapper.updateUserOpenId(userId,openId);
    }
}

 

(六)登录接口

@RestController
@Api(tags = "会员登录服务")
public interface MemberLoginService {
    /*
    *
    * @Author 陈远波
    * @Date 2020-03-25
     * @param @RequestHeader("X-Real-IP")  从nginx请求头中获取 浏览器真实ip
     * @RequestHeader("channel")  从请求头中获取登录来源 pc,安卓,iOS
    * @return
    */
    @PostMapping("/login")
    @ApiOperation(value = "会员登录",notes = "接收参数进行序列化")
    BaseResponse login(@RequestBody UserLoginDto userLoginDto, @RequestHeader("X-Real-IP")
            String sourceIp, @RequestHeader("channel") String channel, @RequestHeader("deviceInfor") String deviceInfor);
}

录接口实现

 

@RestController
@Slf4j
@CrossOrigin
public class MemberLoginServiceImpl extends BaseApiService implements MemberLoginService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private TokenUtils tokenUtils;
    @Value("${cyb.login.token.prefix}")
    private String loginTokenPrefix;
    @Autowired
    private AsyncLoginLogManage asyncLoginLogManage;
    @Autowired
    private UserLoginLogMapper userLoginLogMapper;
    @Autowired
    private ChannelUtils channelUtils;

    @Override
    public BaseResponse login(UserLoginDto userLoginDto, String sourceIp
            , String channel, String deviceInfor) {
        // 参数验证
        String mobile = userLoginDto.getMobile();
        if (StringUtils.isEmpty(mobile)) {
            return setResultError("mobile参数不能为空");
        }
        String passWord = userLoginDto.getPassWord();
        if (StringUtils.isEmpty(userLoginDto.getPassWord())) {
            return setResultError("passWord参数不能为空");
        }
        if (!channelUtils.existChannel(channel)) {
            return setResultError("登陆类型出现错误!");
        }
        // 查询我们的数据库
        String newPassWord = MD5Util.MD5(passWord);
        UserDo loginUserDo = userMapper.login(mobile,newPassWord);
        if (loginUserDo == null) {
            return setResultError("手机号码或者密码不正确!");
        }
        // 设备信息
        if (StringUtils.isEmpty(deviceInfor)) {
            return setResultError("设备信息不能为空!");
        }

        //获取userId
        Long userId = loginUserDo.getUserId();
        String userToken = tokenUtils.createToken(loginTokenPrefix, userId+"");

        JSONObject resultJSON = new JSONObject();
        resultJSON.put("userToken", userToken);
        String wxOpenId = loginUserDo.getWxOpenId();
        String openIdToken = userLoginDto.getOpenIdToken();
        // 写入日志
        log.info(Thread.currentThread().getName() + " 处理流程1");
        asyncLoginLogManage.loginLog(openIdToken,wxOpenId, mobile,userId, sourceIp, new Date(), userToken
                , channel, deviceInfor);
        log.info(Thread.currentThread().getName() + " 处理流程3");
        return setResultSuccess(resultJSON);
    }
    public void loginLog(Long userId, String loginIp, Date loginTime, String loginToken, String channel,
                         String equipment) {
        UserLoginLogDo userLoginLogDo = new UserLoginLogDo(userId, loginIp, loginTime, loginToken, channel, equipment);
        log.info(Thread.currentThread().getName() + ",userLoginLogDo:" + userLoginLogDo.toString() + ",流程2");
        userLoginLogMapper.insertUserLoginLog(userLoginLogDo);
        log.info(Thread.currentThread().getName() + " 处理流程2");
    }
}

(七)配置文件

cyb:
   login:
      token:
        prefix: memberlogin
        channel: pc,android,ios
      qq:
        accesstoken: https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id={client_id}&client_secret={client_secret}&code={code}&redirect_uri={redirect_uri}
        openid: https://graph.qq.com/oauth2.0/me?access_token=
      wx:
        accesstoken: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
      vue:
        bindingurl: http://127.0.0.1:8849/mayikt_mt_shop/relation_login.html?openIdToken=

 

三、注意事项: 

在此过程中会遇到跨域的错误

解决办法有很多,在此本人使用注解的形式解决,具体解决方案有:

1.在响应头中设置允许跨域的 只适合于小公司

响应配置response.setHeader("Access-Control-Allow-Origin", "*");

2.使用HttpClient转发 效率低

3.使用jsonp处理,jsonp最大的缺陷支持get请求不支持post请求

4.使用nginx配置浏览器访问的项目与接口项目的域名或者端口号码一致性。

    www.mayikt.com/vue 转发到vue项目

        www.mayikt.com/api 转发到接口项目

5.可以直接在nginx中配置允许跨域的代码

    "Access-Control-Allow-Origin", "*"

6.网关中也可以配置类似与nginx允许跨域的代码

    "Access-Control-Allow-Origin", "*"

7.使用SpringBoot注解形式解决跨域问题@CrossOrigin

8.使用微服务网关也可以配置配置浏览器访问的项目与接口项目的域名或者端口号码一致性。

四、效果展示

 1、登录首页VUE实现QQ联合登录_第1张图片

2、点击第三方登录跳转到扫码界面

VUE实现QQ联合登录_第2张图片

手机扫码时手机界面

 

VUE实现QQ联合登录_第3张图片

进入确认关联界面

VUE实现QQ联合登录_第4张图片

输入登录账号密码进行关联

VUE实现QQ联合登录_第5张图片

如果对以上内容有所疑问的可以关注留言,转载请说明出处

 

 

 

 

 

 

 

你可能感兴趣的:(VUE实现QQ联合登录)