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

接着上一篇文章SpringBoot网站添加第三方登录之QQ登录,本文介绍第三方登录之 GitHub。

GitHub 授权相对比较简单,因为无需审核,分分钟搞定。

 

一、创建应用

1、登录 github 后台,创建应用

地址:https://github.com/settings/developers

或者在个人设置里,点最下面那个开发者设置

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

 

进去后,创建一个应用。如图,我已经创建了一个应用

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

查看应用,可以看到应用的id和密码

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

这里没什么好说的,跟 QQ 登录类似

 

2、授权的基本原理

可以参考官方文档

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

2)根据Client ID 、Client Secret 和 code 可获得 token

3)根据 token 获得 用户信息

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

 

其中 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、数据库设计

主要看 bind 和 bind_type 表的数据

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

identifier 表示识别码,保证唯一性

credential 表示凭证,通常用不上

bind_type_id 和 user_id 是外键,很好理解

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

name 唯一性

style 样式

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=96bb0592e6b3b8f2fb90&redirect_uri=http://codergroup.cn/oauth/github/callback&state=LiuYanzhaoLoveLuoQiNeverGiveUp" target="_blank"></a>

 

点击链接后,跳到授权页面

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

 

三、具体代码

参考上一篇文章的 QQ 登录

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

  1. //多个登录差不多都要共用这些方法,所以统一放到这个接口中
  2. public interface AuthService {
  3.     public abstract String getAccessToken(String code);
  4.     public abstract String getOpenId(String accessToken);
  5.     public abstract String refreshToken(String code);
  6.     public abstract String getAuthorizationUrl() throws UnsupportedEncodingException;
  7.     public abstract JSONObject getUserInfo(String accessToken,String openId);
  8. }

 

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

  1. public abstract class DefaultAuthServiceImpl implements AuthService{
  2.     public static RestTemplate getRestTemplate() {// 手动添加
  3.         SimpleClientHttpRequestFactory requestFactory=new SimpleClientHttpRequestFactory();
  4.         requestFactory.setReadTimeout(120000);
  5.         List<HttpMessageConverter<?>> messageConverters = new LinkedList<>();
  6.         messageConverters.add(new ByteArrayHttpMessageConverter());
  7.         messageConverters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
  8.         messageConverters.add(new ResourceHttpMessageConverter());
  9.         messageConverters.add(new SourceHttpMessageConverter<Source>());
  10.         messageConverters.add(new AllEncompassingFormHttpMessageConverter());
  11.         messageConverters.add(new MappingJackson2HttpMessageConverter());
  12.         RestTemplate restTemplate=new RestTemplate(messageConverters);
  13.         restTemplate.setRequestFactory(requestFactory);
  14.         return restTemplate;
  15.     }
  16. }

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

 

3、GithubAuthService.java

  1. public interface GithubAuthService  extends AuthService {
  2. }

 

