准备材料
- 1.已经备案好的域名
- 2.服务器(域名和服务器为统一主体或域名已接入服务器)
- 3.QQ号
- 4.开发流程:https://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0
创建应用
- 1.访问 https://connect.qq.com/manage.html ,登录。
- 2.创建网站应用,填写网站基本信息以及平台信息,提交审核。注:网站回调域后续会用到,是点击授权登录时回调地址,需要与后续开发一致。
程序开发
1. 添加QQ登录按钮,用于点击跳转至QQ授权登录页
2. Java后台实现页面跳转
2.1 编写一个工具类
QQUtil
package cn.zwqh.springboot.common.qq;
import java.io.IOException;
import java.net.URLEncoder;
import org.apache.http.client.ClientProtocolException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import cn.zwqh.springboot.common.http.HttpClientUtils;
import cn.zwqh.springboot.entity.sys.User;
public class QQUtil {
private static final Logger log = LoggerFactory.getLogger(QQUtil.class);
private static final String QQ_APP_ID="XXX";//改成自己的
private static final String QQ_APP_SECRET="XXX";//改成自己的
private static final String LOGIN_REDIRECT_URI="https://www.zwqh.top/account/qqLogin"; //改成自己的
private static final String BIND_REDIRECT_URI="https://www.zwqh.top/account/qqBind"; //改成自己的
private static final String AUTH_CODE_URL="https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id="+QQ_APP_ID+"&redirect_uri=REDIRECT_URI&state=STATE";
private static final String ACCESS_TOKEN_URL="https://graph.qq.com/oauth2.0/token?client_id="+QQ_APP_ID+"&client_secret="+QQ_APP_SECRET+"&code=CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI";
private static final String REFRESH_TOKEN_URL="https://graph.qq.com/oauth2.0/token?client_id="+QQ_APP_ID+"&client_secret="+QQ_APP_SECRET+"&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
private static final String OPEN_ID_URL="https://graph.qq.com/oauth2.0/me?access_token=ACCESS_TOKEN";
private static final String USER_INFO_URL="https://graph.qq.com/user/get_user_info?access_token=ACCESS_TOKEN&oauth_consumer_key="+QQ_APP_ID+"&openid=OPENID";
public static JSONObject getJsonStrByQueryUrl(String paramStr){
String[] params = paramStr.split("&");
JSONObject obj = new JSONObject();
for (int i = 0; i < params.length; i++) {
String[] param = params[i].split("=");
if (param.length >= 2) {
String key = param[0];
String value = param[1];
for (int j = 2; j < param.length; j++) {
value += "=" + param[j];
}
try {
obj.put(key,value);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
return obj;
}
/**
* 获取授权登录页码url
* @return
*/
public static String getLoginConnectUrl(String state) {
String url=null;
try{
url=AUTH_CODE_URL.replace("REDIRECT_URI", URLEncoder.encode(LOGIN_REDIRECT_URI, "utf-8")).replace("STATE", state);
}catch (Exception e) {
log.error(e.toString());
}
return url;
}
/**
* 获取授权绑定页码url
* @return
*/
public static String getBindConnectUrl() {
String url=null;
try{
url=AUTH_CODE_URL.replace("REDIRECT_URI", URLEncoder.encode(BIND_REDIRECT_URI, "utf-8"));
}catch (Exception e) {
log.error(e.toString());
}
return url;
}
/**
* 获取AccessToken
* @return 返回拿到的access_token及有效期
*/
public static QQAccessToken getQQLoginAccessToken(String code) throws ClientProtocolException, IOException{
QQAccessToken token = new QQAccessToken();
String url = ACCESS_TOKEN_URL.replace("CODE", code).replace("REDIRECT_URI", URLEncoder.encode(LOGIN_REDIRECT_URI, "utf-8"));
log.info("这是请求路径:"+url);
String result = HttpClientUtils.doGet(url);
JSONObject jsonObject=getJsonStrByQueryUrl(result);
log.info("这是返回结果:"+jsonObject);
if(jsonObject!=null){ //如果返回不为空,将返回结果封装进AccessToken实体类
token.setAccessToken(jsonObject.getString("access_token"));//接口调用凭证
token.setExpiresIn(jsonObject.getInteger("expires_in"));//access_token接口调用凭证超时时间,单位(秒)
token.setRefreshToken(jsonObject.getString("refresh_token"));
}
return token;
}
/**
* 获取AccessToken
* @return 返回拿到的access_token及有效期
*/
public static QQAccessToken getQQBindAccessToken(String code) throws ClientProtocolException, IOException{
QQAccessToken token = new QQAccessToken();
String url = ACCESS_TOKEN_URL.replace("CODE", code).replace("REDIRECT_URI", URLEncoder.encode(BIND_REDIRECT_URI, "utf-8"));
log.info("这是请求路径:"+url);
String result = HttpClientUtils.doGet(url);
JSONObject jsonObject=getJsonStrByQueryUrl(result);
log.info("这是返回结果:"+jsonObject);
if(jsonObject!=null){ //如果返回不为空,将返回结果封装进AccessToken实体类
token.setAccessToken(jsonObject.getString("access_token"));//接口调用凭证
token.setExpiresIn(jsonObject.getInteger("expires_in"));//access_token接口调用凭证超时时间,单位(秒)
token.setRefreshToken(jsonObject.getString("refresh_token"));
}
return token;
}
/**
* 刷新或续期access_token使用
* @return 返回拿到的access_token及有效期
*/
public static QQAccessToken refreshQQAccessToken(String refreshToken) throws ClientProtocolException, IOException{
QQAccessToken token = new QQAccessToken();
String url = REFRESH_TOKEN_URL.replace("REFRESH_TOKEN",refreshToken);
log.info("这是请求路径:"+url);
String result = HttpClientUtils.doGet(url);
log.info("这是返回结果:"+result);
JSONObject jsonObject=getJsonStrByQueryUrl(result);
log.info("这是转为json的结果:"+jsonObject);
if(jsonObject!=null){ //如果返回不为空,将返回结果封装进AccessToken实体类
token.setAccessToken(jsonObject.getString("access_token"));//接口调用凭证
token.setExpiresIn(jsonObject.getInteger("expires_in"));//access_token接口调用凭证超时时间,单位(秒)
token.setRefreshToken(jsonObject.getString("refresh_token"));
}
return token;
}
/**
* 获取QQopenId
* @return QQopenId
*/
public static String getQQOpenId(String accessToken) throws ClientProtocolException, IOException{
String url = OPEN_ID_URL.replace("ACCESS_TOKEN",accessToken);
log.info("这是请求路径:"+url);
String result = HttpClientUtils.doGet(url).replace("callback(", "").replace(");", "");
log.info("这是返回结果:"+result);
JSONObject jsonObject=JSON.parseObject(result);
log.info("这是转为json的结果:"+jsonObject);
if(jsonObject!=null&&jsonObject.getString("openid")!=null){ //如果返回不为空
return jsonObject.getString("openid");
}
return null;
}
/**
* 获取QQ用户信息
* @param accessToken
* @param openId
* @return
* @throws IOException
* @throws ClientProtocolException
*/
public static JSONObject getUserInfo(String accessToken, String openId) throws ClientProtocolException, IOException {
// 拼接请求地址
String url = USER_INFO_URL.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId);
log.info("这是请求路径:"+url);
String result = HttpClientUtils.doGet(url);
log.info("这是返回结果:"+result);
JSONObject jsonObject=JSONObject.parseObject(result);
log.info("这是转为json的结果:"+jsonObject);
JSONObject json=new JSONObject();
if (jsonObject!=null&&jsonObject.getInteger("ret").equals(0)) {
try {
User user= new User();
// 用户的标识
user.setQqId(openId);
// 昵称
user.setNickname(jsonObject.getString("nickname"));
if(jsonObject.getString("figureurl_2")!=null&&!jsonObject.getString("figureurl_2").isEmpty()) {
// 用户头像
user.setAvatar(jsonObject.getString("figureurl_qq_2"));
}else {
// 用户头像
user.setAvatar(jsonObject.getString("figureurl_qq_1"));
}
json.put("success", true);
json.put("msg", "success");
json.put("user", user);
} catch (Exception e) {
int errorCode = jsonObject.getInteger("ret");
String errorMsg = jsonObject.getString("msg");
log.error("获取用户信息失败 errcode:{} errmsg:{}", errorCode, e.toString());
json.put("success", false);
json.put("msg", errorMsg);
json.put("user", null);
}
}else {
json.put("success", false);
json.put("msg", "请先登录");
json.put("user", null);
}
return json;
}
}
QQAccessToken
package cn.zwqh.springboot.common.qq;
import java.io.Serializable;
public class QQAccessToken implements Serializable {
/**
*
*/
private static final long serialVersionUID = 5258435811207021018L;
private String accessToken;//接口调用凭证
private int expiresIn;//access_token接口调用凭证超时时间,单位(秒)
private String openid;//授权用户唯一标识
private String refreshToken;//用户刷新access_token
private String scope;//用户授权的作用域,使用逗号(,)分隔
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public int getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
2.2 Controller层实现
package cn.zwqh.springboot.action.web;
import java.io.IOException;
import java.util.Date;
import java.util.UUID;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSONObject;
import cn.zwqh.springboot.action.BaseAction;
import cn.zwqh.springboot.common.CookieUtil;
import cn.zwqh.springboot.common.DateUtil;
import cn.zwqh.springboot.common.EscapeUnescape;
import cn.zwqh.springboot.common.qq.QQAccessToken;
import cn.zwqh.springboot.common.qq.QQUtil;
import cn.zwqh.springboot.common.redis.RedisHandle;
import cn.zwqh.springboot.entity.SessionUser;
import cn.zwqh.springboot.entity.sys.User;
import cn.zwqh.springboot.service.UserService;
@Controller
@RequestMapping("/account")
public class AccountAction extends BaseAction {
/**
*
*/
private static final long serialVersionUID = 1729415442021645693L;
@Resource
private RedisHandle redisHandle;
@Autowired
private UserService userService;
/**
* 跳转至QQ登录界面
*/
@RequestMapping("/qqConnect")
@ResponseBody
public void qqConnect() {
try {
String referer = getRequest().getHeader("REFERER");
String state = DateUtil.formatUserDefineDate(new Date(), "yyyyMMddHHmmssSSS");
redisHandle.set(state, referer, 60 * 30L);
getResponse().sendRedirect(QQUtil.getLoginConnectUrl(state));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* QQ第三方登录
*
* @throws Exception
*/
@RequestMapping("/qqLogin")
@ResponseBody
public void qqLogin() throws Exception {
String code = getRequest().getParameter("code");
String state = getRequest().getParameter("state");
System.out.println("code = " + code + ", state = " + state);
if (code != null && !"".equals(code)) {
QQAccessToken qqAccessToken = QQUtil.getQQLoginAccessToken(code);
if (qqAccessToken.getAccessToken().equals("")) {
// 我们的网站被CSRF攻击了或者用户取消了授权
// 做一些数据统计工作
System.out.print("没有获取到响应参数");
// 跳转返回地址
outJsonFailure("未获取到AccessToken,请重新进行QQ授权登录");
} else {
QQAccessToken qqAccessToken2 = QQUtil.refreshQQAccessToken(qqAccessToken.getRefreshToken());
String accessToken = qqAccessToken2.getAccessToken();
String referer = redisHandle.get(state).toString();
redisHandle.set(accessToken, referer, 60 * 30L);
redisHandle.remove(state);
getResponse().sendRedirect("https://www.zwqh.top/account/getQQUserInfo?qqAccessToken=" + accessToken);
}
} else {
outJsonFailure("缺少code参数");
}
}
/**
* 获取QQ用户信息
*
* @param qqAccessToken
* @throws Exception
*/
@GetMapping("/getQQUserInfo")
public String getQQUserInfo(String qqAccessToken) throws Exception {
System.out.println("accessToken = " + qqAccessToken);
String referer = redisHandle.get(qqAccessToken).toString();
if (qqAccessToken != null && !"".equals(qqAccessToken)) {
try {
String qqOpenId = QQUtil.getQQOpenId(qqAccessToken);
if (qqOpenId != null) {
System.out.println("**************qq登录成功 qqOpenId = " + qqOpenId);
// 获取QQ用户信息
JSONObject object = QQUtil.getUserInfo(qqAccessToken, qqOpenId);
// 数据库中判断qqOpenId是否存在,存在则登录,不存在则注册
User user = userService.getUserByQQOpenId(qqOpenId);
if (user != null) {
user.setAvatar(object.getJSONObject("user").getString("avatar"));
user.setNickname(object.getJSONObject("user").getString("nickname"));
user.setLastLoginTime(DateUtil.formatDateTime(new Date()));
userService.updateUser(user);
SessionUser suser = SessionUser.getInstance(user);
String token = UUID.randomUUID().toString();
redisHandle.set(token, suser, 60 * 60L * 24 * 7);// 设置用户缓存及过期时间(一星期)
JSONObject data = new JSONObject();
data.put("userId", user.getId());
data.put("nickname", user.getNickname());
data.put("avatar", user.getAvatar());
data.put("token", token);
CookieUtil.setValue(getResponse(), "loginUser", data.toString());
} else {
user = new User();
user.setAvatar(object.getJSONObject("user").getString("avatar"));
user.setNickname(object.getJSONObject("user").getString("nickname"));
user.setLastLoginTime(DateUtil.formatDateTime(new Date()));
user.setRegisterTime(DateUtil.formatDateTime(new Date()));
user.setQqId(qqOpenId);
userService.insertUser(user);
SessionUser suser = SessionUser.getInstance(user);
String token = UUID.randomUUID().toString();
redisHandle.set(token, suser, 60 * 60L * 24 * 7);// 设置用户缓存及过期时间(一星期)
JSONObject data = new JSONObject();
data.put("userId", user.getId());
data.put("nickname", user.getNickname());
data.put("avatar", user.getAvatar());
data.put("token", token);
CookieUtil.setValue(getResponse(), "loginUser", data.toString());
}
} else {
putInRequest("error", "未获取到用户openid,请重新QQ授权登录");
}
} catch (Exception e) {
e.printStackTrace();
putInRequest("error", "登录异常");
}
} else {
putInRequest("error", "缺少code参数");
}
return "redirect:" + referer;
}
/**
* 退出登录
* @return
*/
@RequestMapping("/logout")
public String logout() {
String referer = getRequest().getHeader("REFERER");
String data= CookieUtil.getCookieValue(getRequest(), "loginUser");
if(data!=null&&data!="") {
JSONObject user=JSONObject.parseObject(EscapeUnescape.unescape(data));
String token=user.getString("token");
redisHandle.remove(token);
CookieUtil.deleteValue("loginUser",getResponse());
}
return "redirect:" + referer;
}
}
2.3 JavaScript 处理页面
var data=eval('('+unescape(getCookie("loginUser"))+')');
var a = document.getElementsByClassName("blog-user")[0];
if(data!=null){
a.setAttribute("href","/account/logout");
a.innerHTML='';
}else{
a.setAttribute("href","/account/qqConnect");
a.innerHTML='';
}
总结
总的来说QQ授权登录还是很简单的,该方法使用web端以及wap端。
非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处.
本文标题: QQ第三方授权登录OAuth2.0实现(Java)
本文网址:https://www.zwqh.top/article/info/7