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

接着上一篇文章SpringBoot网站添加第三方登录之QQ登录,本文介绍第三方登录之 GitHub。 GitHub 授权相对比较简单,因为无需审核,分分钟搞定。  

一、创建应用

1、登录 github 后台,创建应用 地址:https://github.com/settings/developers 或者在个人设置里,点最下面那个开发者设置   进去后,创建一个应用。如图,我已经创建了一个应用 查看应用,可以看到应用的id和密码 这里没什么好说的,跟 QQ 登录类似   2、授权的基本原理 可以参考官方文档 1)根据 GitHub 登录链接可以回调获得 code 2)根据Client ID 、Client Secret 和 code 可获得 token 3)根据 token 获得 用户信息   其中 id 是一个唯一的值,比如上面的 25662729   3、几个必要的URL 参考官方文档 1)登录页面授权 URL: https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&state=%s   2)获得 Token 的 URL: https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s&redirect_uri=%s&state=%s     3)获得用户信息的 URL:   https://api.github.com/user?access_token=%s  

二、基本准备

1、数据库设计   直接看图片   2、放置 Github 登录按钮 这里有两种常用的方式 ① 弹小窗
  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://github.com/login/oauth/authorize?client_id=96bb0592e6b3b8f2fb90&redirect_uri=http://codergroup.cn/oauth/github/callback&state=LiuYanzhaoLoveLuoQiNeverGiveUp";
  11.         openWin(url,"qqLogin",650,500);
  12.     }
  13. </script>
  14. <a  href="javascript:void(0);" onclick="qqLogin()"></a>
  ② 在新窗口打开
  1. <a href="https://github.com/login/oauth/authorize?client_id=96bb0511e6b3b8f2fb90&redirect_uri=http://codergroup.cn/oauth/github/callback&state=use-login" target="_blank"></a>
  点击链接后,跳到授权页面  

三、具体代码

参考上一篇文章的 QQ 登录 1、由于做了多个登录,所以代码做了一定程度的封装,大致如下:
  1. package com.liuyanzhao.sens.service;
  2. import com.liuyanzhao.sens.model.dto.BindUserDTO;
  3. import com.liuyanzhao.sens.model.dto.JsonResult;
  4. import com.liuyanzhao.sens.utils.Response;
  5. import java.io.UnsupportedEncodingException;
  6. /**
  7.  * @author 言曌
  8.  * @date 2019/1/20 上午10:24
  9.  */
  10. public interface AuthService {
  11.     /**
  12.      * 根据code获得Token
  13.      *
  14.      * @param code code
  15.      * @return token
  16.      */
  17.     Response<String> getAccessToken(String code);
  18.     /**
  19.      * 根据Token获得OpenId
  20.      *
  21.      * @param accessToken Token
  22.      * @return openId
  23.      */
  24.     Response<String> getOpenId(String accessToken);
  25.     /**
  26.      * 刷新Token
  27.      *
  28.      * @param code code
  29.      * @return 新的token
  30.      */
  31.     Response<String> refreshToken(String code);
  32.     /**
  33.      * 拼接授权URL
  34.      *
  35.      * @return URL
  36.      */
  37.     Response<String> getAuthorizationUrl();
  38.     /**
  39.      * 根据Token和OpenId获得用户信息
  40.      *
  41.      * @param accessToken Token
  42.      * @param openId openId
  43.      * @return 第三方应用给的用户信息
  44.      */
  45.     Response<BindUserDTO> getUserInfo(String accessToken, String openId);
  46. }
  2、由于全部是自己封装的,所以http请求的代码也是所有的登录共用的,这里统一放放到了类DefaultAuthServiceImpl中,代码如下:
  1. package com.liuyanzhao.sens.service.impl;
  2. import com.liuyanzhao.sens.service.AuthService;
  3. import org.springframework.http.client.SimpleClientHttpRequestFactory;
  4. import org.springframework.http.converter.ByteArrayHttpMessageConverter;
  5. import org.springframework.http.converter.HttpMessageConverter;
  6. import org.springframework.http.converter.ResourceHttpMessageConverter;
  7. import org.springframework.http.converter.StringHttpMessageConverter;
  8. import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
  9. import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
  10. import org.springframework.http.converter.xml.SourceHttpMessageConverter;
  11. import org.springframework.web.client.RestTemplate;
  12. import javax.xml.transform.Source;
  13. import java.nio.charset.StandardCharsets;
  14. import java.util.LinkedList;
  15. import java.util.List;
  16. /**
  17.  * @author 言曌
  18.  * @date 2018/5/9 下午3:02
  19.  */
  20. public abstract class DefaultAuthServiceImpl implements AuthService {
  21.     public static RestTemplate getRestTemplate() {// 手动添加
  22.         SimpleClientHttpRequestFactory requestFactory=new SimpleClientHttpRequestFactory();
  23.         requestFactory.setReadTimeout(120000);
  24.         List<HttpMessageConverter<?>> messageConverters = new LinkedList<>();
  25.         messageConverters.add(new ByteArrayHttpMessageConverter());
  26.         messageConverters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
  27.         messageConverters.add(new ResourceHttpMessageConverter());
  28.         messageConverters.add(new SourceHttpMessageConverter<Source>());
  29.         messageConverters.add(new AllEncompassingFormHttpMessageConverter());
  30.         messageConverters.add(new MappingJackson2HttpMessageConverter());
  31.         RestTemplate restTemplate=new RestTemplate(messageConverters);
  32.         restTemplate.setRequestFactory(requestFactory);
  33.         return restTemplate;
  34.     }
  35. }
