23 09 2019

准备材料

创建应用

  • 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端。

延伸阅读
  1. Java用Freemarker完美导出word文档(带图片)
  2. QQ第三方授权登录OAuth2.0实现(Java)
  3. Markdown基本语法
  4. Java 实现微信公众号文章内容一键采取(标题+内容+图片)
发表评论