Java实现了第三方qq账号登录...

实现思路

创业初期
归结为创业初期是因为这个时候用户量比较少,甚至还没有接入上面所说的其他第三方的账户系统,只是自建的体系就可以满足,自建体系的话,目前常用的有

用户名密码注册登陆
这种方式在很多初期网站建设会使用,先注册,再进行登录,在老一点的cms中都能找到这个影子。

流程图:
在这里插入图片描述

流程说明:

前端将用户名、密码发送到服务器,服务器进行常规的判断,判断用户名、密码长度是否满足,用户名是否重复等条件,条件不通过直接返回对应错误码给到前端,这里密码字段,为了防止传输过程中被截胡,建议加密再上传,我们的传输密码默认都是会进行一个md5加密,然后记录到数据库再进行一层加密,就算是脱库也没事,密码不要明文存储。

校验通过后,就将用户名密码写入数据库,并进行后面积分发放等操作,这里不展开。

现在进行登录,前端将用户名,密码发送给到服务端,服务端首先会校验登录次数是否超过设置的阈值,如果超过只能继续等待被关小黑屋。

如果未超过继续登录逻辑,判断用户名、密码是否正确,不正确密码则进行阈值的判断,如果超过则关小黑屋,记住小黑屋必须设置过期时间,要不然就会永久关上了,这个可以用redis的过期来做。

登录成功后进行后续的一切后置逻辑,比如加积分。。。等操作。

手机号注册登陆
流程图:

在这里插入图片描述

流程说明:

首先输入手机号,然后发送到服务端,服务端将手机号记录在我们数据库中,然后生成随机验证码,并将手机号和验证码绑定到一个redis里面,然后记录过期时间,这个过期时间一般是10分钟左右,这就是我们一般手机验证码的有效期。

手机接收到手机短信后,那么就在界面填写验证码发送服务端,服务端收到验证码后就会在redis里面查询到这个手机号对应的验证码,失败就返回错误码。

成功后就进行登录操作。

这里看起来没有明确的注册登录操作,其实在发送手机号码就可以认为是一个常规的注册,然后后面的验证码输入就是一个登陆操作,

问: 那我要密码咋办?

答: 在后续产品里面增加一个 手机号码密码补录的功能 即可,这也是现在很常规的手法,但是现在移动互联网大爆炸时代,密码已经显得不是那么重要了,反正我从来记不住密码,如果手机号码能操作的app,绝对不用密码来操作。

数据库设计
表结构 :

自增id 用户名 密码 手机号 错误次数
1 user1 7fef6171469e80d32c0559f88b377245 13456789012 0
2 user2 7fef6171469e80d32c0559f88b377245 13456789013 0
说明 :

这里只是单纯说明需要用到的数据,没有扩展具体场景,这个表结构能够满足上面两个方案的设计。

引入第三方账户方案
这里是以QQ-SDK的登录逻辑, 我们先来一波时序图

说明:

客户端自己调起登录的界面,进行输入用户名、密码,这里的是第三方的用户名,密码,登录成功后,会返回access_token openid expire_in,这过程会使用到oauth2.0,不过在sdk里面进行内置回调获取了,后面我们会说明我们自身实现的oauth2.0

客户端拿到access_token、openid、login_type(qq、wechat…)请求应用服务器,应用服务器拿到这些数据后就会根据对应的login_type去对应的用户中心进行access_token和openid进行校验。校验不通过则返回对应错误码
校验通过后就会判断本地是否有这个login_type和openid是否存在,不存在则进行获取远程的用户名、头像等基础信息来作为本地基础数据,并且返回code值

如果已经存在,那就是进行登录操作,返回code值。

客户端拿到code值后进行token值的换取,这个完全遵照oauth2.0的协议来走的,后续每次请求必须带上token,token值在服务端的时间比较久,因为我们想要做的是那种永不下线的操作,所以每次请求我们都将token过期时间进行累加。

数据库设计
根据部分小伙伴的的建议,我这里做一下数据库的整理:


用户基础表(users)

字段 备注
user_id 用户id
token 用户登陆的token
expire_in token过期时间
try_times 登录失败次数


用户验证关联表(user_auth_rel)

字段 备注
id 自增id
user_id 用户id
auth_id 验证表id
auth_type 验证类型(local、third)


本地用户表(user_local_auth)

字段 备注
auth_id 认证id,自增id
user_name 用户唯一标识
password 用户密码
mobile 用户手机


第三方用户表(user_third_auth)

字段 备注
auth_id 用户id
openid 第三方用户唯一标识
login_type 第三方平台标识(qq、wechat…)
access_token 第三方获取的access_token,校验使用
说明

