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

一、OAuth2简介

OAuth是目前最流行的授权机制,用来授权第三方应用,获取用户数据。OAuth在全世界已经得到广泛应用,目前的版本是2.0版。简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。 因此令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异:

  • 令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化。
  • 令牌可以被数据所有者撤销,会立即失效。
  • 令牌有权限范围(scope)

因为令牌有着和密码一样的功能,所以对于令牌也必须严格保密,泄漏令牌和泄漏密码的后果是一样的。这也就是为啥令牌的有效期很短的原因了。

二、GitHub第三方登录的原理

第三方登录实际就是OAuth授权认证。用户想要登录 A 网站,A 网站让用户提供第三方网站的数据,证明自己的身份。获取第三方网站的身份数据,就需要 OAuth 授权。

以使用GitHub为第三方登录为例:

  1. 用户在网站点击使用GitHub登录,A网站跳转到GitHub(会带上回调URI和Client ID )
  2. GitHub 要求用户登录,然后询问"A 网站要求获得 xx 权限,你是否同意?"(对于同一个网站,同意一次之后,下次再登录就不在需要用户授权)
  3. 用户同意,GitHub 就会重定向回 A 网站,同时发回一个授权码。
  4. A 网站使用授权码,向 GitHub 请求令牌。
  5. GitHub 返回令牌.
  6. A 网站使用令牌,向 GitHub 请求用户数据。

使用GitHub做第三方登录认证的具体的流程可如下图所示:

好了,原理大致了解到这里,接下来动手搭建一下GitHub第三方登录。

三、GitHub第三方登录搭建示例

1、创建应用

