一、OAuth2简介
OAuth是目前最流行的授权机制,用来授权第三方应用,获取用户数据。OAuth在全世界已经得到广泛应用,目前的版本是2.0版。简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。 因此令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异:
- 令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化。
- 令牌可以被数据所有者撤销,会立即失效。
- 令牌有权限范围(scope)
因为令牌有着和密码一样的功能,所以对于令牌也必须严格保密,泄漏令牌和泄漏密码的后果是一样的。这也就是为啥令牌的有效期很短的原因了。
二、GitHub第三方登录的原理
第三方登录实际就是OAuth授权认证。用户想要登录 A 网站,A 网站让用户提供第三方网站的数据,证明自己的身份。获取第三方网站的身份数据,就需要 OAuth 授权。
以使用GitHub为第三方登录为例:
- 用户在网站点击使用GitHub登录,A网站跳转到GitHub(会带上回调URI和Client ID )
- GitHub 要求用户登录,然后询问"A 网站要求获得 xx 权限,你是否同意?"(对于同一个网站,同意一次之后,下次再登录就不在需要用户授权)
- 用户同意,GitHub 就会重定向回 A 网站,同时发回一个授权码。
- A 网站使用授权码,向 GitHub 请求令牌。
- GitHub 返回令牌.
- 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授权登录功能就算完成了。哪里有不清楚的地方可以在评论区留言。