一、创建应用
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 登录按钮
这里有两种常用的方式
① 弹小窗
- <script>
- function openWin(url,name,iWidth,iHeight) {
- //获得窗口的垂直位置
- var iTop = (window.screen.availHeight - 30 - iHeight) / 2;
- //获得窗口的水平位置
- var iLeft = (window.screen.availWidth - 10 - iWidth) / 2;
- 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');
- }
- function qqLogin() {
- 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";
- openWin(url,"qqLogin",650,500);
- }
- </script>
- <a href="javascript:void(0);" onclick="qqLogin()"></a>
② 在新窗口打开
- <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、由于做了多个登录,所以代码做了一定程度的封装,大致如下:
- package com.liuyanzhao.sens.service;
- import com.liuyanzhao.sens.model.dto.BindUserDTO;
- import com.liuyanzhao.sens.model.dto.JsonResult;
- import com.liuyanzhao.sens.utils.Response;
- import java.io.UnsupportedEncodingException;
- /**
- * @author 言曌
- * @date 2019/1/20 上午10:24
- */
- public interface AuthService {
- /**
- * 根据code获得Token
- *
- * @param code code
- * @return token
- */
- Response<String> getAccessToken(String code);
- /**
- * 根据Token获得OpenId
- *
- * @param accessToken Token
- * @return openId
- */
- Response<String> getOpenId(String accessToken);
- /**
- * 刷新Token
- *
- * @param code code
- * @return 新的token
- */
- Response<String> refreshToken(String code);
- /**
- * 拼接授权URL
- *
- * @return URL
- */
- Response<String> getAuthorizationUrl();
- /**
- * 根据Token和OpenId获得用户信息
- *
- * @param accessToken Token
- * @param openId openId
- * @return 第三方应用给的用户信息
- */
- Response<BindUserDTO> getUserInfo(String accessToken, String openId);
- }
2、由于全部是自己封装的,所以http请求的代码也是所有的登录共用的,这里统一放放到了类DefaultAuthServiceImpl中,代码如下:
- package com.liuyanzhao.sens.service.impl;
- import com.liuyanzhao.sens.service.AuthService;
- import org.springframework.http.client.SimpleClientHttpRequestFactory;
- import org.springframework.http.converter.ByteArrayHttpMessageConverter;
- import org.springframework.http.converter.HttpMessageConverter;
- import org.springframework.http.converter.ResourceHttpMessageConverter;
- import org.springframework.http.converter.StringHttpMessageConverter;
- import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
- import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
- import org.springframework.http.converter.xml.SourceHttpMessageConverter;
- import org.springframework.web.client.RestTemplate;
- import javax.xml.transform.Source;
- import java.nio.charset.StandardCharsets;
- import java.util.LinkedList;
- import java.util.List;
- /**
- * @author 言曌
- * @date 2018/5/9 下午3:02
- */
- public abstract class DefaultAuthServiceImpl implements AuthService {
- public static RestTemplate getRestTemplate() {// 手动添加
- SimpleClientHttpRequestFactory requestFactory=new SimpleClientHttpRequestFactory();
- requestFactory.setReadTimeout(120000);
- List<HttpMessageConverter<?>> messageConverters = new LinkedList<>();
- messageConverters.add(new ByteArrayHttpMessageConverter());
- messageConverters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
- messageConverters.add(new ResourceHttpMessageConverter());
- messageConverters.add(new SourceHttpMessageConverter<Source>());
- messageConverters.add(new AllEncompassingFormHttpMessageConverter());
- messageConverters.add(new MappingJackson2HttpMessageConverter());
- RestTemplate restTemplate=new RestTemplate(messageConverters);
- restTemplate.setRequestFactory(requestFactory);
- return restTemplate;
- }
- }
由此,所有的登录Service只需要继承AuthService即可。
3、QQ登录 Service 接口 QQAuthService.java
- package com.liuyanzhao.sens.service;
- /**
- * @author 言曌
- * @date 2018/5/9 下午3:00
- */
- public interface QQAuthService extends AuthService {
- }
4、QQ登录 Service 实现 QQAuthServiceImpl.java
- package com.liuyanzhao.sens.service.impl;
- import com.alibaba.fastjson.JSONObject;
- import com.liuyanzhao.sens.model.dto.BindUserDTO;
- import com.liuyanzhao.sens.service.QQAuthService;
- import com.liuyanzhao.sens.utils.Response;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.stereotype.Service;
- import org.springframework.web.util.UriComponentsBuilder;
- import java.net.URI;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * @author 言曌
- * @date 2018/5/9 下午3:15
- */
- @Service
- @Slf4j
- public class QQAuthServiceImpl extends DefaultAuthServiceImpl implements QQAuthService {
- //QQ 登陆页面的URL
- private final static String AUTHORIZATION_URL =
- "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s";
- //获取token的URL
- 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";
- // 获取用户 openid 的 URL
- private static final String OPEN_ID_URL = "https://graph.qq.com/oauth2.0/me?access_token=%s";
- // 获取用户信息的 URL,oauth_consumer_key 为 apiKey
- private static final String USER_INFO_URL = "https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s";
- // 下面的属性可以通过配置读取
- // QQ 在登陆成功后回调的 URL,这个 URL 必须在 QQ 互联里填写过
- private static final String CALLBACK_URL = "http://localhost:8080/oauth/qq/callback";
- // QQ 互联应用管理中心的 APP ID
- private static final String APP_ID = "111111111";
- // QQ 互联应用管理中心的 APP Key
- private static final String APP_SECRET = "111111111111111111111111111111111";
- // QQ 互联的 API 接口,访问用户资料
- private static final String SCOPE = "get_user_info";
- @Override
- public Response<String> getAuthorizationUrl() {
- String url = String.format(AUTHORIZATION_URL, APP_ID, CALLBACK_URL, SCOPE);
- return Response.yes(url);
- }
- @Override
- public Response<String> getAccessToken(String code) {
- String url = String.format(ACCESS_TOKEN_URL, APP_ID, APP_SECRET, code, CALLBACK_URL);
- UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
- URI uri = builder.build().encode().toUri();
- String resp = getRestTemplate().getForObject(uri, String.class);
- if (resp != null && resp.contains("access_token")) {
- Map<String, String> map = getParam(resp);
- String access_token = map.get("access_token");
- return Response.yes(access_token);
- }
- log.error("QQ获得access_token失败,code无效,resp:{}", resp);
- return Response.no("code无效!");
- }
- //由于QQ的几个接口返回类型不一样,此处是获取key-value类型的参数
- private Map<String, String> getParam(String string) {
- Map<String, String> map = new HashMap();
- String[] kvArray = string.split("&");
- for (int i = 0; i < kvArray.length; i++) {
- String[] kv = kvArray[i].split("=");
- map.put(kv[0], kv[1]);
- }
- return map;
- }
- //QQ接口返回类型是text/plain,此处将其转为json
- private JSONObject ConvertToJson(String string) {
- string = string.substring(string.indexOf("(") + 1, string.length());
- string = string.substring(0, string.indexOf(")"));
- JSONObject jsonObject = JSONObject.parseObject(string);
- return jsonObject;
- }
- @Override
- public Response<String> getOpenId(String accessToken) {
- String url = String.format(OPEN_ID_URL, accessToken);
- UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
- URI uri = builder.build().encode().toUri();
- String resp = getRestTemplate().getForObject(uri, String.class);
- if (resp != null && resp.contains("openid")) {
- JSONObject jsonObject = ConvertToJson(resp);
- String openid = jsonObject.getString("openid");
- return Response.yes(openid);
- }
- log.error("QQ获得openid失败,accessToken无效,resp:{}", resp);
- return Response.no(resp);
- }
- @Override
- public Response<BindUserDTO> getUserInfo(String accessToken, String openId) {
- String url = String.format(USER_INFO_URL, accessToken, APP_ID, openId);
- UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
- URI uri = builder.build().encode().toUri();
- String resp = getRestTemplate().getForObject(uri, String.class);
- JSONObject data = JSONObject.parseObject(resp);
- BindUserDTO result = new BindUserDTO();
- result.setOpenId(openId);
- result.setGender(data.getString("gender"));
- result.setAvatar(data.getString("figureurl_qq_2"));
- result.setNickname(data.getString("nickname"));
- return Response.yes(result);
- }
- @Override
- public Response<String> refreshToken(String code) {
- return null;
- }
- }
5、在Controller中调用,代码如下:
- package com.liuyanzhao.sens.web.controller.front;
- import cn.hutool.core.date.DateUtil;
- import cn.hutool.extra.servlet.ServletUtil;
- import com.liuyanzhao.sens.entity.Log;
- import com.liuyanzhao.sens.entity.ThirdAppBind;
- import com.liuyanzhao.sens.entity.User;
- import com.liuyanzhao.sens.model.dto.LogsRecord;
- import com.liuyanzhao.sens.model.dto.SensConst;
- import com.liuyanzhao.sens.model.enums.BindTypeEnum;
- import com.liuyanzhao.sens.service.*;
- import com.liuyanzhao.sens.utils.Response;
- import lombok.extern.slf4j.Slf4j;
- 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.RequestParam;
- import javax.servlet.http.HttpServletRequest;
- /**
- * @author 言曌
- * @date 2018/5/9 下午2:59
- */
- @Controller
- @Slf4j
- public class AuthController {
- @Autowired
- private QQAuthService qqAuthService;
- @Autowired
- private UserService userService;
- @Autowired
- private ThirdAppBindService thirdAppBindService;
- @Autowired
- private LogService logService;
- /**
- * 第三方授权后会回调此方法,并将code传过来
- *
- * @param code code
- * @return
- */
- @GetMapping("/oauth/qq/callback")
- public String oauthByQQ(@RequestParam(value = "code") String code, HttpServletRequest request) {
- Response<String> tokenResponse = qqAuthService.getAccessToken(code);
- if (tokenResponse.isSuccess()) {
- Response<String> openidResponse = qqAuthService.getOpenId(tokenResponse.getData());
- if (openidResponse.isSuccess()) {
- //根据openId去找关联的用户
- ThirdAppBind bind = thirdAppBindService.findByAppTypeAndOpenId(BindTypeEnum.QQ.getValue(), openidResponse.getData());
- if (bind != null && bind.getUserId() != null) {
- //执行Login操作
- User user = userService.findByUserId(bind.getUserId());
- if (user != null) {
- request.getSession().setAttribute(SensConst.USER_SESSION_KEY, user);
- logService.saveByLog(new Log(LogsRecord.LOGIN, LogsRecord.LOGIN_SUCCESS+"(QQ登录)", ServletUtil.getClientIP(request), DateUtil.date()));
- log.info("用户[{}]登录成功(QQ登录)。", user.getUserDisplayName());
- return "redirect:/admin";
- }
- }
- }
- }
- return "redirect:/admin/login";
- }
- }
2018年11月28日 17:33:50
你好,在编辑的过程中出现了问题,麻烦你把源码发我邮箱谢谢