OAuth2.0--通过github学习整个流程

在日常生活中,我们常常会发现越来越多的网站或者应用允许用户采用第三方登陆的方式进行用户验证,避免了用户因账号密码过多,嫌注册繁琐而流失。合理利用主流资源使得开发即高效,又便捷。现在第三方验证采用OAuth2.0机制,本文介绍该机制的流程,并且通过github的第三方登陆进行演示,并简单提及QQ、微信的第三方登陆过程。

概述

网上关于OAuth2.0的介绍很多了,我想从不同的角度去写。主要分为以下几个部分

  • 简单介绍OAuth2.0流程,并用命令行实现验证过程
  • springboot整合验证过程
  • 整合springboot+springsecure
  • 简单介绍QQ和微信接入

OAuth2.0流程

先看官方文档

再看优秀博主

如果都不想看,看这里:

这个机制服务于第三方用户接入,有三种角色:用户、应用开发者、平台。现在我们以应用开发者的角色获得用户在github平台的个人信息,完成用户接入。

平台注册授权应用

首先,我们在github注册一个授权应用,如下图所示,填入应用名称,主页URL,应用描述,以及授权回调URL。注意啦:演示过程中这俩个URL都可以随意填写,最好是打不开的链接,很方面看到演示效果。实际中回调URL当然要填写为用户授权成功后的页面地址啦,比如主页并且验证通过显示已登陆。

选择注册,会跳转到应用详情页面,这里有clientID和 client secret keys , 关于他们的用法后面会讲到,注意千万不要泄露哦!

关于回调URL:官方文档强调!!

关于clientID和 client secret keys:

模拟用户登陆

在浏览器打开链接,关于参数说明:

  • scope 表示请求用户的权限范围,这里定义了全部类型。
  • client_id 填写注册的应用clent ID,现在我们的ID是:313f926fb7ff25bf5655

拼接的URL如下,必须在浏览器中打开是因为github收到这个验证后,会跳转到用户确定登陆界面,用户授权后重定向到之前写的回调URL,并附带参数code。

https://github.com/login/oauth/authorize?scope=user:email&client_id=313f926fb7ff25bf5655

我们模拟用户点击了这个链接,然后会跳转到github登陆授权页面。

用户同意授权后,跳转到回调URL并附带code参数,服务器获得code,进行后续验证。

后台进行code验证,并获取用户信息。

这里需要特别强调,这个code只能使用一次,使用一次后便失效了,再次查询会返回错误的结果。

正确的结果:access_token=bd1386f1ef37d84062b67dde003d4def02e28f1b
&scope=user%3Aemail&token_type=bearer

错误的情况:error=bad_verification_code
&error_description=The+code+passed+is+incorrect+or+expired.
&error_uri=https%3A%2F%2Fdeveloper.github.com%2Fapps%2Fmanaging-oauth-apps
%2Ftroubleshooting-oauth-app-access-token-request-errors%2F
%23bad-verification-code

我们通过code获取token,然后根据token就可以获取用户信息了,token的使用次数目前测试没有发现限制。我们在命令行用curl模拟http请求。用code获取token,需要用post的方式。参数有三个:

  • client_id 313f926fb7ff25bf5655
  • client_secret b2cc859781895e16d33c847b3e7e64e7fab32324
  • code d10a11d79984d87cfe8a

带入链接: -d参数表示以post的方式请求,后面的字符串是参数列表。

curl -d “client_id=313f926fb7ff25bf5655
&client_secret=b2cc859781895e16d33c847b3e7e64e7fab32324
&code=d10a11d79984d87cfe8a
&accept=json” https://github.com/login/oauth/access_token

这样我们就得到了token

token:3744194421ff20c0b314b7615acbb402129447b2

接下来我们通过token获取用户信息,链接是 https://api.github.com/user/{scope}?access_token={access_token} scope选填,具体取值参照上文,我们这里不填写。

curl https://api.github.com/user
?access_token=3744194421ff20c0b314b7615acbb402129447b2

运行结果如下

总结

