SpringBoot网站添加第三方登录之QQ登录

avatar 2018年05月16日17:49:54 7 11989 views
博主分享免费Java教学视频,B站账号:Java刘哥

一、创建应用

1、在 QQ互联 创建应用 地址:https://connect.qq.com/manage.html#/ 然后进行实名认证,创建应用,审核通过

 

然后点击查看,可以获得 APP ID 和 APP Key

回调地址如下  

2、授权的基本原理

可以参考官方文档

1)根据QQ登录链接可以回调获得 code

 

2)根据APP ID 、APP Key 和 code 可获得 token 3)根据 token 获得 OpenId 4)  根据 OpenId 可以获得用户的基本信息

其中 OpenId 是一个唯一的值,比如 8A674574E1B12345D790A111EFE81234  

3、几个必要的URL

1)登录页面授权 URL: https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s

2)获得 Token 的 URL: https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s

3)获得用户OpenId 的 URL: https://graph.qq.com/oauth2.0/me?access_token=%s

4)获得用户信息的 URL: https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s    

二、基本准备

1、数据库设计

直接看图片

   

2、放置 QQ 登录按钮

这里有两种常用的方式

① 弹小窗

 
  1. <script>
  2.     function openWin(url,name,iWidth,iHeight) {
  3.         //获得窗口的垂直位置
  4.         var iTop = (window.screen.availHeight - 30 - iHeight) / 2;
  5.         //获得窗口的水平位置
  6.         var iLeft = (window.screen.availWidth - 10 - iWidth) / 2;
  7.         window.open(url, name, 'height=' + iHeight + ',innerHeight=' + iHeight + ',width=' + iWidth + ',innerWidth=' + iWidth + ',top=' + iTop + ',left=' + iLeft + ',status=no,toolbar=no,menubar=no,location=no,resizable=no,scrollbars=0,titlebar=no');
  8.     }
  9.     function qqLogin() {
  10.         var url = "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=11111111&redirect_uri=http://localhost:8080/oauth/qq/callback&scope=get_user_info";
  11.         openWin(url,"qqLogin",650,500);
  12.     }
  13. </script>
  14. <a  href="javascript:void(0);" onclick="qqLogin()"></a>

 

② 在新窗口打开

 
  1. <a href="https://graph.qq.com/oauth2.0/authorizeresponse_type=code&client_id=11111111&redirect_uri=http://localhost:8080/oauth/qq/callback&scope=get_user_info" target="_blank"></a>

 

三、具体代码