4、GithubAuthServiceImpl.java

  1. package com.liuyanzhao.forum.service.impl;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.liuyanzhao.forum.service.GithubAuthService;
  4. import com.liuyanzhao.forum.vo.BindUser;
  5. import org.hibernate.service.spi.ServiceException;
  6. import org.springframework.stereotype.Service;
  7. import org.springframework.web.util.UriComponentsBuilder;
  8. import java.io.UnsupportedEncodingException;
  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. public class GithubAuthServiceImpl extends DefaultAuthServiceImpl implements GithubAuthService {
  18.     private static final String AUTHORIZE_URL = "https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&state=%s";
  19.     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";
  20.     private static final String USER_INFO_URL = "https://api.github.com/user?access_token=%s";
  21.     // 下面的属性可以通过配置读取
  22.     private static final String CALLBACK_URL = "http://codergroup.cn/oauth/github/callback";//回调地址
  23.     private static final String API_KEY = "####";//Client ID
  24.     private static final String API_SECRET = "####";//Client Secret
  25.     private static final String GITHUB_STATE = "LiuYanzhaoLoveLuoQiNeverGiveUp";//state,随便填,但要一致
  26.     //此处是获取key-value类型的参数
  27.     private Map<String, String> getParam(String string) {
  28.         Map<String, String> map = new HashMap();
  29.         String[] kvArray = string.split("&");
  30.         for (int i = 0; i < kvArray.length; i++) {
  31.             String[] kv = kvArray[i].split("=");
  32.             if(kv.length == 2) {
  33.                 map.put(kv[0], kv[1]);
  34.             } else if(kv.length == 1) {
  35.                 map.put(kv[0],"");
  36.             }
  37.         }
  38.         return map;
  39.     }
  40.     @Override
  41.     public String getAccessToken(String code) {
  42.         String url = String.format(ACCESS_TOKEN_URL, API_KEY, API_SECRET, code, CALLBACK_URL,GITHUB_STATE);
  43.         UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
  44.         URI uri = builder.build().encode().toUri();
  45.         String resp = getRestTemplate().getForObject(uri, String.class);
  46.         if (resp.contains("access_token")) {
  47.             Map<String, String> map = getParam(resp);
  48.             String access_token = map.get("access_token");
  49.             return access_token;
  50.         } else {
  51.             throw new ServiceException(resp);
  52.         }
  53.     }
  54.     @Override
  55.     public String getOpenId(String accessToken) {
  56.         return null;
  57.     }
  58.     @Override
  59.     public String refreshToken(String code) {
  60.         return null;
  61.     }
  62.     @Override
  63.     public String getAuthorizationUrl() throws UnsupportedEncodingException {
  64.         return String.format(AUTHORIZE_URL,API_KEY,CALLBACK_URL,GITHUB_STATE);
  65.     }
  66.     @Override
  67.     public BindUser getUserInfo(String accessToken, String openId) {
  68.         String url = String.format(USER_INFO_URL, accessToken);
  69.         UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
  70.         URI uri = builder.build().encode().toUri();
  71.         String resp = getRestTemplate().getForObject(uri, String.class);
  72.         JSONObject data = JSONObject.parseObject(resp);
  73.         BindUser result = new BindUser();
  74.         result.setOpenId(data.getString("id"));
  75.         result.setAvatar(data.getString("avatar_url"));
  76.         result.setNickname(data.getString("name"));
  77.         return result;
  78.     }
  79. }

 

5、Controller,根据项目不同,自己修改