通过这个实践过程可以发现,OAuth2.0的验证是非常简单的。只有第三方平台遵守OAuth2.0规范,那么流程就是一样的。

  • 开发者到第三方平台注册应用,获取应用ID和secret。不同平台叫法可能不一样
  • 引导用户点击第三方登陆链接,获取code
  • 开发者后台获取code,到第三方平台进行验证,获取access_token
  • 根据token查询用户信息

springboot 简单示例

上述用curl工具简单的验证了整个过程,我将验证过程和springboot整合到一起做了个简单的demo。参见github

包结构如下:

注意:我将回调地址修改为: http://localhost:8080/getGithubCode 这样便于对返回的code进行验证处理。运行项目后默认进入登陆页面:http://localhost:8080/ 如下图所示,点击github图标即可进入验证页面。

用户会看到github提供的授权页面

授权后会重定向到回调URL,在Controller中的getCode方法中捕获参数,发起http请求,分别查询token和用户信息,随后将用户信息返回到hello.html页面,如下图:

代码都写了注释,将部分逻辑做一些说明:首先是封装类GithubAppInfo.java和Scopes.java

Controller类的处理,首先是拼接授权链接,然后跳转到登陆页面。

处理回调URL,获取code,根据code查询token以及用户信息。

最终对用户信息进行处理。并将链接重定向到登陆页面(已登陆状态)。

springboot+springsecure 简单整合

为了接入securtiy,可谓是历经万难,对于spring的开发设计思想充满了敬畏。大概做一点有挑战的事情应该就是乐趣吧。好了,这里将简单利用security管理我们的oauth2.0验证过程,我为此准备了一个示例,可以拿来即用,参见github。但确实是浅尝辄止,大佬们请绕行。

我们的验证流程是不会变化的,但如何接入security是个问题,先简单了解一下security和springboot的整合。要明白一点,配置好spring boot的包以后,整个项目默认就全部接受了安全验证。

牛刀初试

我们先将github上回调地址修改为

http://localhost:8080/login/oauth2/code/

之后我们会讲到为什么,以及如何修改为自己的地址。现在我们一步步尝试吧~
在上节springboot 的基础上添加五个maven依赖

daemon.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>

此时新建一个control 类随意映射一个地址,运行就能看到security已经完全接管了我们的程序。

可以明显看出我们的程序已经不受控制了,没关系,我们继续用security实现OAuth2.0,后面就会慢慢讲清楚。我们在application.yml文件(注意上节中使用的application.properties,两种配置方式是等价的)中加入github的配置信息,security帮我们实现了整个过程,我们只需要加入client_ID和client_secret即可。(复制进去IDE会自动调整格式)

spring.security.oauth2.client.registration.github.client-id: 313f926fb7ff25bf5655
spring.security.oauth2.client.registration.github.client-secret: b2cc859781895e16d33c847b3e7e64e7fab32324

配置好这个后,我们就会发现页面神奇地变成了OAuth2.0的验证方式,不过默认的登陆页面不是我们自己写的,完成验证后就会回到我们自己的页面了。

看到这里,可能会觉得很奇怪吧,明明我们几乎什么也没做,就干完了所有事情。不过后面才是麻烦的,毕竟我们不可能要求用户进入网站就授权验证吧,也不可能就做个这个验证页面给用户看吧。所以我们需要解决几个问题:

  • 部分页面不需要验证就可以访问
  • 默认跳转的登陆页面要用我们自己的
  • 配置自己的回调URL
  • 我要使用其他的第三方平台验证怎么办?
  • 其他诸如token保存到数据库,深入自定义验证过程等就不多讲了。

浅尝辄止

开放部分页面

这里新建一个home.html,index.html,绑定映射路径为/ 和 /home ,新建userinfo.html 绑定映射路径为/userinfo。
三个页面都可以互相跳转,但home和index不需要权限验证就能访问,访问需要权限的页面会自动跳转到登陆页面。

首先新建一个java config类 OAuth2LoginSecurityConfig.java 继承WebSecurityConfigurerAdapter,重写 configure(HttpSecurity http)方法(注意参数要是http这个),按照下图配置完我们重新运行就能看到我们想要的结果,除了/和/home其他包括静态资源在内的一切URL都将被拦截到登陆页面。