参考这里 1、由于做了多个登录,所以代码做了一定程度的封装,大致如下:

 
  1. package com.liuyanzhao.sens.service;
  2.  
  3. import com.liuyanzhao.sens.model.dto.BindUserDTO;
  4. import com.liuyanzhao.sens.model.dto.JsonResult;
  5. import com.liuyanzhao.sens.utils.Response;
  6.  
  7. import java.io.UnsupportedEncodingException;
  8.  
  9. /**
  10.  * @author 言曌
  11.  * @date 2019/1/20 上午10:24
  12.  */
  13.  
  14. public interface AuthService {
  15.  
  16.     /**
  17.      * 根据code获得Token
  18.      *
  19.      * @param code code
  20.      * @return token
  21.      */
  22.     Response<String> getAccessToken(String code);
  23.  
  24.     /**
  25.      * 根据Token获得OpenId
  26.      *
  27.      * @param accessToken Token
  28.      * @return openId
  29.      */
  30.     Response<String> getOpenId(String accessToken);
  31.  
  32.     /**
  33.      * 刷新Token
  34.      *
  35.      * @param code code
  36.      * @return 新的token
  37.      */
  38.     Response<String> refreshToken(String code);
  39.  
  40.     /**
  41.      * 拼接授权URL
  42.      *
  43.      * @return URL
  44.      */
  45.     Response<String> getAuthorizationUrl();
  46.  
  47.     /**
  48.      * 根据Token和OpenId获得用户信息
  49.      *
  50.      * @param accessToken Token
  51.      * @param openId openId
  52.      * @return 第三方应用给的用户信息
  53.      */
  54.     Response<BindUserDTO> getUserInfo(String accessToken, String openId);
  55. }

  2、由于全部是自己封装的,所以http请求的代码也是所有的登录共用的,这里统一放放到了类DefaultAuthServiceImpl中,代码如下:

 
  1. package com.liuyanzhao.sens.service.impl;
  2.  
  3. import com.liuyanzhao.sens.service.AuthService;
  4. import org.springframework.http.client.SimpleClientHttpRequestFactory;
  5. import org.springframework.http.converter.ByteArrayHttpMessageConverter;
  6. import org.springframework.http.converter.HttpMessageConverter;
  7. import org.springframework.http.converter.ResourceHttpMessageConverter;
  8. import org.springframework.http.converter.StringHttpMessageConverter;
  9. import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  10. import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
  11. import org.springframework.http.converter.xml.SourceHttpMessageConverter;
  12. import org.springframework.web.client.RestTemplate;
  13.  
  14. import javax.xml.transform.Source;
  15. import java.nio.charset.StandardCharsets;
  16. import java.util.LinkedList;
  17. import java.util.List;
  18.  
  19. /**
  20.  * @author 言曌
  21.  * @date 2018/5/9 下午3:02
  22.  */
  23.  
  24. public abstract class DefaultAuthServiceImpl implements AuthService {
  25.     public static RestTemplate getRestTemplate() {// 手动添加
  26.         SimpleClientHttpRequestFactory requestFactory=new SimpleClientHttpRequestFactory();
  27.         requestFactory.setReadTimeout(120000);
  28.         List<HttpMessageConverter<?>> messageConverters = new LinkedList<>();
  29.         messageConverters.add(new ByteArrayHttpMessageConverter());
  30.         messageConverters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
  31.         messageConverters.add(new ResourceHttpMessageConverter());
  32.         messageConverters.add(new SourceHttpMessageConverter<Source>());
  33.         messageConverters.add(new AllEncompassingFormHttpMessageConverter());
  34.         messageConverters.add(new MappingJackson2HttpMessageConverter());
  35.         RestTemplate restTemplate=new RestTemplate(messageConverters);
  36.         restTemplate.setRequestFactory(requestFactory);
  37.         return restTemplate;
  38.     }
  39. }

由此,所有的登录Service只需要继承AuthService即可。  