这里把qq登录和github登录,未登录之前绑定,登录后再后台绑定融合到了一起。

  1. package com.liuyanzhao.forum.controller;
  2. import com.liuyanzhao.forum.entity.Bind;
  3. import com.liuyanzhao.forum.entity.BindType;
  4. import com.liuyanzhao.forum.entity.User;
  5. import com.liuyanzhao.forum.repository.BindTypeRepository;
  6. import com.liuyanzhao.forum.service.BindService;
  7. import com.liuyanzhao.forum.service.GithubAuthService;
  8. import com.liuyanzhao.forum.service.QQAuthService;
  9. import com.liuyanzhao.forum.service.UserService;
  10. import com.liuyanzhao.forum.vo.BindUser;
  11. import org.slf4j.Logger;
  12. import org.slf4j.LoggerFactory;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.security.authentication.AuthenticationManager;
  15. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  16. import org.springframework.security.core.Authentication;
  17. import org.springframework.security.core.context.SecurityContextHolder;
  18. import org.springframework.security.web.authentication.WebAuthenticationDetails;
  19. import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
  20. import org.springframework.stereotype.Controller;
  21. import org.springframework.ui.Model;
  22. import org.springframework.web.bind.annotation.*;
  23. import javax.servlet.http.HttpServletRequest;
  24. import javax.servlet.http.HttpServletResponse;
  25. /**
  26.  * @author 言曌
  27.  * @date 2018/5/9 下午2:59
  28.  */
  29. @Controller
  30. public class AuthController {
  31.     private Logger logger = LoggerFactory.getLogger(AuthController.class);
  32.     @Autowired
  33.     private QQAuthService qqAuthService;
  34.     @Autowired
  35.     private GithubAuthService githubAuthService;
  36.     @Autowired
  37.     private UserService userService;
  38.     @Autowired
  39.     protected AuthenticationManager authenticationManager;
  40.     @Autowired
  41.     private BindTypeRepository bindTypeRepository;
  42.     @Autowired
  43.     private BindService bindService;
  44.     //第三方授权后会回调此方法,并将code传过来
  45.     @GetMapping("/oauth/{type}/callback")
  46.     public String auth(@RequestParam(value = "code") String code,
  47.                        @PathVariable(value = "type") String type,
  48.                        HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {
  49.         //1、判断类型是否存在
  50.         BindType bindType = bindTypeRepository.findByName(type);
  51.         if (bindType == null) {
  52.             return "error/404";
  53.         }
  54.         //2、根据code获取token,根据openId判断用户是否已经绑定过
  55.         String accessToken = null;
  56.         String openId = null;
  57.         BindUser userInfo = null;
  58.         String identifier = null;
  59.         if ("qq".equals(type)) {
  60.             try {
  61.                 accessToken = qqAuthService.getAccessToken(code);
  62.             } catch (Exception e) {
  63.                 e.printStackTrace();
  64.                 response.sendRedirect(qqAuthService.getAuthorizationUrl());
  65.             }
  66.             openId = qqAuthService.getOpenId(accessToken);
  67.             userInfo = qqAuthService.getUserInfo(accessToken, openId);
  68.             identifier = openId;
  69.         } else if ("github".equals(type)) {
  70.             try {
  71.                 accessToken = githubAuthService.getAccessToken(code);
  72.             } catch (Exception e) {
  73.                 e.printStackTrace();
  74.                 response.sendRedirect(githubAuthService.getAuthorizationUrl());
  75.             }
  76.             userInfo = githubAuthService.getUserInfo(accessToken, null);
  77.             identifier = userInfo.getOpenId();
  78.         } else {
  79.             return "error/404";
  80.         }
  81.         userInfo.setBindType(bindType);
  82.         request.getSession().setAttribute("bindUser", userInfo);
  83.         User user = userService.getUserByCondition(bindType, identifier);
  84.         //3、判断是否登录
  85.         //如果登录,绑定当前用户
  86.         if (SecurityContextHolder.getContext().getAuthentication() != null && SecurityContextHolder.getContext().getAuthentication().isAuthenticated()
  87.                 && !SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString().equals("anonymousUser")) {
  88.             User principal = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  89.             Bind bind = new Bind();
  90.             bind.setBindType(bindType);
  91.             bind.setUser(principal);
  92.             bind.setIdentifier(userInfo.getOpenId());
  93.             bindService.saveBind(bind);
  94.             return "redirect:/manage/account/bind";
  95.         } else {
  96.             //如果未登录,进行登录或注册
  97.             //如果用户不存在,则跳转到绑定页面
  98.             if (user == null) {
  99.                 return "redirect:/bind";
  100.             } else {
  101.                 //如果用户已存在,则授权登录
  102.                 //授权登录
  103.                 UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
  104.                 token.setDetails(new WebAuthenticationDetails(request));
  105.                 Authentication authenticatedUser = authenticationManager.authenticate(token);
  106.                 SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
  107.                 request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
  108.                 //跳到首页
  109.                 return "home/common/auth_success";
  110.             }
  111.         }
  112.     }
  113. }

 

 

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

发表评论

:?::razz::sad::evil::!::smile::oops::grin::eek::shock::???::cool::lol::mad::twisted::roll::wink::idea::arrow::neutral::cry::mrgreen: