基于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. 引言 公司经历多年发展后,在内部存在多套信息系统,每套信息系统的作用各不相同,每套系统也都拥有自己独立的账号密码权限体系,这时,每个人员都需要记住不同系统的账号密码,人员入职和离职时,人事部门都 ...
随机推荐
- MemCache在Windows下环境的搭建及启动
sc create "memcached-11212" start= auto binPath= "D:\memcached_en32or64\x64\memcached ...
- virtualbox安装完系统之后重新启动重新进入安装界面原因
安装完成之后如果重新启动出现卡死的情况,直接关闭掉virtualbox,然后重新启动. 在这里记得完成安装后再改下设置,把Storage里的IDE控制器设为没有盘片,否则下次启动时又是安装界面.
- python的partition() 方法
描述 partition() 方法用来根据指定的分隔符将字符串进行分割. 如果字符串包含指定的分隔符,则返回一个3元的元组,第一个为分隔符左边的子串,第二个为分隔符本身,第三个为分隔符右边的子串. p ...
- zoj 3865
Superbot Time Limit: 2 Seconds Memory Limit: 65536 KB Superbot is an interesting game which you ...
- 51nod 1238 最小公倍数之和 V3 【欧拉函数+杜教筛】
首先题目中给出的代码打错了,少了个等于号,应该是 G=0; for(i=1;i<=N;i++) for(j=1;j<=N;j++) { G = (G + lcm(i,j)) % 10000 ...
- 5 分钟掌握 JS 实用窍门技巧,帮你快速撸码--- 删除数组尾部元素、E6对象解构、async/await、 操作平铺嵌套多维数组等
1. 删除数组尾部元素 一个简单方法就是改变数组的length值: const arr = [11, 22, 33, 44, 55, 66]; arr.length = 3; console.log( ...
- [App Store Connect帮助]七、在 App Store 上发行(3.4)提交至“App 审核”:将构建版本从审核中移除
若要停止“App 审核”流程,您可以将该 App 版本从 App 审核中移除.要执行此项操作,App 状态必须为下列之一: 正在等待出口合规检查 正在等待审核 正在审核 等待开发者发布 等待 Appl ...
- OKEX websocket API 连接Python范例
因为 websocket-client 新版的各种大脑降级设计 很多功能无法使用需要安装老版本websocket-client的包才能正常使用 pip3 install websocket-clien ...
- spring boot+mybatis报错mapper无法注入
搭建spring boot项目时启动出现的问题,先来看异常片段: Error starting ApplicationContext. To display the conditions report ...
- 史上最详细最全的Linux上安装Oracle的教程-centos7
一.安装Oracle前准备 1.创建运行oracle数据库的系统用户和用户组 [humf@localhost ~]$ su root #切换到root Password: [root@localhos ...