由此,所有的登录Service只需要继承AuthService即可。   3、GithubAuthService.java
  1. package com.liuyanzhao.sens.service;
  2. /**
  3.  * @author 言曌
  4.  * @date 2018/5/15 下午11:28
  5.  */
  6. public interface GithubAuthService extends AuthService {
  7. }
  4、GithubAuthServiceImpl.java
  1. package com.liuyanzhao.sens.service.impl;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.liuyanzhao.sens.model.dto.BindUserDTO;
  4. import com.liuyanzhao.sens.service.GithubAuthService;
  5. import com.liuyanzhao.sens.utils.Response;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.web.util.UriComponentsBuilder;
  9. import java.net.URI;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. /**
  13.  * @author 言曌
  14.  * @date 2018/5/15 下午11:31
  15.  */
  16. @Service
  17. @Slf4j
  18. public class GithubAuthServiceImpl extends DefaultAuthServiceImpl implements GithubAuthService {
  19.     private static final String AUTHORIZE_URL = "https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&state=%s";
  20.     private static final String ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s&redirect_uri=%s&state=%s";
  21.     private static final String USER_INFO_URL = "https://api.github.com/user?access_token=%s";
  22.     // 下面的属性可以通过配置读取
  23.     private static final String CALLBACK_URL = "http://localhost:8080/oauth/github/callback";//回调地址
  24.     private static final String API_KEY = "11111111111111111";//Client ID
  25.     private static final String API_SECRET = "1111111111111111111111111111111";//Client Secret
  26.     private static final String GITHUB_STATE = "use-login";//state,随便填,会返回原值给你
  27.     //此处是获取key-value类型的参数
  28.     private Map<String, String> getParam(String string) {
  29.         Map<String, String> map = new HashMap();
  30.         String[] kvArray = string.split("&");
  31.         for (int i = 0; i < kvArray.length; i++) {
  32.             String[] kv = kvArray[i].split("=");
  33.             if (kv.length == 2) {
  34.                 map.put(kv[0], kv[1]);
  35.             } else if (kv.length == 1) {
  36.                 map.put(kv[0], "");
  37.             }
  38.         }
  39.         return map;
  40.     }
  41.     @Override
  42.     public Response<String> getAccessToken(String code) {
  43.         String url = String.format(ACCESS_TOKEN_URL, API_KEY, API_SECRET, code, CALLBACK_URL, GITHUB_STATE);
  44.         UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
  45.         URI uri = builder.build().encode().toUri();
  46.         String resp;
  47.         try {
  48.             resp = getRestTemplate().getForObject(uri, String.class);
  49.         } catch (Exception e) {
  50.             log.error("Github获得access_token失败,code不正确或者已过期, cause:{}", e);
  51.             return Response.no("code无效");
  52.         }
  53.         if (resp != null && resp.contains("access_token")) {
  54.             Map<String, String> map = getParam(resp);
  55.             String access_token = map.get("access_token");
  56.             return Response.yes(access_token);
  57.         }
  58.         log.error("GitHub登录失败,code不正确或者已过期:");
  59.         return Response.no("code无效");
  60.     }
  61.     @Override
  62.     public Response<String> getOpenId(String accessToken) {
  63.         String url = String.format(USER_INFO_URL, accessToken);
  64.         UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
  65.         URI uri = builder.build().encode().toUri();
  66.         String resp;
  67.         try {
  68.             resp = getRestTemplate().getForObject(uri, String.class);
  69.         } catch (Exception e) {
  70.             log.error("GitHub获得OpenId失败,access_token无效, cause:{}", e);
  71.             return Response.no("access_token无效!");
  72.         }
  73.         if (resp != null && resp.contains("id")) {
  74.             JSONObject data = JSONObject.parseObject(resp);
  75.             String openid = data.getString("id");
  76.             return Response.yes(openid);
  77.         }
  78.         return Response.no("access_token无效!");
  79.     }
  80.     @Override
  81.     public Response<String> refreshToken(String code) {
  82.         return null;
  83.     }
  84.     @Override
  85.     public Response<String> getAuthorizationUrl() {
  86.         String url = String.format(AUTHORIZE_URL, API_KEY, CALLBACK_URL, GITHUB_STATE);
  87.         return Response.yes(url);
  88.     }
  89.     @Override
  90.     public Response<BindUserDTO> getUserInfo(String accessToken, String openId) {
  91.         String url = String.format(USER_INFO_URL, accessToken);
  92.         UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
  93.         URI uri = builder.build().encode().toUri();
  94.         String resp;
  95.         try {
  96.             resp = getRestTemplate().getForObject(uri, String.class);
  97.         } catch (Exception e) {
  98.             log.error("GitHub获得用户信息失败,access_token无效, cause:{}", e);
  99.             return Response.no("access_token无效!");
  100.         }
  101.         if (resp != null && resp.contains("id")) {
  102.             JSONObject data = JSONObject.parseObject(resp);
  103.             BindUserDTO result = new BindUserDTO();
  104.             result.setOpenId(data.getString("id"));
  105.             result.setAvatar(data.getString("avatar_url"));
  106.             result.setNickname(data.getString("name"));
  107.             return Response.yes(result);
  108.         }
  109.         return Response.no("access_token无效!");
  110.     }
  111. }
  5、Controller,根据项目不同,自己修改 这里把qq登录和github登录,未登录之前绑定,登录后再后台绑定融合到了一起。
  1. package com.liuyanzhao.sens.web.controller.front;
  2. import cn.hutool.core.date.DateUtil;
  3. import cn.hutool.extra.servlet.ServletUtil;
  4. import com.liuyanzhao.sens.entity.Log;
  5. import com.liuyanzhao.sens.entity.ThirdAppBind;
  6. import com.liuyanzhao.sens.entity.User;
  7. import com.liuyanzhao.sens.model.dto.LogsRecord;
  8. import com.liuyanzhao.sens.model.dto.SensConst;
  9. import com.liuyanzhao.sens.model.enums.BindTypeEnum;
  10. import com.liuyanzhao.sens.service.*;
  11. import com.liuyanzhao.sens.utils.Response;
  12. import lombok.extern.slf4j.Slf4j;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.stereotype.Controller;
  15. import org.springframework.web.bind.annotation.GetMapping;
  16. import org.springframework.web.bind.annotation.RequestParam;
  17. import javax.servlet.http.HttpServletRequest;
  18. /**
  19.  * @author 言曌
  20.  * @date 2018/5/9 下午2:59
  21.  */
  22. @Controller
  23. @Slf4j
  24. public class AuthController {
  25.     @Autowired
  26.     private GithubAuthService githubAuthService;
  27.     @Autowired
  28.     private UserService userService;
  29.     @Autowired
  30.     private ThirdAppBindService thirdAppBindService;
  31.     @Autowired
  32.     private LogService logService;
  33.     /**
  34.      * 第三方授权后会回调此方法,并将code传过来
  35.      *
  36.      * @param code code
  37.      * @return
  38.      */
  39.     @GetMapping("/oauth/github/callback")
  40.     public String oauthByGitHub(@RequestParam(value = "code") String code,
  41.                                 HttpServletRequest request) {
  42.         Response<String> tokenResponse = githubAuthService.getAccessToken(code);
  43.         if (tokenResponse.isSuccess()) {
  44.             Response<String> openidResponse = githubAuthService.getOpenId(tokenResponse.getData());
  45.             if (openidResponse.isSuccess()) {
  46.                 //根据openId去找关联的用户
  47.                 ThirdAppBind bind = thirdAppBindService.findByAppTypeAndOpenId(BindTypeEnum.GITHUB.getValue(), openidResponse.getData());
  48.                 if (bind != null && bind.getUserId() != null) {
  49.                     //执行Login操作
  50.                     User user = userService.findByUserId(bind.getUserId());
  51.                     if (user != null) {
  52.                         request.getSession().setAttribute(SensConst.USER_SESSION_KEY, user);
  53.                         logService.saveByLog(new Log(LogsRecord.LOGIN, LogsRecord.LOGIN_SUCCESS+"(GitHub登录)", ServletUtil.getClientIP(request), DateUtil.date()));
  54.                         log.info("用户[{}]登录成功(登录)。", user.getUserDisplayName());
  55.                         return "redirect:/admin";
  56.                     }
  57.                 }
  58.             }
  59.         }
  60.         return "redirect:/admin/login";
  61.     }
  62. }
   

发表评论

目前评论:1