引言

上一章节完成了用户登出的功能,本章讲解QQ授权登录业务流程,如图所示
在这里插入图片描述

1.OAuth理论基础

1.1.什么是开放平台

在一些大型互联网公司,随着公司的业务发展逐渐庞大,需要和外部合伙伙伴进行合作,需要将公司的接口开放给外部其他合伙伙伴进行调用。

比如腾讯的QQ互联网(一键登录)、微信开放平台、蚂蚁金服开放平台 、微博开放平台,比如实现功能QQ联合登陆、微信扫码登陆。

还有就是在大型集团公司中,分为总公司,和旗下多个分公司,总公司与分公司相互通讯也可以采用开放平台形式对接口进行授权。

1.2.什么是OAuth2.0

1.2.1.OAuth

OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问让他们存储在另外的服务提供者上的的信息,而不需要讲用户名和密码提供给第三方网站或者分享他们数据的所有内容.

1.2.2.QQ登录OAuth2.0

对于用户相关的OpenAPI(例如获取用户信息,动态同步,照片,日志,分享等)。为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。采用OAuth2.0标准协议来进行用户身份验证和获取用户授权,相对于之前的OAuth1.0协议,其认证流程更简单和安全。

1.2.3.具体概念

  • appId:商户号 永久不能改
  • appKey:商户秘钥 这个是可以改的
  • 授权码Code:获取accessToken,只有10分钟有效
  • 回调地址:授权成功,重定向地址
  • openid:开放平台生产唯一的用户id

1.3.OAuth授权原理

OAuth认证和授权的过程如下:

  1. 用户访问第三方网站,想对用户存放在服务商的某些资源进行操作。
  2. 第三方网站向服务商请求一个临时令牌。
  3. 服务商验证第三方网站的身份后,授予一个临时令牌。
  4. 第三方网站获得临时令牌后,将用户导向至服务商的授权页面请求用户授权,然后这个过程中将临时令牌和第三方网站的返回地址发送给服务商
  5. 用户在服务商的授权页面上输入自己的用户名和密码,授权第三方网站访问所相应的资源。
  6. 授权成功后,服务商将用户导向第三方网站的返回地址。
  7. 第三方网站根据临时令牌从服务商那里获取访问令牌。
  8. 服务商根据令牌和用户的授权情况授予第三方网站访问令牌。
  9. 第三方网站使用获取到的访问令牌访问存放在服务商的对应的用户资源。

1.4.调用QQ互联步骤

  1. 生成QQ联合登陆授权链接,会跳转到授权界面(如下图:): url:https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=申请后的clientId&redirect_uri=申请授权成功后跳转的url
    在这里插入图片描述

  2. (腾讯)用户选择账号后,使用重定向方式跳转回调地址 http://跳转的url 传递一个code参数

  3. code作为授权码,使用code参数获取accessToken

  4. 使用accessToken获取用户信息(openid、头像、QQ年龄之类)

1.5.项目架构图

在这里插入图片描述

2.QQ联合登录代码实现

腾讯开放平台网址: https://connect.qq.com/index.html
腾讯开放平台文档: http://wiki.connect.qq.com/

代码实现思路:

1.编写授权链接接口
2.编写授权回调接口
### 获取到授权码
### 使用授权码获取accessToken
### 使用accessToken获取用户openid
3.使用openid查询数据库user信息表中是否有关联

2.1.环境准备

将腾讯开放平台Sdk4J放入本地Maven私服仓库中:

mvn install:install-file -Dfile=jar包的位置(参数一) -DgroupId=groupId(参数二) -DartifactId=artifactId(参数三) -Dversion=version(参数四) -Dpackaging=jar
 
 
mvn install:install-file -Dfile="D:\Sdk4J.jar" -DgroupId=com.tengxun -DartifactId=sdk4j  -Dversion=1.0 -Dpackaging=jar

引入依赖

<dependency>
	<groupId>com.tengxun</groupId>
	<artifactId>sdk4j</artifactId>
	<version>1.0</version>
</dependency>

在这里插入图片描述
配置文件:qqconnectconfig.properties
在这里插入图片描述

2.2.代码编写

1)生成授权链接

package com.guoranxinxian.member.controller;

