参考:https://blog.csdn.net/a992970569/article/details/82107899
①:打开qq互联官网:https://connect.qq.com/index.html登录qq,然后点击登陆后的头像(当时找入口找了半天?)进行个人开发者信息认证。
这个认证大概要半天时间。认证成功后就可以申请应用了。
②:点击应用管理,创建应用,填写信息。可以先随便写一个,过两个小时就显示就申请失败了。不过没关系的,因为我们这时候失败的申请也能用。不过只能登陆自己的qq,用来做测试。
我写的地址和回调地址:(其他随便写,反正是用来做测试的)
③:找到自己应用的app ID和app KEY。之后会用到。
登陆
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;
参考官方文档:使用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参数状态的绑定。 |
如果用户成功登录并授权,则会跳转到指定的回调地址,并在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来改变父模版的样式,这样做不是很合适,因为这样的操作在手机端会失效。而如果不通过这种方式还可以通过刷新的办法来实现登录,感觉这样更难受,我也不知道其他的办法。
回调页面
登录成功
${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";
}