这个方法能做很多事情,更多的配置参考:

指定自己的登陆页面

上小节其实图没有截完整,点击Display_User_Info是没法跳转到登陆页面的,因为我们继承了上述方法,默认的配置就被覆盖了,login也不再会有默认的登陆页面了。因此,我们只需要自己配置映射路径即可,为了区分,我们将登陆页面映射到userLogin。

未映射login对应的页面

放上之前的登陆页面,更改映射路径,注意静态资源也会被拦截,我们需要对/css 路径放行。修改配置如下

注意我们github的连接要改成http://localhost:8080/oauth2/authorization/github 原因后面讲。

重启应用,输入除了/ 和 /home 以外的任意路径都将跳转到登陆页面

注意,此时点击github图标,会重定向到

https://github.com/login/oauth/authorize?scope=user:email&client_id=313f926fb7ff25bf5655

这是因为路径http://localhost:8080/oauth2/authorization/github被自动接管了,实际部署时http://localhost:8080从request中获取。

现在进行授权,就能跳转到用户信息页面,关于userinfo的获取,可以参见具体实现(官方案例)。

实际过程中发现,并不能跳转,将链接换成github的授权链接后,security似乎认为我们没有完成授权,也就是说我们只得到了code,没有获取到token,security没有成功接管,经过思考后,添加了login页面的配置,解决了这个问题。

现在点击输入http://localhost:8080/userinfo 在登陆页面点击github登陆

授权后自动跳转到user_info页面,security自动帮我们完成token获取和用户信息获取的发包操作,后续会贴出源码。默认将token存在内存中,和session对应起来。也可以存入数据库Redis,这个项目应用了简单的实例,具体配置非常简单。

注意哦,回调地址必须设置为http://localhost:8080/login/oauth2/code/ 才能自动处理哦。

配置自己的回调URL

其实这个没必要配置,因为中间过程都是自动处理的。关于这个可以参考官方教程,如下图:

但我实际操作中发现并没有用,看错误信息,发现请求链接的回调地址参数没有发生变化,这个只有深入源码查一下,先放一放~ 也可能是我自己配置错误了。

关于更多的第三方平台接入

官方只提供了四个厂商的默认配置,分别是Google、Facebook、OKta、Github。其他配置方式我在示例中做了注释,同时引用官方的说明。共有两种方式配置

总结

为了完成这个demo,我花了四天时间,从头开始学习springsecurity,期间读了很多源码,看了很多资料。但依旧还是很迷糊,因此只能浅尝辄止。想来还是因为没有整体把握思想,对于相关的机制没有完整的逻辑认知,后续打算从整体入手,学习整个过程和不错的思想。对与文中出现的错误,可以和我邮件交流,非常感谢。我不愿误人子弟,尽力多查资料,介绍的相对不那么深入,这更多的算我的个人学习笔记。

源码

这里打算单独写一篇博客,后面补充~~

记录问题

在实际使用中发现几个问题,记录在这里,后面看能不能解决

  • 上述关于回调地址的处理,需要查源码分析解决
  • 这一点应该算security的缺陷。当用户取消授权后,再次登陆页面,会直接报权限错误,而不是重新提示授权,这一点我考虑是token存到数据库或者内存中,没有相应的机制对token进行验证,应该是我还没有掌握~
  • 关于使用jdbc进行用户权限验证中发现Datasource不能自动注入,提示有多个bean实例,解决方案是@Autowired 下面加上 @Qualifier(“dataSource”) 参数是自动注入的实例名称。 具体原因没有得到有效的答案。

QQ微信支付宝接入

这部分其实没有多大意义了,明白流程和实现手段,只是到不同到平台申请应用,并遵守对应的规范,做出相应的调整即可。实际开发的时候再做处理吧~ ps: 其实只是因为我是小菜鸟,还没有接触到这个应用场景~。~

欢迎和我邮件交流~

Fork me on GitHub