import com.guoranxinxian.common.base.BaseWebController;
import com.guoranxinxian.member.feign.MemberLoginServiceFeign;
import com.qq.connect.oauth.Oauth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

/**
 * QQ授权
 */
@Controller
@Slf4j
public class QQAuthoriController extends BaseWebController {

    /**
     * 生成QQ授权回调地址
     *
     * @return
     */
    @RequestMapping("/qqAuth")
    public String qqAuth(HttpServletRequest request) {
        try {
            String authorizeURL = new Oauth().getAuthorizeURL(request);
            return "redirect:" + authorizeURL;
        } catch (Exception e) {
            return ERROR_500_FTL;
        }
    }
}

2)QQ授权回调

package com.guoranxinxian.member.controller;

import com.alibaba.fastjson.JSONObject;
import com.guoranxinxian.api.BaseResponse;
import com.guoranxinxian.common.base.BaseWebController;
import com.guoranxinxian.common.constants.WebConstants;
import com.guoranxinxian.common.util.CookieUtils;
import com.guoranxinxian.constants.Constants;
import com.guoranxinxian.member.feign.MemberLoginServiceFeign;
import com.qq.connect.api.OpenID;
import com.qq.connect.api.qzone.UserInfo;
import com.qq.connect.javabeans.AccessToken;
import com.qq.connect.javabeans.qzone.UserInfoBean;
import com.qq.connect.oauth.Oauth;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * QQ授权
 */
@Controller
@Slf4j
public class QQAuthoriController extends BaseWebController {

    @Autowired
    private QQAuthoriFeign qqAuthoriFeign;

    private static final String MB_QQ_QQLOGIN = "member/qqlogin";

    @Autowired
    private MemberLoginServiceFeign memberLoginServiceFeign;
    /**
     * 重定向到首页
     */
    private static final String REDIRECT_INDEX = "redirect:/";

    /**
     * 生成QQ授权回调地址
     *
     * @return
     */
    @RequestMapping("/qqAuth")
    public String qqAuth(HttpServletRequest request) {
        try {
            String authorizeURL = new Oauth().getAuthorizeURL(request);
            return "redirect:" + authorizeURL;
        } catch (Exception e) {
            return ERROR_500_FTL;
        }
    }

    /**
     * 回调
     * @param code
     * @return
     */
    @RequestMapping("/qqLoginBack")
    public String qqLoginBack(String code, HttpServletRequest request, HttpServletResponse response, HttpSession httpSession) {
        try {
            // 使用授权码获取accessToken
            AccessToken accessTokenObj = (new Oauth()).getAccessTokenByRequest(request);
            if (accessTokenObj == null) {
                return ERROR_500_FTL;
            }
            String accessToken = accessTokenObj.getAccessToken();
            if (StringUtils.isEmpty(accessToken)) {
                return ERROR_500_FTL;
            }
            // 使用accessToken获取用户openid
            OpenID openIDObj = new OpenID(accessToken);
            String openId = openIDObj.getUserOpenID();
            if (StringUtils.isEmpty(openId)) {
                return ERROR_500_FTL;
            }
            // 使用openid 查询数据库是否已经关联账号信息
            BaseResponse<JSONObject> findByOpenId = qqAuthoriFeign.findByOpenId(openId);
            if (!isSuccess(findByOpenId)) {
                return ERROR_500_FTL;
            }
            // 如果调用接口返回203 ,跳转到关联账号页面
            if (findByOpenId.getRtnCode().equals(Constants.HTTP_RES_CODE_NOTUSER_203)) {
                // 页面需要展示 QQ头像
                UserInfo qzoneUserInfo = new UserInfo(accessToken, openId);
                UserInfoBean userInfoBean = qzoneUserInfo.getUserInfo();
                if (userInfoBean == null) {
                    return ERROR_500_FTL;
                }
                // 用户的QQ头像
                String avatarURL100 = userInfoBean.getAvatar().getAvatarURL100();
                request.setAttribute("avatarURL100", avatarURL100);
                // 需要将openid存入在session中
                httpSession.setAttribute(WebConstants.LOGIN_QQ_OPENID, openId);
                return MB_QQ_QQLOGIN;
            }
            // 如果能够查询到用户信息的话,直接自动登陆
            // 自动实现登陆
            JSONObject data = findByOpenId.getData();
            String token = data.getString("token");
            CookieUtils.setCookie(request, response, WebConstants.LOGIN_TOKEN_COOKIENAME, token);
            return REDIRECT_INDEX;

        } catch (Exception e) {
            return ERROR_500_FTL;
        }
    }

}

