基于oauth2.0实现应用的第三方登录
OAuth2
OAuth2所涉及到的对象主要有以下四个:
- Client 第三方应用,我们的应用就是一个Client
- Resource Owner 资源所有者,即用户
- Authorization Server 授权服务器,即提供第三方登录服务的服务器,如Github
- Resource Server 拥有资源信息的服务器,通常和授权服务器属于同一应用
OAuth2的基本流程为:
- 第三方应用请求用户授权。
- 用户同意授权,并返回一个凭证(code)
- 第三方应用通过第二步的凭证(code)向授权服务器请求授权
- 授权服务器验证凭证(code)通过后,同意授权,并返回一个资源访问的凭证(Access Token)。
- 第三方应用通过第四步的凭证(Access Token)向资源服务器请求相关资源。
- 资源服务器验证凭证(Access Token)通过后,将第三方应用请求的资源返回。
Github对应用开放授权
进入github中的Settings/Developer settings中创建一个应用,表示你的应用会使用github授权。
填写好相关的信息后,填写Authorization callback URL为http://localhost:8080/oauth/github/callback(后面授权会用到),可以得到Client ID 和 Client Secret,结果如下:

github授权第三方应用的过程
根据 GitHub 登录链接可以回调获得 code
根据Client ID 、Client Secret 和 code 可获得 token
根据 token 获得用户信息
必要的URL:
登录页面授权URL:
https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&state=%s
获得Token的URL:
获得用户信息的URL:
应用获得用户的信息时,会返回一个唯一的标识,用于唯一标识资源所有者即用户,于是我们可以将此标识与数据库中我们自己的本地用户相关联。

测试
在进行编码之前,我们首先访问上面的几种URL,并分析流程及返回结果。
首先访问https://github.com/login/oauth/authorize?client_id=50d7f61132da7f8574a1&redirect_uri=http://localhost:8080/oauth/github/callback&state=thisisrandomstring
分析:该URL为引导用户对应用授权github信息,参数client_id为该应用创建时的Client ID,redirect_uri为该应用创建时填写的Authorization callback URL,state为随机字符串,它用于防止跨站点请求伪造攻击。访问时结果如下:

响应结果能够理解,然后点击授权按钮,就会自动跳转到http://localhost:8080/oauth/github/callback?code=107b7d2f85201535880c&state=thisisrandomstring,URL为我们填写的回调URL,code参数即为凭证,
state为上一步的随机字符串。
接下来,我们应该获取token,根据github官方文档,我们需要发起一个POST请求,URL为https://github.com/login/oauth/access_token,
需要携带的参数如下:
| Name | Type | Description |
|---|---|---|
client_id |
string |
Required. The client ID you received from GitHub for your GitHub App. |
client_secret |
string |
Required. The client secret you received from GitHub for your GitHub App. |
code |
string |
Required. The code you received as a response to Step 1. |
redirect_uri |
string |
The URL in your application where users are sent after authorization. |
state |
string |
The unguessable random string you provided in Step 1. |
接下来,我们通过Postman模拟这一个过程,结果如下:

您还可以根据Accept标头接收不同格式的内容:
Accept: application/json
{"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo,gist", "token_type":"bearer"}
Accept: application/xml
<OAuth>
<token_type>bearer</token_type>
<scope>repo,gist</scope>
<access_token>e72e16c7e42f292c6912e7710c838347ae178b4a</access_token>
</OAuth>
嗯,成功获取到了Token无误,接下来该获取用户的信息了。发起GET请求,URL为https://api.github.com/user,携带参数access_token=获取到的token,结果如下,可以获取到用户的基本信息。