users表只是单纯针对我们业务侧的登录,主要是做自身业务的oauth2.0业务,
user_local_auth是做自己用户名、密码登录,手机号码登录信息记录,
user_third_auth是我们第三方用户体系的数据记录,
user_auth_rel是用来关联我们users表与user_local_auth、user_third_auth。
整个设计理念就是将自建用户与第三方在存储上区分,这在架构演进上也是合乎情理的,开始用户体系大多自建,而后才是对外接入。

QQ登录

QQ互联注册一个账号
网站地址:https://connect.qq.com/,添加一个应用,具体怎么申请以及需要填写的信息,腾讯官网有详细文档。注册并完成相应信息填写后,可以在应用管理中查到应用的APP ID和APP Key。(注,这个申请还是比较麻烦的,申请了好几次,可能是脸黑吧)成功后如下图:
在这里插入图片描述
还需要添加一个回调地址,如下图:
在这里插入图片描述

加入jar包

<!-- 第三方QQ登录 -->
<dependency>
    <groupId>com.qq</groupId>
    <artifactId>Sdk4J</artifactId>
    <version>2</version>
</dependency>

登录页面

<button type="submit" class="btn btn-default" onclick="qqLogin()">qq登录</button>
function qqLogin() {
   window.open("/login/qqLogin","TencentLogin");
}

Controller编写

package com.gbq.boot.web.controller;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.gbq.boot.web.bean.User;
import com.gbq.boot.web.comment.qqLoginComment.AuthComment;
import com.gbq.boot.web.service.UserService;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;


/**
 * 登录Controller
 */
@RestController
@RequestMapping("/login")
public class LoginController {

    @Resource
    private UserService userService;
    @Resource
    private AuthComment authComment;


    @RequestMapping("/index")
    public ModelAndView index(@ModelAttribute("user") User user) {
        return new ModelAndView("/shop/index","user",user);
    }


    @RequestMapping("/login.html")
    public ModelAndView toLogin() {
        return new ModelAndView("login");
    }

    @RequestMapping("/qqLogin")
    public void qqLogin(HttpServletResponse response)throws Exception{
        //随机产生字符串
        String state = StrUtil.uuid();
        String url = authComment.getAuthUrl(state);
        System.out.println(url);
        //重定向
        response.sendRedirect(url);
    }


    @GetMapping("/redirect")
    public ModelAndView getData(@RequestParam(value = "code") String code, RedirectAttributes model){

        //获取token
        String accessToken = authComment.getAccessToken(code);
        System.out.println("accessToken"+accessToken);

        //获取openId
        String openId = authComment.getOpenId(accessToken);
        System.out.println("openId"+openId);

        //获取用户信息
        JSONObject userInfo = authComment.getUserInfo(accessToken, openId);
        String myName = userInfo.getString("nickname");
        User user = new User(null, "","111111",myName, System.currentTimeMillis(),"是",
                userInfo.getString("figureurl_2"), userInfo.getString("gender")
                ,1,1,"", "", openId);

        //通过openId查询
        User usr = userService.findUsrByOpenId(openId);
        if (null != usr){
            user.setId(usr.getId());
            userService.updateById(user);
        }else {
            userService.insert(user);
        }
        model.addFlashAttribute("user", user);
       //重定向
        return new ModelAndView("redirect:/login/index");
    }


}

AuthComment类编写

package com.gbq.boot.web.comment.qqLoginComment;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.apache.commons.io.IOUtils.toByteArray;

@Component
public class AuthComment {

    //QQ 登陆页面的URL
    private final static String AUTHORIZATION_URL =
            "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s";
    //获取token的URL
    private final static String ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s";
    // 获取用户 openid 的 URL
    private static final String OPEN_ID_URL = "https://graph.qq.com/oauth2.0/me?access_token=%s";
    // 获取用户信息的 URL,oauth_consumer_key 为 apiKey
    private static final String USER_INFO_URL = "https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s";
    // 下面的属性可以通过配置读取
    // QQ 在登陆成功后回调的 URL,这个 URL 必须在 QQ 互联里填写过
    private static final String CALLBACK_URL = "http://127.0.0.1:8080/login/redirect";
    // QQ 互联应用管理中心的 APP ID
    private static final String APP_ID = "你的id";
    // QQ 互联应用管理中心的 APP Key
    private static final String APP_SECRET = "你的key";


    /**
     * QQ 登陆页面的URL
     * @param scope
     * @return
     */
    public String getAuthUrl(String scope) {
        return String.format(AUTHORIZATION_URL, APP_ID, CALLBACK_URL, scope);
    }

    /**
     * 获取Access Token值
     */
    public String getAccessToken(String code){
        String ur = String.format(ACCESS_TOKEN_URL, APP_ID, APP_SECRET,code, CALLBACK_URL);
        String compile = "access_token=(\\w*)&";
        String result = this.getUrl(ur);
        return this.getMatcher(result,compile);
    }

