本文共 10629 字,大约阅读时间需要 35 分钟。
参考:
①:打开qq互联官网:登录qq,然后点击登陆后的头像(当时找入口找了半天?)进行个人开发者信息认证。
这个认证大概要半天时间。认证成功后就可以申请应用了。
②:点击应用管理,创建应用,填写信息。可以先随便写一个,过两个小时就显示就申请失败了。不过没关系的,因为我们这时候失败的申请也能用。不过只能登陆自己的qq,用来做测试。
我写的地址和回调地址:(其他随便写,反正是用来做测试的)
③:找到自己应用的app ID和app KEY。之后会用到。
<button type="button" class="" οnclick="login()" >
<img src="/image/qq.jpg" style="height:30px">
<span class="">登陆</span>
</button>
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");}
即点击后打开如下页面
logincheck页面完成的操作会在之后提到,注意这种操作在手机端打开失败,暂时还没有处理。
qq互联只提供jsSDK和PHPSDK,没有javaSDK,可是使用jsSDK自我定制度低,而且不使用SDK,按照流程开发也很简单:
QQ登录OAuth2.0总体处理流程如下:
Step1:申请接入,获取appid和apikey; Step2:开发应用,并设置协作者帐号进行测试联调; Step3:放置QQ登录按钮; Step4:通过用户登录验证和授权,获取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参数状态的绑定。 |
此时用户得到这样的登录页面。
如果用户成功登录并授权,则会跳转到指定的回调地址,并在redirect_uri地址后带上Authorization Code和原始的state值。
Step5:通过Access Token获取用户的OpenID;
参考官方文档:
Step6:调用OpenAPI,来请求访问或修改用户授权的资源。参考官方文档:
注意我用的是springboot后台,这个getData函数中用到了usrService。需要在类中。
@Autowiredprivate 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 = "=" + 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. 以接口为例:
(请将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自动登录网站。
参考网址:
读的有点不明白,我就简单的操作一下:将openid通过md5加密一下,就是token,保存到数据库中,并发送到浏览器当做cookie。然后下次打开页面的时候通过cookie中的token来查询数据库,得到用户信息。
退出登录则只要删除session和浏览器保存的token:
4.其中登录的时候需要通过openid来判断数据库是否已经存在此用户,详细步骤见代码。
5.因为要实现登录完成后关闭小窗口,所以需要返回页面。因为jsp落伍了,我就使用的freemaker模版。feedback.html代码为:
这个页面主要是通过js来改变父模版的样式,这样做不是很合适,因为这样的操作在手机端会失效。而如果不通过这种方式还可以通过刷新的办法来实现登录,感觉这样更难受,我也不知道其他的办法。
回调页面 登录成功
${json}
因为本篇涉及到的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"; }
转载地址:http://cfihn.baihongyu.com/