网站实现qq登录(springboot后台)

成果展示:www.qihea.xyz

参考:https://blog.csdn.net/a992970569/article/details/82107899

1.申请qq互联权限:

①:打开qq互联官网:https://connect.qq.com/index.html登录qq,然后点击登陆后的头像(当时找入口找了半天?)进行个人开发者信息认证。

这个认证大概要半天时间。认证成功后就可以申请应用了。

②:点击应用管理,创建应用,填写信息。可以先随便写一个,过两个小时就显示就申请失败了。不过没关系的,因为我们这时候失败的申请也能用。不过只能登陆自己的qq,用来做测试。

网站实现qq登录(springboot后台)_第1张图片

我写的地址和回调地址:(其他随便写,反正是用来做测试的)

网站实现qq登录(springboot后台)_第2张图片

③:找到自己应用的app ID和app KEY。之后会用到。

网站实现qq登录(springboot后台)_第3张图片

2.前端页面设置按钮:

js:login()函数代码:

//在新标签页打开网站
function login(){
  window.open("/logincheck","TencentLogin",
   "width=450,height=320,menubar=0,scrollbars=1,resizable=1,status=1,titlebar=0,toolbar=0,location=1");
}

即点击后打开如下页面网站实现qq登录(springboot后台)_第4张图片

logincheck页面完成的操作会在之后提到,注意这种操作在手机端打开失败,暂时还没有处理。

3.完成后台:

qq互联只提供jsSDK和PHPSDK,没有javaSDK,可是使用jsSDK自我定制度低,而且不使用SDK,按照流程开发也很简单:

QQ登录OAuth2.0总体处理流程如下:
Step1:申请接入,获取appid和apikey;
Step2:开发应用,并设置协作者帐号进行测试联调;
Step3:放置QQ登录按钮;
Step4:通过用户登录验证和授权,获取Access Token;

参考官方文档:使用Authorization_Code获取Access_Token,过程描述已经很详细了。我叙述一下我的完成过程。

①:生成state值,保存到session,然后携带此state值跳转到qq互联获取Authorization Code。

上面的js代码访问的是logincheck页面。代码为:

@GetMapping("/logincheck")
    public String loginUrl(HttpServletRequest request){
        //获取当前sesion
        HttpSession sessoin=request.getSession();
        //随机产生字符串
        String state=getRandomString(10);
        sessoin.setAttribute("state",state);
        //重定向
        return "redirect:https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id="
      + 101533009 + "&redirect_uri=" + "http://127.0.0.1:8080/recall" + "&state=" + state;
    }

这一步主要是获取一个state值加入到session,state值是用来防止CSRF攻击的,自己随机产生一段字符串此值。

产生随机字符串代码:

