OAuth2

OAuth2所涉及到的对象主要有以下四个:

  • Client 第三方应用,我们的应用就是一个Client
  • Resource Owner 资源所有者,即用户
  • Authorization Server 授权服务器,即提供第三方登录服务的服务器,如Github
  • Resource Server 拥有资源信息的服务器,通常和授权服务器属于同一应用

OAuth2的基本流程为:

  1. 第三方应用请求用户授权。
  2. 用户同意授权,并返回一个凭证(code)
  3. 第三方应用通过第二步的凭证(code)向授权服务器请求授权
  4. 授权服务器验证凭证(code)通过后,同意授权,并返回一个资源访问的凭证(Access Token)。
  5. 第三方应用通过第四步的凭证(Access Token)向资源服务器请求相关资源。
  6. 资源服务器验证凭证(Access Token)通过后,将第三方应用请求的资源返回。

Github对应用开放授权

进入github中的Settings/Developer settings中创建一个应用,表示你的应用会使用github授权。

填写好相关的信息后,填写Authorization callback URL为http://localhost:8080/oauth/github/callback(后面授权会用到),可以得到Client IDClient Secret,结果如下:

github授权第三方应用的过程

  1. 根据 GitHub 登录链接可以回调获得 code

  2. 根据Client ID 、Client Secret 和 code 可获得 token

  3. 根据 token 获得用户信息

必要的URL

  1. 登录页面授权URL:

    https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&state=%s

  2. 获得Token的URL:

    https://github.com/login/oauth/access_token?client_id=%s&client_secret=%s&code=%s&redirect_uri=%s&state=%s

  3. 获得用户信息的URL:

    https://api.github.com/user?access_token=%s

应用获得用户的信息时,会返回一个唯一的标识,用于唯一标识资源所有者即用户,于是我们可以将此标识与数据库中我们自己的本地用户相关联。

测试

在进行编码之前,我们首先访问上面的几种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,结果如下,可以获取到用户的基本信息。

编码

  1. 首先需要一个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);
    }
  2. 接着,使用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);
    }
    }
  3. 将需要获取的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;
    } }
  4. 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);
    }
  5. 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认证和授权机制讲解

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

基于oauth2.0实现应用的第三方登录的更多相关文章

  1. IdentityServer4之SSO(基于OAuth2.0、OIDC)单点登录、登出

    IdentityServer4之SSO(基于OAuth2.0.OIDC)单点登录.登出 准备  五个Web站点: 1.localhost:5000 :                  认证服务器.2 ...

  2. ASP.NET WebApi 基于OAuth2.0实现Token签名认证

    一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将是我们需要思考的问题.为了保护我们的WebApi数 ...

  3. OAuth2.0和企业内部统一登录,token验证方式,OAuth2.0的 Authorization code grant 和 Implicit grant区别

    统一登录是个很多应用系统都要考虑的问题,多个项目的话最好前期进行统一设计,否则后面改造兼容很麻烦: cas认证的方式:新公司都是老项目,用的是cas认证的方式,比较重而且依赖较多,winform的项目 ...

  4. 基于OAuth2.0的token无感知刷新

    目前手头的vue项目关于权限一块有一个需求,其实架构师很早就要求我做了,但是由于这个紧急程度不是很高,最近临近项目上线,我才想起,于是赶紧补上这个功能.这个项目是基于OAuth2.0认证,需要在每个请 ...

  5. 基于OAuth2.0的第三方认证

    浅显易懂的解释 来源 yahoo OAuth认证 原理 理解OAuth 2.0:原理.分类 一张图搞定OAuth2.0:是什么,怎么用 应用自身,完成用户认证: 缺点: 1.不同的访问Web应用提供不 ...

  6. QQ联合登录(基于Oauth2.0协议)

    1. 获取授权码Authorization Code https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id= ...

  7. 基于Vue、Springboot网站实现第三方登录之QQ登录,以及邮件发送

    基于Vue.Springboot实现第三方登录之QQ登录 前言 一.前提(准备) 二.QQ登录实现 1.前端 2.后端 1.application.yml 和工具类QQHttpClient 2.QQL ...

  8. OAuth2.0 原理流程及其单点登录和权限控制

    2018年07月26日 07:21:58 kefeng-wang 阅读数:5468更多 所属专栏: Java微服务构架   版权声明:[自由转载-非商用-非衍生-保持署名]-转载请标明作者和出处. h ...

  9. 基于OAuth2.0的统一身份认证中心设计

    1. 引言 公司经历多年发展后,在内部存在多套信息系统,每套信息系统的作用各不相同,每套系统也都拥有自己独立的账号密码权限体系,这时,每个人员都需要记住不同系统的账号密码,人员入职和离职时,人事部门都 ...

随机推荐

  1. java虚拟机内存区域理解

    java虚拟机有的区域随着虚拟机进程的启动而存在, 有的区域依赖用户线程的启动和结束而建立和销毁. 程序计数器:为了线程切换后能恢复到正确的执行位置,每个线程都有一个独立的程序计数器.(针对java方 ...

  2. AutoIT: 如何通过坐标相对位置来对无法识别的Menu以及GridView进行定位点击操作

    一般情况下,GridView中的数据来自数据库,我们通过Windows Info,是无法获取GridView中的信息的.而软件定制的Menu,很多时候无法通过系统提供的WinMenuSelectIte ...

  3. 第十七周 Leetcode 403. Frog Jump(HARD) 线性dp

    leetcode403 我们维护青蛙从某个石头上可以跳那些长度的距离即可 用平衡树维护. 总的复杂度O(n^2logn) class Solution { public: bool canCross( ...

  4. 使用expdp的心得

    第一步:首先使用DBA权限的用户创建directory,我使用system ,可以在服务器本地创建,也可以远程连接sqlplus进行创建,使用的将是服务器上面的路径.要确保创建directory时,操 ...

  5. STM32F4 DMA2D_R2M

    图像处理的专门DMA 看一段示例代码 /** * @brief Displays a line. * @param Xpos: specifies the X position. * @param Y ...

  6. SVN常用命令说明(转载)

    转自:http://www.blogjava.net/jasmine214--love/archive/2011/01/12/342839.html /** * 转载请注明作者longdick htt ...

  7. 前端性能优化之WebP

    此文已由作者吴维伟授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 前端性能优化是一件很琐碎的事情.它不像其它很多技术,在确切有限的步骤下就可以把功能做好.它就像是在打扫屋子, ...

  8. vultr 购买vps

    基本安装转自:https://github.com/uxh/shadowsocks_bash/wiki/Vultr%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B 连接 Vul ...

  9. 结对测试vs随机测试

    在接口测试过程中,最关键的是对参数的各种情况进行测试. 随机测试是指随机选择一些参数值来测. 结对测试是指parewise算法生成较高"性价比"的组合情况来测. 随机测试存在的问题 ...

  10. 进击的Python【第七章】:python各种类,反射,异常处理和socket基础

    Python的高级应用(三)面向对象编程进阶 本章学习要点: 面向对象高级语法部分 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 一.面向对象高级语法部分 静态方法 ...