3)QQAuthoriFeign

package com.guoranxinxian.member.feign;

import org.springframework.cloud.openfeign.FeignClient;

@FeignClient("guoranxinxian-shop-service-member")
public interface QQAuthoriFeign extends QQAuthoriService {
}

4)QQAuthoriService

package com.guoranxinxian.service;

import com.alibaba.fastjson.JSONObject;
import com.guoranxinxian.api.BaseResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 用户授权接口
 */
public interface QQAuthoriService {
    /**
     * 根据 openid查询是否已经绑定,如果已经绑定,则直接实现自动登陆
     *
     * @param qqOpenId
     * @return
     */
    @RequestMapping("/findByOpenId")
    BaseResponse<JSONObject> findByOpenId(@RequestParam("qqOpenId") String qqOpenId);
}

5)QQAuthoriServiceImpl

package com.guoranxinxian.impl;

import com.alibaba.fastjson.JSONObject;
import com.guoranxinxian.api.BaseResponse;
import com.guoranxinxian.constants.Constants;
import com.guoranxinxian.entity.BaseApiService;
import com.guoranxinxian.entity.UserDo;
import com.guoranxinxian.mapper.UserMapper;
import com.guoranxinxian.service.QQAuthoriService;
import com.guoranxinxian.util.GenerateToken;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class QQAuthoriServiceImpl extends BaseApiService<JSONObject> implements QQAuthoriService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private GenerateToken generateToken;

    @Override
    public BaseResponse<JSONObject> findByOpenId(String qqOpenId) {
        // 1.根据qqOpenId查询用户信息
        if (StringUtils.isEmpty(qqOpenId)) {
            return setResultError("qqOpenId不能为空!");
        }
        // 2.如果没有查询到 直接返回状态码203
        UserDo userDo = userMapper.findByOpenId(qqOpenId);
        if (userDo == null) {
            return setResultError(Constants.HTTP_RES_CODE_NOTUSER_203, "根据qqOpenId没有查询到用户信息");
        }
        // 3.如果能够查询到用户信息的话 返回对应用户信息token
        String keyPrefix = Constants.MEMBER_TOKEN_KEYPREFIX + "QQ_LOGIN";
        Long userId = userDo.getUserId();
        String userToken = generateToken.createToken(keyPrefix, userId + "");
        JSONObject data = new JSONObject();
        data.put("token", userToken);
        return setResultSuccess(data);
    }

}

(3)QQ绑定请求

   @RequestMapping("/qqJointLogin")
    public String qqJointLogin(@ModelAttribute("loginVo") LoginVo loginVo, Model model, HttpServletRequest request,
                               HttpServletResponse response, HttpSession httpSession) {

        // 1.获取用户openid
        String qqOpenId = (String) httpSession.getAttribute(WebConstants.LOGIN_QQ_OPENID);
        if (StringUtils.isEmpty(qqOpenId)) {
            return ERROR_500_FTL;
        }

        // 2.将vo转换dto调用会员登陆接口
        UserLoginInDTO userLoginInpDTO = BeanUtils.voToDto(loginVo, UserLoginInDTO.class);
        userLoginInpDTO.setQqOpenId(qqOpenId);
        userLoginInpDTO.setLoginType(Constants.MEMBER_LOGIN_TYPE_PC);
        String info = webBrowserInfo(request);
        userLoginInpDTO.setDeviceInfor(info);
        BaseResponse<JSONObject> login = memberLoginServiceFeign.login(userLoginInpDTO);
        if (!isSuccess(login)) {
            setErrorMsg(model, login.getMsg());
            return MB_QQ_QQLOGIN;
        }
        // 3.登陆成功之后如何处理 保持会话信息 将token存入到cookie 里面 首页读取cookietoken 查询用户信息返回到页面展示
        JSONObject data = login.getData();
        String token = data.getString("token");
        CookieUtils.setCookie(request, response, WebConstants.LOGIN_TOKEN_COOKIENAME, token);
        return REDIRECT_INDEX;
    }
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