编码
首先需要一个service用来定义oauth的一些方法,如获取token,获取用户信息等。
package com.yunche.novels.service; import com.yunche.novels.vo.AuthUserVO;
import org.springframework.util.MultiValueMap; /**
* @author yunche
* @date 2019/04/04
*/
public interface AuthService { String getToken(MultiValueMap<String, String> params); AuthUserVO getUserInfo(String token); boolean checkIsExistsOpenId(String openId); boolean storeOpenIdByUser(String openId, Integer userId); String getUserNameByOpenId(String openId);
}
接着,使用GitHub来完成具体的service的实现。
package com.yunche.novels.service.impl; import com.yunche.novels.mapper.AuthForGitHubMapper;
import com.yunche.novels.service.AuthService;
import com.yunche.novels.util.AuthHelper;
import com.yunche.novels.vo.AuthTokenVO;
import com.yunche.novels.vo.AuthUserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.Map; /**
* @author yunche
* @date 2019/04/04
*/
@Service
public class GitHubAuthServiceImpl implements AuthService {
@Autowired
private AuthForGitHubMapper gitHubMapper; private static final String GET_TOKEN_URL = "https://github.com/login/oauth/access_token"; private static final String GET_USER_URL = "https://api.github.com/user"; private static final String CLIENT_ID = "50d7f61132da7f8574a1"; private static final String CLIENT_SECRET = "6779d154cfc44115e1f3607c0000085c5c1cf178"; private static final String REDIRECT_URI = "http://localhost:8080/oauth/github/callback"; @Override
public String getToken(MultiValueMap<String, String> params) {
params.add("client_id", CLIENT_ID);
params.add("client_secret", CLIENT_SECRET);
params.add("redirect_uri", REDIRECT_URI);
AuthTokenVO authTokenVO = AuthHelper.sendPostGetToken(GET_TOKEN_URL, params);
String token = authTokenVO.getAccess_token();
return token;
} @Override
public AuthUserVO getUserInfo(String token) {
Map<String, String> map = new HashMap<>();
map.put("access_token", token);
return AuthHelper.sendGetToUser(GET_USER_URL, map);
} @Override
public boolean checkIsExistsOpenId(String openId) {
return gitHubMapper.checkIsExists(openId) > 0;
} @Override
public boolean storeOpenIdByUser(String openId, Integer userId) {
Date date = new Date();
Timestamp timeStamp = new Timestamp(date.getTime());
return gitHubMapper.storeOpenIdByUser(openId, userId, timeStamp) > 0;
} @Override
public String getUserNameByOpenId(String openId) {
return gitHubMapper.getUserNameByOpenId(openId);
}
}
将需要获取的token和用户信息的json封装成对象。
package com.yunche.novels.vo; /**
* @author yunche
* @date 2019/04/04
*/
public class AuthTokenVO { private String access_token; private String token_type; private String scope; public String getAccess_token() {
return access_token;
} public void setAccess_token(String access_token) {
this.access_token = access_token;
} public String getToken_type() {
return token_type;
} public void setToken_type(String token_type) {
this.token_type = token_type;
} public String getScope() {
return scope;
} public void setScope(String scope) {
this.scope = scope;
} public AuthTokenVO() {
} public AuthTokenVO(String access_token, String token_type, String scope) {
this.access_token = access_token;
this.token_type = token_type;
this.scope = scope;
}
}
package com.yunche.novels.vo; /**
* @author yunche
* @date 2019/04/04
*/
public class AuthUserVO { /**
* 用户第三方应用名
*/
private String login; /**
* 用户第三方唯一标识
*/
private String id; /**
* 用户第三方头像
*/
private String avatar_url; public String getLogin() {
return login;
} public void setLogin(String login) {
this.login = login;
} public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getAvatar_url() {
return avatar_url;
} public void setAvatar_url(String avatar_url) {
this.avatar_url = avatar_url;
} }
mapper类操作数据库。
package com.yunche.novels.mapper; import org.apache.ibatis.annotations.*; import java.util.Date; /**
* @author yunche
* @date 2019/04/05
*/
@Mapper
public interface AuthForGitHubMapper { /**
* 检查该openId是否已经注册过
* @param openId
* @return
*/
@Select("SELECT COUNT(*) FROM oauth_detail WHERE open_id=#{openId} and app_type='github'")
Integer checkIsExists(String openId); /**
* 存储该OpenId
* @param openId
* @param userId
* @return
*/
@Insert("INSERT INTO oauth_detail(open_id, app_type, user_id, status, create_time) VALUES(#{openId},'github',#{userId},1,#{createTime})")
Integer storeOpenIdByUser(@Param(value = "openId") String openId, @Param(value = "userId") Integer userId, @Param(value = "createTime") Date createTime); @Select("SELECT user_name FROM user, oauth_detail WHERE user_id=user.id AND open_id = #{openId}")
String getUserNameByOpenId(String openId);
}
package com.yunche.novels.mapper; import com.yunche.novels.bean.User;
import org.apache.ibatis.annotations.*; /**
* @author yunche
* @date 2019/04/05
*/
@Mapper
public interface UserMapper { @Insert("INSERT INTO user(user_name, password) VALUES(#{userName}, #{password}) ")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
Integer storeUser(User user); @Select("SELECT COUNT(*) FROM user where user_name=#{name}")
Integer checkUserNameIsExists(String name);
}
Controller类。
package com.yunche.novels.controller; import com.yunche.novels.bean.User;
import com.yunche.novels.service.UserService;
import com.yunche.novels.service.impl.GitHubAuthServiceImpl;
import com.yunche.novels.util.MD5Utils;
import com.yunche.novels.util.StringHelper;
import com.yunche.novels.vo.AuthUserVO; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpSession; /**
* @author yunche
* @date 2019/04/04
*/
@Controller
public class AuthController { @Autowired
private GitHubAuthServiceImpl authService;
@Autowired
private UserService userService; @GetMapping("/oauth/github/callback")
public String authorizeForGitHub(@RequestParam("code") String code, @RequestParam("state") String state, HttpSession session) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("code", code);
map.add("state", state);
String token = authService.getToken(map);
//获取用户在第三方的信息
AuthUserVO userVO = authService.getUserInfo(token);
String openId = userVO.getId();
//注册该openId
if(!authService.checkIsExistsOpenId(openId)) {
User u = new User();
String userName = userVO.getLogin();
//确保用户的用户名唯一
while (userService.IsExistsName(userName)) {
userName += StringHelper.getRandomString(3);
}
u.setUserName(userName);
//生成一个随机的一定长度的字符串并使用MD5加密,由于第三方的密码不可用,故随机。
u.setPassword(MD5Utils.getMD5(StringHelper.getRandomString(16))); //注册用户
if(userService.insertUser(u)) {
//将本地用户与OpenId相关联
if(authService.storeOpenIdByUser(openId, u.getId())) {
//存储用户session
session.setAttribute("user", u.getUserName());
}
}
}
else {
session.setAttribute("user", authService.getUserNameByOpenId(openId));
}
// 重定向到之前需要授权的页面
return "redirect:" + state;
}
}
参考资料
基于oauth2.0实现应用的第三方登录的更多相关文章
- IdentityServer4之SSO(基于OAuth2.0、OIDC)单点登录、登出
IdentityServer4之SSO(基于OAuth2.0.OIDC)单点登录.登出 准备 五个Web站点: 1.localhost:5000 : 认证服务器.2 ...
- ASP.NET WebApi 基于OAuth2.0实现Token签名认证
一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将是我们需要思考的问题.为了保护我们的WebApi数 ...
- OAuth2.0和企业内部统一登录,token验证方式,OAuth2.0的 Authorization code grant 和 Implicit grant区别
统一登录是个很多应用系统都要考虑的问题,多个项目的话最好前期进行统一设计,否则后面改造兼容很麻烦: cas认证的方式:新公司都是老项目,用的是cas认证的方式,比较重而且依赖较多,winform的项目 ...
- 基于OAuth2.0的token无感知刷新
目前手头的vue项目关于权限一块有一个需求,其实架构师很早就要求我做了,但是由于这个紧急程度不是很高,最近临近项目上线,我才想起,于是赶紧补上这个功能.这个项目是基于OAuth2.0认证,需要在每个请 ...
- 基于OAuth2.0的第三方认证
浅显易懂的解释 来源 yahoo OAuth认证 原理 理解OAuth 2.0:原理.分类 一张图搞定OAuth2.0:是什么,怎么用 应用自身,完成用户认证: 缺点: 1.不同的访问Web应用提供不 ...
- QQ联合登录(基于Oauth2.0协议)
1. 获取授权码Authorization Code https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id= ...
- 基于Vue、Springboot网站实现第三方登录之QQ登录,以及邮件发送
基于Vue.Springboot实现第三方登录之QQ登录 前言 一.前提(准备) 二.QQ登录实现 1.前端 2.后端 1.application.yml 和工具类QQHttpClient 2.QQL ...
- OAuth2.0 原理流程及其单点登录和权限控制
2018年07月26日 07:21:58 kefeng-wang 阅读数:5468更多 所属专栏: Java微服务构架 版权声明:[自由转载-非商用-非衍生-保持署名]-转载请标明作者和出处. h ...
- 基于OAuth2.0的统一身份认证中心设计
1. 引言 公司经历多年发展后,在内部存在多套信息系统,每套信息系统的作用各不相同,每套系统也都拥有自己独立的账号密码权限体系,这时,每个人员都需要记住不同系统的账号密码,人员入职和离职时,人事部门都 ...
随机推荐
- Tomcat闪退的解决办法
1 首先考虑的是端口占用 2 现在讲的是这一种方法 (1)找到解压缩的文件的bin目录 找到startup.bat 记事本打开 开头加上两句话 SET JAVA_HOME=C:\Program Fil ...
- C#托盘图标
在C#中实现托盘是多么简单 http://www.cnblogs.com/anytao/archive/2006/04/26/385377.html http://www.cnblogs.com/du ...
- FZU 2020 组合 (Lucas定理)
题意:中文题. 析:直接运用Lucas定理即可.但是FZU好奇怪啊,我开个常数都CE,弄的工CE了十几次,在vj上还不显示. 代码如下: #pragma comment(linker, "/ ...
- 【性能测试】服务器资源监测工具sar安装
[root@yyy ~]# sar Cannot open /var/log/sa/sa19: No such file or directory 在Linux系统中,运行sar命令,发现无法执行: ...
- 转 叫板OpenStack:用Docker实现私有云
http://www.cnblogs.com/alexkn/p/4239457.html 看到各大厂商的云主机,会不会觉得高大上?目前大公司的主流方案是OpenStack,比如某个公司的私有云
- jmeter(十五)Jmeter默认报告优化
一.本文目的: 之前写了两篇文章搭建持续集成接口测试平台(Jenkins+Ant+Jmeter)和ANT批量执行Jmeter脚本,功能实现上都没有什么问题,但是最后生成的报告有一点小问题,虽然不影响使 ...
- C++中的数学函数汇总
math.h 数学函数库,一些数学计算的公式的具体实现是放在math.h里,具体有: 1 三角函数 double sin (double); double cos (double); double t ...
- 树莓派 VNC 远程桌面 同一个桌面
如何在ssh登录的情况下配置好vino 1.传输文件 2.ssh sudo dpkg -i ~/swap/deb/tight* sudo cp ~/swap/vino.desktop /etc/xdg ...
- 移动端UI自动化Appium测试——Windows系统Appium环境配置
1.安装JDK,官网下载即可,这里用的1.8,环境变量配置 2.安装Android sdk,API >= 17,环境变量配置 3.安装Nodejs,官网http://nodejs.org/dow ...
- Oracle对表空间无权限
有的时候我们在Oracle数据库中对执行insert.update之类的语句时会出错,Oracle说我们对表空间无权限.执行下面的语句就可以修改用户对表空间的权限了. 执行语句: alter user ...