① 登录 github 后台(地址:https://github.com/settings/developers ),创建应用你需要添加GitHub登录的应用。或者在GitHub的settings 中点击Deveploper settings

② 之后直接点击New GitHub App

③ 之后填写你的App有关的信息即可

在注册成功之后,GitHub 会返回客户端 ID(client ID)和客户端密钥(client secret),这就是应用的身份识别码,在授权的时候会用到。

一个填写示例:

2、几个重要的URL
  • 登录页面授权 URL:https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&state=%s
  • 获得 Token 的 URL:https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s&redirect_uri=%s&state=%s
  • 获得用户信息的 URL:https://api.github.com/user?access_token=%s
3、设计数据库

设计一个数据表用于存放用户的认证记录,用于认证之后就将他的认证信息存放到这个表中,并和主用户表(User)绑定。

之后Mapper(DAO)、Service层自己按照自己业务逻辑自行实现,准备好有关接口等待Controller的调用。

4、放置GitHub登录按钮
<form action="#" th:action="@{/user/login}" method="post">
	<div class="sign-in-htm">
		<div class="group">
			<label class="label">手机号/邮箱</label>
			<input id="username" name="username" type="text" class="input" list="mails">
			<datalist id="mails"></datalist>
		</div>
		<div class="group" style="position: relative;">
			<label for="pass" class="label">密码</label>
			<input id="password" name="password" type="password" class="input">
			<span id="login-password-display-toggle" class="password-hide" style=":before;position: absolute;right:2px;top:18px;cursor: pointer;"></span>
		</div>
		<div class="group" >
		   <input id="check1" name="remember-me" type="checkbox" class="check" checked>
		   <label for="check1" style="font-size: 14px"> <span class="icon txt1"></span> 保持登录</label>
			<a href="#" class="txt1" style="float: right" th:href="@{/user/change_password}">忘记密码?</a>
		</div>
		<div class="group" style="margin-top: 20px">
			<input id="login-btn" style="cursor: pointer;"  type="submit"  class="button" value="登录">
		</div>
		<div  class="txt1 text-center p-t-54 p-b-20"><span>第三方登录</span></div>
		<div class="flex-c-m">
            <!--gitHub登录,这里不建议直接把登录页面授权 URL那一长串直接写在这里,可以在后台重定向到GitHub授权页面-->
			<a href="#" th:href="@{/oauth/github/}" title="github登录" class="login100-social-item bg4"><i class="fa fa-github"></i></a>
		</div>
	</div>
</form>

点击GitHub登录之后的授权页面:

5、后台代码具体实现

① 首先在SpringBoot配置文件配置如下信息:

#第三方授权登录
oauth:
  github:
    client-id: your_client-id
    client-secret: your_client-secret
    redirectUrl: http://127.0.0.1:8081/oauth/github/callback
    authorizeUrl: https://github.com/login/oauth/authorize
    accessTokenUrl: https://github.com/login/oauth/access_token
    userInfoUrl: https://api.github.com/user

② 之后写一个配置类用于获取这些文件中的这些信息

/**
 * @author :huangxin
 * @modified :
 * @since :2020/05/28 11:18
 */
@Data
@Component
@ConfigurationProperties(prefix = "oauth.github",ignoreUnknownFields = true)
public class GitHubProperties {
    private String clientId;
    private String clientSecret;
    private String authorizeUrl;
    private String redirectUrl;
    private String accessTokenUrl;
    private String userInfoUrl;
}

③ 为了提高系统的扩展性,比如以后还可以做个QQ登录、微信登录.....,因此这里提供一个接口用于认证服务

/**
 * @author :huangxin
 * @modified :
 * @since :2020/05/28 15:47
 */
public interface IAuthService<T> {

    /**
     * 根据code获得Token
     *
     * @param code code
     * @return token
     */
    String getAccessToken(String code);

    /**
     * 根据Token获得OpenId
     *
     * @param accessToken Token
     * @return openId
     */
    String getOpenId(String accessToken);

    /**
     * 刷新Token
     *
     * @param code code
     * @return 新的token
     */
    String refreshToken(String code);

    /**
     * 拼接授权URL
     *
     * @return URL
     */
    String getAuthorizationUrl();

    /**
     * 根据Token和OpenId获得用户信息
     *
     * @param accessToken Token
     * @return 第三方应用给的用户信息
     */
    T getUserInfo(String accessToken);

}

GitHub认证授权实现类:

/**
 * @author :huangxin
 * @modified :
 * @since :2020/05/28 15:50
 */
@Service
@Slf4j
public class GitHubAuthServiceImpl implements IAuthService<GitHubUser>{

    @Autowired
    private GitHubProperties gitHubProperties;

    @Autowired
    private OauthMapper oauthMapper;

    /**
     * 根据GitHub返回的code获得token
     *
     * @param code code
     * @return Token
     */
    @Override
    public String getAccessToken(String code) {
        String url = gitHubProperties.getAccessTokenUrl() +
                "?client_id=" + gitHubProperties.getClientId() +
                "&client_secret=" + gitHubProperties.getClientSecret() +
                "&code=" + code +
                "&grant_type=authorization_code";
        log.info("getAccessToke url : {}", url);
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.add("accept", "application/json");
        HttpEntity<String> requestEntity = new HttpEntity<>(requestHeaders);
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
        //响应内容
        String responseStr = response.getBody();
        log.info("responseStr={}", responseStr);

        //JSON解析响应的内容
        JSONObject object = JSON.parseObject(responseStr);
        String accessToken = object.getString("access_token");
        log.info("accessToken={}", accessToken);
        return accessToken;
    }

    @Override
    public String getOpenId(String accessToken) {
        return null;
    }

    @Override
    public String refreshToken(String code) {
        return null;
    }

    /**拼接GitHub授权登录页面的URL*/
    @Override
    public String getAuthorizationUrl() {
        return gitHubProperties.getAuthorizeUrl() +
                "?client_id=" + gitHubProperties.getClientId() +
                "&redirect_uri=" + gitHubProperties.getRedirectUrl();
    }

    /**根据token获取用户的信息*/
    @Override
    public GitHubUser getUserInfo(String accessToken) {
        String userInfoUrl = gitHubProperties.getUserInfoUrl();
        log.info("getUserInfo url:{}", userInfoUrl);

        HttpHeaders headers = new HttpHeaders();
        headers.add("accept", "application/json");
        // AccessToken放在请求头中
        headers.add("Authorization", "token " + accessToken);
        // 构建请求实体
        HttpEntity<String> requestEntity = new HttpEntity<>(headers);
        RestTemplate restTemplate = new RestTemplate();
        // get请求方式
        ResponseEntity<String> response = restTemplate.exchange(userInfoUrl, HttpMethod.GET, requestEntity, String.class);
        String userInfo = response.getBody();
        GitHubUser gitHubUser = JSON.parseObject(userInfo, GitHubUser.class);
        assert gitHubUser != null;
        log.info("gitHubUser info={}", gitHubUser.toString());
        return gitHubUser;
    }

}

④ GitHub认证控制器(Controller)

@Slf4j
@Controller
@RequestMapping("/oauth/github")
public class GitHubOauthController {

    @Autowired
    private IAuthService<GitHubUser> githubAuthService;
    @Autowired
    private IOauthService<GitHubUser> oauthService;

    /**
     * 让用户跳转到 GitHub授权页面
     *
     * @return 跳转url
     */
    @GetMapping(value = "/")
    public String authorize() {
        String url = githubAuthService.getAuthorizationUrl();
        log.info("Authorize url:{}", url);
        return "redirect:" + url;
    }


    /**
     * GitHub回调接口:当用户同一授权之后github调用此接口,用于返回到系统的指定页面
     *
     * @param code github回调的时候返回的授权码(使用过后据失效)
     * @return 指定页面
     */
    @GetMapping("/callback")
    public String callback(@RequestParam(value = "code") String code,
                           HttpServletRequest request,
                           HttpServletResponse response,
                           RedirectAttributes redirectAttributes) {
        String accessToken = githubAuthService.getAccessToken(code);
        //获得用户在Github上的账户信息
        GitHubUser gitHubUser = githubAuthService.getUserInfo(accessToken);
        //......在这里就可以获取有关GitHub上这个用户的信息了,这里的设计可以根据自己的系统做具体的实现
        log.info("redirect to : index");
        return "redirect:/";
    }

}

GitHub返回的用户信息中的id在GitHub上是一个唯一的Id,这个可以作为用户是否在使用GitHub在系统登录过的依据。

好了,这里我们的Github授权登录功能就算完成了。哪里有不清楚的地方可以在评论区留言。

留言区

还能输入500个字符