    /**
     * 获取openId
     * @param accessToken
     * @return
     */
    public String getOpenId(String accessToken) {
        String url = String.format(OPEN_ID_URL, accessToken);
        String compile = "openid\":\"(\\w*)\"";
        String result = this.getUrl(url);
        return this.getMatcher(result,compile);
    }

    /**
     * 获取qq用户信息
     * @param accessToken
     * @param openId
     * @return
     */
    public JSONObject getUserInfo(String accessToken, String openId) {
        String url = String.format(USER_INFO_URL, accessToken, APP_ID, openId);
        String result = this.getUrl(url);
        return JSON.parseObject(result);
    }

    private String getMatcher(String result,String compile) {
        //使用正则表达式解析网址
        Pattern p = Pattern.compile(compile);
        Matcher m = p.matcher(result);
        m.find();
        return m.group(1);
    }

    //解析url
    private String getUrl(String ur) {
        try {
            URL url = new URL(ur);
            HttpURLConnection conn = null;
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5 * 1000);
            conn.setRequestMethod("GET");
            InputStream inStream = conn.getInputStream();
            byte[] data = toByteArray(inStream);
            String result = new String(data, "UTF-8");
            System.out.println(result);
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return  null;
    }

}

此处不再编写userService

成功会返回json串
在这里插入图片描述

其中主要需要的是,nickname——qq名称,figureurl_qq_x——不同尺寸的qq头像,等等等等!

登录成功跳转到页面
在这里插入图片描述

成功后的页面index

注意我使用的是freemarker模板,给大家贴上freemarker配置,已经mvc配置

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>freemarker:
  template-loader-path: classpath:/templates/
  # 是否启用模板缓存。
  suffix: .ftl
  cache: false
  # 模板编码。
  charset: UTF-8
  # 是否检查模板位置是否存在。
  check-template-location: true
  content-type: text/html
  #是否启用freemarker
  enabled: true

mvc:
  view:
    prefix: /templates/
    suffix: .html
  static-path-pattern: /static/**

总结

总的来讲,第三方用户的接入技术上来讲是比较简单的,这里设计多一个user_thirds是可以支持足够多的第三方接入,当然一般我们也就两三个登录就好,太多登录方不仅自身维护成本,界面摆盘也不好看不是。

希望大家能够通过以上学习,能够对于我们多账户登录有一个比较好的认知,这里设计方案不包含分表分库、没有服务化,就是简单直接的设计,当然用户量和需要的不一样,在这个基础上还要加很多东西,谢谢大家阅读

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
课程简介这是一门使用Java语言,SpringBoot框架,从0开发一个RESTful API应用,接近企业级的项目(我的云音乐),课程包含了基础内容,高级内容,项目封装,项目重构等知识,99%代码为手写;因为这是项目课程;所以不会深入到源码讲解某个知识点,以及原理,但会粗略的讲解下基础原理;主要是讲解如何使用系统功能,流行的第三方框架,第三方服务,完成接近企业级项目,目的是让大家,学到真正的企业级项目开发技术。适用人群刚刚毕业的学生想提高职场竞争力想学从零开发SpringBoot项目想提升SpringBoot项目开发技术想学习SpringBoot项目架构技术想学习企业级项目开发技术就是想学习SpringBoot开发能学到什么从0开发一个类似企业级项目学会能做出市面上90%通用API快速增加1到2年实际开发经验刚毕业学完后能找到满意的工作已经工作学完后最高涨薪30%课程信息全课程目前是82章,155小时,每节视频都经过精心剪辑。在线学习分辨率最高1080P课程知识点1~11章:学习方法,项目架构,编码规范,Postman使用方法,Git和Github版本控制12~16章:搭建开发环境,快速入门SpringBoot框架17~20章:快速入门MySQL数据库21~30章:MyBatis,登录注册,找回密码,发送短信,发送邮件,企业级接口配置31~41章:实现歌单,歌单标签,音乐,列表分页,视频,评论,好友功能42~48章:阿里云OSS,话题,MyBatis-plus,应用监控49~53章:Redis使用,集成Redis,SpringCache,HTTP缓存54~58章:Elasticsearch使用,集成Elasticsearch,使用ES搜索59~61章:商城,集成支付宝SDK,支付宝支付62~64章:常用哈希和加密算法,接口加密和签名65~67章:实时挤掉用户,企业级项目测试环境,企业级接口文档68~69章:SpringBoot全站HTTPS,自签证书,申请免费证书70~73章:云MySQL数据库,云Redis数据库使用,轻量级应用部署环境,域名解析74~80章:Docker使用,生产级Kubernetes集群,域名解析,集群全站HTTPS81~82章:增强和重构项目,课程总结,后续学习计划
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值