//length用户要求产生字符串的长度
    public String getRandomString(int length){
        String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random=new Random();
        StringBuffer sb=new StringBuffer();
        for(int i=0;i

跳转到qq互联的请求网址:
url = "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=" + appid + "&redirect_uri=" + redirectURI + "&state=" + state

参数 是否必须 含义
response_type 必须 授权类型,此值固定为“code”。
client_id 必须 申请QQ登录成功后,分配给应用的appid。
redirect_uri 必须 成功授权后的回调地址,必须是注册appid时填写的主域名下的地址,建议设置为网站首页或网站的用户中心。注意需要将url进行URLEncode。
state 必须 client端的状态值。用于第三方应用防止CSRF攻击,成功授权后回调时会原样带回。请务必严格按照流程检查用户与state参数状态的绑定。

 

此时用户得到网站实现qq登录(springboot后台)_第5张图片这样的登录页面。

如果用户成功登录并授权,则会跳转到指定的回调地址,并在redirect_uri地址后带上Authorization Code和原始的state值。

Step5:通过Access Token获取用户的OpenID;

参考官方文档:获取用户OpenID_OAuth2.0
Step6:调用OpenAPI,来请求访问或修改用户授权的资源。

参考官方文档:OpenAPI调用说明_OAuth2.0

注意我用的是springboot后台,这个getData函数中用到了usrService。需要在类中。

@Autowired
private UsrService usrService;

因为获取qq互联认证还是比较快的,且返回数据格式简单,所以直接在服务器进行申请,而不是通过js代码在浏览器进行qq互联申请了。

讲解回调函数流程:

1.因为上面带着state值和Authorization Code值来到的回调页面/recall,所以先进行state值的验证,即从服务器中此用户session取出之前保存的state值,然后和带来的state值进行比较,如果相同继续向下进行。

2.通过Authorization Code获取Access Token

url = " https://graph.qq.com/oauth2.0/token? grant_type=authorization_code

&client_id=" + appid + "&client_secret="+ appkey + "&redirect_uri=" + redirectURI + "&code=" + code

请求参数请包含如下内容:

参数 是否必须 含义
grant_type 必须 授权类型,在本步骤中,此值为“authorization_code”。
client_id 必须 申请QQ登录成功后,分配给网站的appid。
client_secret 必须 申请QQ登录成功后,分配给网站的appkey。
code 必须 上一步返回的authorization code。
如果用户成功登录并授权,则会跳转到指定的回调地址,并在URL中带上Authorization Code。
例如,回调地址为www.qq.com/my.php,则跳转到:
http://www.qq.com/my.php?code=520DD95263C1CFEA087******
注意此code会在10分钟内过期。
redirect_uri 必须 与上面一步中传入的redirect_uri保持一致。

如果成功返回,即可在返回包中获取到Access Token。 如:
access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14。

参数说明 描述
access_token 授权令牌,Access_Token。
expires_in 该access token的有效期,单位为秒。
refresh_token 在授权自动续期步骤中,获取新的Access_Token时需要提供的参数。

然后通过正则表达式截取,正则表达式的截取见代码。

3.获取用户OpenID_OAuth2.0:

url = "https://graph.qq.com/oauth2.0/me?access_token=" + access_token

access_token 必须 在Step1中获取到的access token。

PC网站接入时,获取到用户OpenID,返回包如下:

callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );

openid是此网站上唯一对应用户身份的标识,网站可将此ID进行存储便于用户下次登录时辨识其身份,或将其与用户在网站上的原有账号进行绑定。

这一步我也是通过正则表达式截取的,因为他还有个callback字符串,没法直接转化为类。

4.OpenAPI调用说明_OAuth2.0

1. 以get_user_info接口为例:
(请将access_token,appid等参数值替换为你自己的)

https://graph.qq.com/user/get_user_info?access_token=YOUR_ACCESS_TOKEN&oauth_consumer_key=YOUR_APP_ID&openid=YOUR_OPENID

2. 成功返回后,即可获取到用户数据,获取你感兴趣的数据:

{
"ret":0,
"msg":"",
"nickname":"Peter",
"figureurl":"http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/30",
"figureurl_1":"http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/50",
"figureurl_2":"http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/100",
"figureurl_qq_1":"http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/40",
"figureurl_qq_2":"http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/100",
"gender":"男",
"is_yellow_vip":"1",
"vip":"1",
"yellow_vip_level":"7",
"level":"7",
"is_yellow_year_vip":"1"
}

这个返回值可以直接通过fastjson转化为json类,来获取值.

获取流程到这里结束了,但是还有一些细节问题。

1.数据库设计:show create table usr

CREATE TABLE `usr` (
  `openid` varchar(100) NOT NULL,
  `name` varchar(60) NOT NULL,
  `signature` varchar(100) DEFAULT NULL,
  `accessToken` varchar(100) DEFAULT NULL,
  `icon` varchar(100) NOT NULL,
  `gender` varchar(3) DEFAULT '男',
  `mytoken` char(33) DEFAULT NULL,
  PRIMARY KEY (`openid`),
  UNIQUE KEY `mytoken` (`mytoken`),
  KEY `tokenIndex` (`mytoken`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

这里面我mytoken是用来实现自动登录的。下面再说。

2.其中qq用户名可能有特殊符号,数据库可能保存乱码,需要处理一下通过base64编码一哈。

    private String nameToDb(String str){
        try {
            return  Base64.encodeBase64String(str.getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String nameToshow(String str){
        try {
            return   new String(Base64.decodeBase64(str.getBytes()),"utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

3.我数据库中有mytoken是因为我有一个功能是通过cookie自动登录网站。

参考网址:web实现自动登录功能

读的有点不明白,我就简单的操作一下:将openid通过md5加密一下,就是token,保存到数据库中,并发送到浏览器当做cookie。然后下次打开页面的时候通过cookie中的token来查询数据库,得到用户信息。

退出登录则只要删除session和浏览器保存的token:详解如何删除cookie

4.其中登录的时候需要通过openid来判断数据库是否已经存在此用户,详细步骤见代码。

5.因为要实现登录完成后关闭小窗口,所以需要返回页面。因为jsp落伍了,我就使用的freemaker模版。feedback.html代码为:

这个页面主要是通过js来改变父模版的样式,这样做不是很合适,因为这样的操作在手机端会失效。而如果不通过这种方式还可以通过刷新的办法来实现登录,感觉这样更难受,我也不知道其他的办法。




    
    回调页面


登录成功

因为本篇涉及到的session和freemaker模版知识很简单,自己了解一下吧。

最终的登录效果:(头像不一样,是因为我后来改的)

我将上述内容整合到一起,代码比较冗杂(下个版本会更改),而且更为致命的是没有注意到全部的错误处理,你们写的时候一定要加上,我等有机会再改吧。

 @GetMapping("/recall")
    public String getData(ModelMap model, HttpServletRequest request, HttpServletResponse response){
        UsrShow usrShow = null;
        //判段state值
        HttpSession sessoin = request.getSession();
        String mystate = (String) sessoin.getAttribute("state");
        if (mystate == null) {
            System.out.println("kong");
        }
        String state = request.getParameter("state");
        if (!state.equals(mystate)) {
            System.out.println("不相等");
        }
        //获取Access Token值:101533058  1f1aa99f87d83ecee80202354c0f31cb   http://www.qihea.xyz/recall
        String url1 = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id="
                + 你的id + "&client_secret=" + "写你的必看我的" + "&redirect_uri="
                + "http://127.0.0.1:8080/recall" + "&code=" + request.getParameter("code");
        //发送url请求获取数据
        try {
            URL url = new URL(url1);
            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);
            //使用正则表达式解析网址
            Pattern p = Pattern.compile("access_token=(\\w*)&");
            Matcher m = p.matcher(result);
            m.find();
            //得到access_token
            String access_token = m.group(1);
            //System.out.println(access_token);
            String url2 = "https://graph.qq.com/oauth2.0/me?access_token=" + access_token;
            url = new URL(url2);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5 * 1000);
            conn.setRequestMethod("GET");
            inStream = conn.getInputStream();
            data = toByteArray(inStream);
            String result2 = new String(data, "UTF-8");
            //System.out.println(result2);
            p = Pattern.compile("client_id\":\"(\\w*)\",");
            m = p.matcher(result2);
            m.find();
            String appid = m.group(1);
            p = Pattern.compile("openid\":\"(\\w*)\"");
            m = p.matcher(result2);
            m.find();
            //得到openid
            String openid = m.group(1);
            System.out.println(openid);
            //判断数据库中是否有此
            usrShow = usrService.findUsrByid(openid);
            //通过openid计算token
            String token = getMD5(openid);
            //如果数据库中没有
            if (usrShow == null) {
                String url3 = "https://graph.qq.com/user/get_user_info?access_token=" + access_token
                        + "&oauth_consumer_key=" + appid + "&openid=" + openid;
                url = new URL(url3);
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(5 * 1000);
                conn.setRequestMethod("GET");
                inStream = conn.getInputStream();
                data = toByteArray(inStream);
                String result3 = new String(data, "UTF-8");
                //System.out.println(result3);
                //json字符串转化为json对象
                JSONObject jsonObject = JSON.parseObject(result3);
                String myname = jsonObject.getString("nickname");
                myname = nameToDb(myname);
                //生成usr对象,并插入数据库
                Usr usr = new Usr("", myname
                        , jsonObject.getString("figureurl_2"), jsonObject.getString("gender")
                        , access_token, openid, token);
                if (!usrService.insertUsr(usr))
                    System.out.println("添加失败");
                //生成usrshow对象
                usrShow = new UsrShow(null, usr.getName()
                        , usr.getIcon(), usr.getGender());
            }
            //改变名称格式
            usrShow.setName(nameToshow(usrShow.getName()));
            //加入session
            sessoin.setAttribute("usrshow", usrShow);
            //cookie中存入token
            Cookie cookie = new Cookie("token", token);//创建新cookie
            cookie.setMaxAge(2592000);// 设置存在时间为一个月
            cookie.setPath("/");//设置作用域
            response.addCookie(cookie);
        } catch (IOException e) {
            e.printStackTrace();
        }
        model.addAttribute("json",JSON.toJSONString(usrShow));
        return "feedback";
    }

 

你可能感兴趣的:(web项目)