QQ第三方授权登录OAuth2.0实现(Java)

准备材料

创建应用

  • 1.访问 https://connect.qq.com/manage.html ,登录。
  • 2.创建网站应用,填写网站基本信息以及平台信息,提交审核。注:网站回调域后续会用到,是点击授权登录时回调地址,需要与后续开发一致。

程序开发

1. 添加QQ登录按钮,用于点击跳转至QQ授权登录页

<a href="/account/qqConnect" class="blog-user"> <i
				class="fa fa-qq"></i>
			</a>

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='<img alt="'+data.nickname+'" title="'+data.nickname+'" src="'+data.avatar+'" class="layui-circle" width="40px" height="40px">';
	}else{
		a.setAttribute("href","/account/qqConnect");
		a.innerHTML='<i class="fa fa-qq"></i>';
	}

总结

总的来说QQ授权登录还是很简单的,该方法使用web端以及wap端。

非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处.

本文标题: QQ第三方授权登录OAuth2.0实现(Java)

本文网址: https://www.zwqh.top/article/info/7

如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...

posted @ 2019-09-24 16:54  朝雾轻寒  阅读(7698)  评论(0编辑  收藏  举报