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<String> unionLogin(@RequestParam("unionPublicId") String unionPublicId);

    /**
     * 聯合登陸回調接口
     *
     * @return
     */
    @GetMapping("/login/oauth/callback")
    String unionLoginCallback(@RequestParam("unionPublicId") String unionPublicId);

    /**
     * 查詢當前開通的渠道
     *
     * @return
     */
    @GetMapping("/unionLoginList")
    @ResponseBody
    BaseResponse<List<UnionLoginDto>> 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<String> 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<List<UnionLoginDto>> unionLoginList() {
        List<UnionLoginDo> unionLoginList = unionLoginMapper.selectByUnionLoginList();
        if (unionLoginList == null) {
            return setResultError("當前沒有可用渠道");
        }
        List<UnionLoginDto> 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<JSONObject> 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<JSONObject> 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、登錄首頁

2、點擊第三方登錄跳轉到掃碼界面

手機掃碼時手機界面

 

進入確認關聯界面

輸入登錄賬號密碼進行關聯

如果對以上內容有所疑問的可以關注留言,轉載請說明出處

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章