最新文章


SPRINGBOOT之QQ登录
最近在网站上做了一个QQ登录OAuth2.0接入的功能,下面我就来分享一下如何实现QQ登录。首先在开发之前我们需要为QQ登录做如下准备:1、登录QQ互联https://connect.qq.com/成为开发者(成为开发者需要填写资料,审核时间大概2天吧)。2、有了开发者账号之后就能创建应用,这时会要求你填写网站信息,之后又会进入审核阶段(本人当时几个小时就审核过了)。接下来就是开发阶段了根据QQ互联官方网站可以知道QQ登录开发流程如下:1、获取appid和appkey,appid和appkey在你创建的应用信息中查找。2、放置QQ登录按钮。3、获取Authorization Code。4、通过Authorization Code获取Access Token。5、获取QQ用户的openid。6、通过openid访问/修改QQ用户信息。以下为开发步骤和示例代码本项目是一个maven构建的项目。使用fastjson处理json数据,fastjson依赖:<dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>1.2.47</version></dependency>一、写好配置功能1、配置基本信息## 第三方登录配置信息oauth:  qq:    app_id: ----------------    app_key: --------------------------------    # 获取ocde地址    get_authorization_code_uri: https://graph.qq.com/oauth2.0/authorize    # 获取ocde的参数,固定为code    response_type: code    # 回调地址    redirect_uri: ---------------------------    # 获取Access Token地址    get_access_token_uri: https://graph.qq.com/oauth2.0/token    # 获取Access Token,固定为authorization_code    token_grant_type: authorization_code    # 刷新Access Token,refresh_token    refresh_token_grant_type: refresh_token    # 获取openid的地址    get_openid_uri: https://graph.qq.com/oauth2.0/me    # 获取用户信息地址    get_user_info_uri: https://graph.qq.com/user/get_user_info 2、编写配置信息的配置类,通过该配置类获取配置信息@Component@ConfigurationProperties(prefix = "oauth.qq")public class QqOAuthConfig {    private String appId;    private String appKey;    /**     * 获取ocde地址     */    private String getAuthorizationCodeUri;    /**     * 获取ocde的参数,固定为code     */    private String responseType;    /**     * 回调地址     */    private String redirectUri;    /**     * 获取Access Token地址     */    private String getAccessTokenUri;    /**     * 获取Access Token,固定为authorization_code     */    private String tokenGrantType;    /**     * 刷新Access Token,refresh_token     */    private String refreshTokenGrantType;    /**     * 获取openid的地址     */    private String getOpenidUri;    /**     * 获取用户信息地址     */    private String getUserInfoUri;    public String getAppId() {        return appId;    }    public void setAppId(String appId) {        this.appId = appId;    }    public String getAppKey() {        return appKey;    }    public void setAppKey(String appKey) {        this.appKey = appKey;    }    public String getGetAuthorizationCodeUri() {        return getAuthorizationCodeUri;    }    public void setGetAuthorizationCodeUri(String getAuthorizationCodeUri) {        this.getAuthorizationCodeUri = getAuthorizationCodeUri;    }    public String getResponseType() {        return responseType;    }    public void setResponseType(String responseType) {        this.responseType = responseType;    }    public String getRedirectUri() {        return redirectUri;    }    public void setRedirectUri(String redirectUri) {        this.redirectUri = redirectUri;    }    public String getGetAccessTokenUri() {        return getAccessTokenUri;    }    public void setGetAccessTokenUri(String getAccessTokenUri) {        this.getAccessTokenUri = getAccessTokenUri;    }    public String getTokenGrantType() {        return tokenGrantType;    }    public void setTokenGrantType(String tokenGrantType) {        this.tokenGrantType = tokenGrantType;    }    public String getRefreshTokenGrantType() {        return refreshTokenGrantType;    }    public void setRefreshTokenGrantType(String refreshTokenGrantType) {        this.refreshTokenGrantType = refreshTokenGrantType;    }    public String getGetOpenidUri() {        return getOpenidUri;    }    public void setGetOpenidUri(String getOpenidUri) {        this.getOpenidUri = getOpenidUri;    }    public String getGetUserInfoUri() {        return getUserInfoUri;    }    public void setGetUserInfoUri(String getUserInfoUri) {        this.getUserInfoUri = getUserInfoUri;    }} 二、放置QQ登录按钮三、接下来就是QQ登录主要流程。此时我们需要httpclient工具来获取通过请求返回的信息,小伙伴们可以去百度了解一下httpclient。1、加入httpclient依赖<dependency>    <groupId>org.apache.httpcomponents</groupId>    <artifactId>httpclient</artifactId>    <version>4.5.7</version></dependency> 2、配置httpclient工具1)封装响应结果public class HttpClientResult implements Serializable {    /**     * 响应状态码     */    private int code;    /**     * 响应数据     */    private String content;    public HttpClientResult() {    }    public HttpClientResult(int code) {        this.code = code;    }    public HttpClientResult(int code, String content) {        this.code = code;        this.content = content;    }    public int getCode() {        return code;    }    public void setCode(int code) {        this.code = code;    }    public String getContent() {        return content;    }    public void setContent(String content) {        this.content = content;    }} 2)封装httpClient工具类public class HttpClientUtil {    // 编码格式。发送编码格式统一用UTF-8    private static final String ENCODING = "UTF-8";    // 设置连接超时时间,单位毫秒。    private static final int CONNECT_TIMEOUT = 6000;    // 请求获取数据的超时时间(即响应时间),单位毫秒。    private static final int SOCKET_TIMEOUT = 6000;    /**     * 发送get请求;不带请求头和请求参数     *     * @param url 请求地址     * @return     * @throws Exception     */    public static HttpClientResult doGet(String url) throws Exception {        return doGet(url, null, null);    }    /**     * 发送get请求;带请求参数     *     * @param url    请求地址     * @param params 请求参数集合     * @return     * @throws Exception     */    public static HttpClientResult doGet(String url, Map<String, String> params) throws Exception {        return doGet(url, null, params);    }    /**     * 发送get请求;带请求头和请求参数     *     * @param url     请求地址     * @param headers 请求头集合     * @param params  请求参数集合     * @return     * @throws Exception     */    public static HttpClientResult doGet(String url, Map<String, String> headers, Map<String, String> params) throws Exception {        // 创建httpClient对象        CloseableHttpClient httpClient = HttpClients.createDefault();        // 创建访问的地址        URIBuilder uriBuilder = new URIBuilder(url);        if (params != null) {            Set<Map.Entry<String, String>> entrySet = params.entrySet();            for (Map.Entry<String, String> entry : entrySet) {                uriBuilder.setParameter(entry.getKey(), entry.getValue());            }        }        // 创建http对象        HttpGet httpGet = new HttpGet(uriBuilder.build());        /**         * setConnectTimeout:设置连接超时时间,单位毫秒。         * setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection         * 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。         * setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。         */        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();        httpGet.setConfig(requestConfig);        // 设置请求头        packageHeader(headers, httpGet);        // 创建httpResponse对象        CloseableHttpResponse httpResponse = null;        try {            // 执行请求并获得响应结果            return getHttpClientResult(httpResponse, httpClient, httpGet);        } finally {            // 释放资源            release(httpResponse, httpClient);        }    }    /**     * 发送post请求;不带请求头和请求参数     *     * @param url 请求地址     * @return     * @throws Exception     */    public static HttpClientResult doPost(String url) throws Exception {        return doPost(url, null, null);    }    /**     * 发送post请求;带请求参数     *     * @param url    请求地址     * @param params 参数集合     * @return     * @throws Exception     */    public static HttpClientResult doPost(String url, Map<String, String> params) throws Exception {        return doPost(url, null, params);    }    /**     * 发送post请求;带请求头和请求参数     *     * @param url     请求地址     * @param headers 请求头集合     * @param params  请求参数集合     * @return     * @throws Exception     */    public static HttpClientResult doPost(String url, Map<String, String> headers, Map<String, String> params) throws Exception {        // 创建httpClient对象        CloseableHttpClient httpClient = HttpClients.createDefault();        // 创建http对象        HttpPost httpPost = new HttpPost(url);        /**         * setConnectTimeout:设置连接超时时间,单位毫秒。         * setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection         * 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。         * setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。         */        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();        httpPost.setConfig(requestConfig);        // 设置请求头      /*httpPost.setHeader("Cookie", "");      httpPost.setHeader("Connection", "keep-alive");      httpPost.setHeader("Accept", "application/json");      httpPost.setHeader("Accept-Language", "zh-CN,zh;q=0.9");      httpPost.setHeader("Accept-Encoding", "gzip, deflate, br");      httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36");*/        packageHeader(headers, httpPost);        // 封装请求参数        packageParam(params, httpPost);        // 创建httpResponse对象        CloseableHttpResponse httpResponse = null;        try {            // 执行请求并获得响应结果            return getHttpClientResult(httpResponse, httpClient, httpPost);        } finally {            // 释放资源            release(httpResponse, httpClient);        }    }    /**     * 发送put请求;不带请求参数     *     * @param url 请求地址     * @param     * @return     * @throws Exception     */    public static HttpClientResult doPut(String url) throws Exception {        return doPut(url);    }    /**     * 发送put请求;带请求参数     *     * @param url    请求地址     * @param params 参数集合     * @return     * @throws Exception     */    public static HttpClientResult doPut(String url, Map<String, String> params) throws Exception {        CloseableHttpClient httpClient = HttpClients.createDefault();        HttpPut httpPut = new HttpPut(url);        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();        httpPut.setConfig(requestConfig);        packageParam(params, httpPut);        CloseableHttpResponse httpResponse = null;        try {            return getHttpClientResult(httpResponse, httpClient, httpPut);        } finally {            release(httpResponse, httpClient);        }    }    /**     * 发送delete请求;不带请求参数     *     * @param url 请求地址     * @param     * @return     * @throws Exception     */    public static HttpClientResult doDelete(String url) throws Exception {        CloseableHttpClient httpClient = HttpClients.createDefault();        HttpDelete httpDelete = new HttpDelete(url);        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();        httpDelete.setConfig(requestConfig);        CloseableHttpResponse httpResponse = null;        try {            return getHttpClientResult(httpResponse, httpClient, httpDelete);        } finally {            release(httpResponse, httpClient);        }    }    /**     * 发送delete请求;带请求参数     *     * @param url    请求地址     * @param params 参数集合     * @return     * @throws Exception     */    public static HttpClientResult doDelete(String url, Map<String, String> params) throws Exception {        if (params == null) {            params = new HashMap<String, String>();        }        params.put("_method", "delete");        return doPost(url, params);    }    /**     * Description: 封装请求头     *     * @param params     * @param httpMethod     */    public static void packageHeader(Map<String, String> params, HttpRequestBase httpMethod) {        // 封装请求头        if (params != null) {            Set<Map.Entry<String, String>> entrySet = params.entrySet();            for (Map.Entry<String, String> entry : entrySet) {                // 设置到请求头到HttpRequestBase对象中                httpMethod.setHeader(entry.getKey(), entry.getValue());            }        }    }    /**     * Description: 封装请求参数     *     * @param params     * @param httpMethod     * @throws UnsupportedEncodingException     */    public static void packageParam(Map<String, String> params, HttpEntityEnclosingRequestBase httpMethod)            throws UnsupportedEncodingException {        // 封装请求参数        if (params != null) {            List<NameValuePair> nvps = new ArrayList<NameValuePair>();            Set<Map.Entry<String, String>> entrySet = params.entrySet();            for (Map.Entry<String, String> entry : entrySet) {                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));            }            // 设置到请求的http对象中            httpMethod.setEntity(new UrlEncodedFormEntity(nvps, ENCODING));        }    }    /**     * Description: 获得响应结果     *     * @param httpResponse     * @param httpClient     * @param httpMethod     * @return     * @throws Exception     */    public static HttpClientResult getHttpClientResult(CloseableHttpResponse httpResponse,CloseableHttpClient httpClient, HttpRequestBase httpMethod) throws Exception {        // 执行请求        httpResponse = httpClient.execute(httpMethod);        // 获取返回结果        if (httpResponse != null && httpResponse.getStatusLine() != null) {            String content = "";            if (httpResponse.getEntity() != null) {                content = EntityUtils.toString(httpResponse.getEntity(), ENCODING);            }            return new HttpClientResult(httpResponse.getStatusLine().getStatusCode(), content);        }        return new HttpClientResult(HttpStatus.SC_INTERNAL_SERVER_ERROR);    }    /**     * Description: 释放资源     *     * @param httpResponse     * @param httpClient     * @throws IOException     */    public static void release(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient) throws IOException {        // 释放资源        if (httpResponse != null) {            httpResponse.close();        }        if (httpClient != null) {            httpClient.close();        }    }} 3、点击QQ登录的按钮获取会跳转到QQ登录页面。如果QQ授权登录成功将会跳转到指定的回调地址(创建应用是填写的地址),获取到Authorization Code,该code有效期10分钟。@GetMapping("/login/qq")public void loginQQ(HttpServletResponse httpServletResponse, String thirdLoginBackHref) {    try {        httpServletResponse.sendRedirect(qqOAuthConfig.getGetAuthorizationCodeUri() +                "?response_type=" + qqOAuthConfig.getResponseType() +                "&client_id=" + qqOAuthConfig.getAppId() +                "&redirect_uri=" + qqOAuthConfig.getRedirectUri() +                "&state=" + UuidUtil.getUuid());    } catch (Exception e) {        throw new BaseException("请求参数异常");    }} 1、编写回调地址。通过Authorization Code获取Access Token(2小时有效期);获取QQ用户的openid;通过openid访问QQ用户信息。1)回调controller@GetMapping("/login/qq/callback")public String qqCallbackUri(String code) {    loginService.qqLogin(code);    return "/"; //返回地址} 2)回调service,获取用户信息。@Override@Transactionalpublic void qqLogin(String code) {    //获取access_token的请求参数    HashMap<String, String> getTokenParams = new HashMap<>();    getTokenParams.put("grant_type", qqOAuthConfig.getTokenGrantType());    getTokenParams.put("client_id", qqOAuthConfig.getAppId());    getTokenParams.put("client_secret", qqOAuthConfig.getAppKey());    getTokenParams.put("code", code);    getTokenParams.put("redirect_uri", qqOAuthConfig.getRedirectUri());    //获取access_token    HttpClientResult resultOfGetAcccessToken = null;    try {        resultOfGetAcccessToken = HttpClientUtil.doGet(qqOAuthConfig.getGetAccessTokenUri(), getTokenParams);    } catch (Exception e) {        throw new BaseException("QQ登录异常");    }    //获取access_token返回参数    String resultParamsOfGetToken = resultOfGetAcccessToken.getContent();    if (resultOfGetAcccessToken.getCode() != 200 || (resultParamsOfGetToken == null || resultParamsOfGetToken.equals(""))) {        throw new BaseException("QQ登录异常");    }    //处理获取access_token返回参数成json    String[] tokenResultString = resultParamsOfGetToken.split("&");    Map<String, String> tokenResultMap = new HashMap<>();    for (String s : tokenResultString) {        String keyValue[] = s.split("=");        if (keyValue.length > 1) {            tokenResultMap.put(keyValue[0], keyValue[1]);        }    }    //access_token    String accessToken = tokenResultMap.get("access_token");    //获取openid    HttpClientResult resultOfGetOpenid = null;    try {        resultOfGetOpenid = HttpClientUtil.doGet(qqOAuthConfig.getGetOpenidUri() + "?access_token=" + accessToken);    } catch (Exception e) {        throw new BaseException("QQ登录异常");    }    //处理获取openid结果    String resultOfOpenidContent = resultOfGetOpenid.getContent();    if (resultOfGetOpenid.getCode() != 200 || (resultOfOpenidContent == null || resultOfOpenidContent.equals(""))) {        throw new BaseException("QQ登录异常");    }    JSONObject resultByGetOpenIdJson = JSON.parseObject(resultOfOpenidContent.replaceAll("callback\\( | \\);", "").trim());    //openid    String openid = (String) resultByGetOpenIdJson.get("openid");    if (resultByGetOpenIdJson.get("client_id") == null || openid == null) {        throw new BaseException("QQ登录异常");    }    //获取用户信息    HttpClientResult resultByGetUserInfo = null;    try {        resultByGetUserInfo = HttpClientUtil.doGet(qqOAuthConfig.getGetUserInfoUri() +                "?access_token=" + accessToken +                "&oauth_consumer_key=" + qqOAuthConfig.getAppId() +                "&openid=" + openid);    } catch (Exception e) {        throw new BaseException("QQ登录异常");    }    //处理用户数据    JSONObject qqUserInfoJson = JSON.parseObject(resultByGetUserInfo.getContent());    //qq头像    String chatHead = (String) qqUserInfoJson.get("figureurl_qq_1");    //qq昵称    String nickname = (String) qqUserInfoJson.get("nickname");    //以下便是登录逻辑,这里就省略不多写了,大家可以根据自己的需求编写登录逻辑。} 说明:上面代码最后的变量qqUserInfoJson便是一个包含了QQ用户信息的json对象,我们可以将自己需要的用户信息取出来,必要时保存在数据库。 虽然省略的登录部分的代码,但在这里也简要说一下QQ登录逻辑:首先QQ登录对于自己的项目肯定是免密登录;其次openid是每个QQ用户在本网站的唯一标识,所以我们QQ登录的用户表一般会存每个用户的openid,在获取到QQ用户信息时判断数据库是否存在与当前openid相同的用户数据,如果没有便添加一条与当前openid相同的用户信息然后登录,如果存在,可以选择直接进入登录逻辑或修改相关登录信息后进入登录逻辑。 这里只介绍简单的QQ登录流程,并没有持续去请求腾讯API获取相关信息,所以刷新access_token功能以后再加入。 至此QQ登录就分享完了,感谢大家的支持!
Mar 20, 2019 9:40:40 PM
278
45
个人名片

  欢迎来到“浩瀚星尘”的个人博客!
  首先,该博客用于分享本人的生活事迹与兴趣爱好; 此外,该博客的主要作用便是与广大的小伙伴一起分享探讨开发技术, 希望大家多多关照。

网名: 浩瀚星尘
城市: 重庆
工作: java