基于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. 引言 公司经历多年发展后,在内部存在多套信息系统,每套信息系统的作用各不相同,每套系统也都拥有自己独立的账号密码权限体系,这时,每个人员都需要记住不同系统的账号密码,人员入职和离职时,人事部门都 ...
随机推荐
- java虚拟机内存区域理解
java虚拟机有的区域随着虚拟机进程的启动而存在, 有的区域依赖用户线程的启动和结束而建立和销毁. 程序计数器:为了线程切换后能恢复到正确的执行位置,每个线程都有一个独立的程序计数器.(针对java方 ...
- AutoIT: 如何通过坐标相对位置来对无法识别的Menu以及GridView进行定位点击操作
一般情况下,GridView中的数据来自数据库,我们通过Windows Info,是无法获取GridView中的信息的.而软件定制的Menu,很多时候无法通过系统提供的WinMenuSelectIte ...
- 第十七周 Leetcode 403. Frog Jump(HARD) 线性dp
leetcode403 我们维护青蛙从某个石头上可以跳那些长度的距离即可 用平衡树维护. 总的复杂度O(n^2logn) class Solution { public: bool canCross( ...
- 使用expdp的心得
第一步:首先使用DBA权限的用户创建directory,我使用system ,可以在服务器本地创建,也可以远程连接sqlplus进行创建,使用的将是服务器上面的路径.要确保创建directory时,操 ...
- STM32F4 DMA2D_R2M
图像处理的专门DMA 看一段示例代码 /** * @brief Displays a line. * @param Xpos: specifies the X position. * @param Y ...
- SVN常用命令说明(转载)
转自:http://www.blogjava.net/jasmine214--love/archive/2011/01/12/342839.html /** * 转载请注明作者longdick htt ...
- 前端性能优化之WebP
此文已由作者吴维伟授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 前端性能优化是一件很琐碎的事情.它不像其它很多技术,在确切有限的步骤下就可以把功能做好.它就像是在打扫屋子, ...
- vultr 购买vps
基本安装转自:https://github.com/uxh/shadowsocks_bash/wiki/Vultr%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B 连接 Vul ...
- 结对测试vs随机测试
在接口测试过程中,最关键的是对参数的各种情况进行测试. 随机测试是指随机选择一些参数值来测. 结对测试是指parewise算法生成较高"性价比"的组合情况来测. 随机测试存在的问题 ...
- 进击的Python【第七章】:python各种类,反射,异常处理和socket基础
Python的高级应用(三)面向对象编程进阶 本章学习要点: 面向对象高级语法部分 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 一.面向对象高级语法部分 静态方法 ...