3、QQ登录 Service 接口 QQAuthService.java

 
  1. package com.liuyanzhao.sens.service;
  2.  
  3. /**
  4.  * @author 言曌
  5.  * @date 2018/5/9 下午3:00
  6.  */
  7.  
  8. public interface QQAuthService extends AuthService {
  9. }

  4、QQ登录 Service 实现 QQAuthServiceImpl.java

 
  1. package com.liuyanzhao.sens.service.impl;
  2.  
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.liuyanzhao.sens.model.dto.BindUserDTO;
  5. import com.liuyanzhao.sens.service.QQAuthService;
  6. import com.liuyanzhao.sens.utils.Response;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.stereotype.Service;
  9. import org.springframework.web.util.UriComponentsBuilder;
  10.  
  11. import java.net.URI;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14.  
  15. /**
  16.  * @author 言曌
  17.  * @date 2018/5/9 下午3:15
  18.  */
  19.  
  20. @Service
  21. @Slf4j
  22. public class QQAuthServiceImpl extends DefaultAuthServiceImpl implements QQAuthService {
  23.  
  24.     //QQ 登陆页面的URL
  25.     private final static String AUTHORIZATION_URL =
  26.             "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s";
  27.     //获取token的URL
  28.     private final static String ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s";
  29.  
  30.     // 获取用户 openid 的 URL
  31.     private static final String OPEN_ID_URL = "https://graph.qq.com/oauth2.0/me?access_token=%s";
  32.  
  33.     // 获取用户信息的 URL,oauth_consumer_key 为 apiKey
  34.     private static final String USER_INFO_URL = "https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s";
  35.  
  36.     // 下面的属性可以通过配置读取
  37.     // QQ 在登陆成功后回调的 URL,这个 URL 必须在 QQ 互联里填写过
  38.     private static final String CALLBACK_URL = "http://localhost:8080/oauth/qq/callback";
  39.     // QQ 互联应用管理中心的 APP ID
  40.     private static final String APP_ID = "111111111";
  41.     // QQ 互联应用管理中心的 APP Key
  42.     private static final String APP_SECRET = "111111111111111111111111111111111";
  43.     // QQ 互联的 API 接口,访问用户资料
  44.     private static final String SCOPE = "get_user_info";
  45.  
  46.  
  47.     @Override
  48.     public Response<String> getAuthorizationUrl() {
  49.         String url = String.format(AUTHORIZATION_URL, APP_ID, CALLBACK_URL, SCOPE);
  50.         return Response.yes(url);
  51.     }
  52.  
  53.     @Override
  54.     public Response<String> getAccessToken(String code) {
  55.         String url = String.format(ACCESS_TOKEN_URL, APP_ID, APP_SECRET, code, CALLBACK_URL);
  56.         UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
  57.         URI uri = builder.build().encode().toUri();
  58.  
  59.         String resp = getRestTemplate().getForObject(uri, String.class);
  60.         if (resp != null && resp.contains("access_token")) {
  61.             Map<String, String> map = getParam(resp);
  62.             String access_token = map.get("access_token");
  63.             return Response.yes(access_token);
  64.         }
  65.         log.error("QQ获得access_token失败,code无效,resp:{}", resp);
  66.         return Response.no("code无效!");
  67.     }
  68.  
  69.     //由于QQ的几个接口返回类型不一样,此处是获取key-value类型的参数
  70.     private Map<String, String> getParam(String string) {
  71.         Map<String, String> map = new HashMap();
  72.         String[] kvArray = string.split("&");
  73.         for (int i = 0; i < kvArray.length; i++) {
  74.             String[] kv = kvArray[i].split("=");
  75.             map.put(kv[0], kv[1]);
  76.         }
  77.         return map;
  78.     }
  79.  
  80.     //QQ接口返回类型是text/plain,此处将其转为json
  81.     private JSONObject ConvertToJson(String string) {
  82.         string = string.substring(string.indexOf("(") + 1, string.length());
  83.         string = string.substring(0, string.indexOf(")"));
  84.         JSONObject jsonObject = JSONObject.parseObject(string);
  85.         return jsonObject;
  86.     }
  87.  
  88.     @Override
  89.     public Response<String> getOpenId(String accessToken) {
  90.         String url = String.format(OPEN_ID_URL, accessToken);
  91.         UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
  92.         URI uri = builder.build().encode().toUri();
  93.  
  94.         String resp = getRestTemplate().getForObject(uri, String.class);
  95.         if (resp != null && resp.contains("openid")) {
  96.             JSONObject jsonObject = ConvertToJson(resp);
  97.             String openid = jsonObject.getString("openid");
  98.             return Response.yes(openid);
  99.         }
  100.         log.error("QQ获得openid失败,accessToken无效,resp:{}", resp);
  101.         return Response.no(resp);
  102.     }
  103.  
  104.     @Override
  105.     public Response<BindUserDTO> getUserInfo(String accessToken, String openId) {
  106.         String url = String.format(USER_INFO_URL, accessToken, APP_ID, openId);
  107.         UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
  108.         URI uri = builder.build().encode().toUri();
  109.  
  110.         String resp = getRestTemplate().getForObject(uri, String.class);
  111.         JSONObject data = JSONObject.parseObject(resp);
  112.         BindUserDTO result = new BindUserDTO();
  113.         result.setOpenId(openId);
  114.         result.setGender(data.getString("gender"));
  115.         result.setAvatar(data.getString("figureurl_qq_2"));
  116.         result.setNickname(data.getString("nickname"));
  117.         return Response.yes(result);
  118.     }
  119.  
  120.     @Override
  121.     public Response<String> refreshToken(String code) {
  122.         return null;
  123.     }
  124. }

  5、在Controller中调用,代码如下:

 
  1. package com.liuyanzhao.sens.web.controller.front;
  2.  
  3. import cn.hutool.core.date.DateUtil;
  4. import cn.hutool.extra.servlet.ServletUtil;
  5. import com.liuyanzhao.sens.entity.Log;
  6. import com.liuyanzhao.sens.entity.ThirdAppBind;
  7. import com.liuyanzhao.sens.entity.User;
  8. import com.liuyanzhao.sens.model.dto.LogsRecord;
  9. import com.liuyanzhao.sens.model.dto.SensConst;
  10. import com.liuyanzhao.sens.model.enums.BindTypeEnum;
  11. import com.liuyanzhao.sens.service.*;
  12. import com.liuyanzhao.sens.utils.Response;
  13. import lombok.extern.slf4j.Slf4j;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.stereotype.Controller;
  16. import org.springframework.web.bind.annotation.GetMapping;
  17. import org.springframework.web.bind.annotation.RequestParam;
  18.  
  19. import javax.servlet.http.HttpServletRequest;
  20.  
  21.  
  22. /**
  23.  * @author 言曌
  24.  * @date 2018/5/9 下午2:59
  25.  */
  26. @Controller
  27. @Slf4j
  28. public class AuthController {
  29.  
  30.     @Autowired
  31.     private QQAuthService qqAuthService;
  32.  
  33.     @Autowired
  34.     private UserService userService;
  35.  
  36.     @Autowired
  37.     private ThirdAppBindService thirdAppBindService;
  38.  
  39.     @Autowired
  40.     private LogService logService;
  41.  
  42.     /**
  43.      * 第三方授权后会回调此方法,并将code传过来
  44.      *
  45.      * @param code code
  46.      * @return
  47.      */
  48.     @GetMapping("/oauth/qq/callback")
  49.     public String oauthByQQ(@RequestParam(value = "code") String code, HttpServletRequest request) {
  50.         Response<String> tokenResponse = qqAuthService.getAccessToken(code);
  51.         if (tokenResponse.isSuccess()) {
  52.             Response<String> openidResponse = qqAuthService.getOpenId(tokenResponse.getData());
  53.             if (openidResponse.isSuccess()) {
  54.                 //根据openId去找关联的用户
  55.                 ThirdAppBind bind = thirdAppBindService.findByAppTypeAndOpenId(BindTypeEnum.QQ.getValue(), openidResponse.getData());
  56.                 if (bind != null && bind.getUserId() != null) {
  57.                     //执行Login操作
  58.                     User user = userService.findByUserId(bind.getUserId());
  59.                     if (user != null) {
  60.                         request.getSession().setAttribute(SensConst.USER_SESSION_KEY, user);
  61.                         logService.saveByLog(new Log(LogsRecord.LOGIN, LogsRecord.LOGIN_SUCCESS+"(QQ登录)", ServletUtil.getClientIP(request), DateUtil.date()));
  62.                         log.info("用户[{}]登录成功(QQ登录)。", user.getUserDisplayName());
  63.                         return "redirect:/admin";
  64.                     }
  65.                 }
  66.             }
  67.         }
  68.         return "redirect:/admin/login";
  69.     }
  70.  
  71.  
  72. }

  查看更多 SpringBoot网站添加第三方登录之GitHub登录  

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
avatar

发表评论

avatar 登录者:匿名
匿名评论,评论回复后会有邮件通知

  

已通过评论:1   待审核评论数:0
  1. avatar 你好

    你好,在编辑的过程中出现了问题,麻烦你把源码发我邮箱谢谢