“OAuth 2.0 provides specific authorization flows for web applications, desktop applications, mobile phones, and smart devices.”
每一个项目的设计都离不开认证授权,即使是公开访问的资源,也属于一种特殊的匿名认证。作为一个合格的Java研发,你需要知道认证的几种方式,怎么设计一个支持各种方式登录的服务器,如何控制第三方应用程序访问用户资源,本文带你一步步揭开认证授权的面纱!
什么是OAuth2?
OAuth2.0授权框架使第三方应用程序能够代表资源所有者通过协调资源所有者和HTTP服务之间的批准交互,或允许第三方应用程序代表自己获得对HTTP服务的有限访问。
说人话:OAuth2是一种协议,用来定义如果有人想接入你写的服务,如何获取用户的授权并访问用户在你服务器上的资源,OAuth2就规定了整个流程该如何交互。
再通俗点,你点了个外卖,告诉外卖小哥你的地址。你家小区,就是你的服务器,你家小区大门口有人脸认证开门,如果有外卖进入,肯定进不来,那外卖小哥就给你打电话,你给保安大哥说给他个临时密码让他进来吧,然后外卖小哥把外卖送到你的地址。这就是OAuth2。
OAuth2.0中的角色
resource Owner 资源拥有者,简单理解为用户 (你自己)
resource server 资源服务,可以简单理解为接口提供者 (外卖地址)
client 客户端,想要接入的应用,包含第三方应用 (外卖小哥)
authorization server 认证服务器,负责颁发校验token(门口保安)
OAuth2.0的认证方式
Authorization Code
Implicit
Resource Owner Password Credential
Client Credentials
本篇文章主讲第一种也是最常用的一种Authorization Code ,
先看下该模式的整个流程。
流程图
A: 客户端发起认证请求,开启整个认证流程,通常是用浏览器,请求resource owner(资源拥有者,也就是用户)来同意自己获取该用户在resource server上的资源(通常是接口)
B:resource owner 资源拥有者进行认证,选择授权还是拒绝
C:如果资源拥有者同意授权,authorization server返回code
D: 客户端携带上一步返回的code请求token
E: 认证服务返回access_token 和 refresh_token
整个流程就是这样,具体的细节参数下面讲,可能有点绕,先有一个感性的认识,
基于上面流程分析一下gitlab使用github的第三方登录。
Gitlab登录页面,可以看到有Sign in with GitHub。那么GitLab在OAuth2.0的角色
是什么呢?
答案是client
GitLab登录页面
点击GitHub,会跳转的GitHub的登录页面,那么你身为一个resource owner,
决定要不要让Gitlab获取你在GitHub上的部分资源。
如果你输入账号密码,选择登录,代表你同意授权,这时GitHub的认证服务器会给GitLab发放一个code
GitLab再使用这个code,去申请access_token,然后得到了你的邮箱等存在GitHub上的信息,如下图划掉的地方,就是你Github的名字。
这样就完成了Gitlab使用了你的Github信息进行了登录。在这个例子中,我们列举一下各个角色。
resource Owner 是你自己
authorization server 毫无疑问是GitHub
resource server 是存放你的用户信息的,毫无疑问也是GitHub
client: 想要使用你的信息的客户端,这个例子是GitLab
到这里是不是有点感觉了?
接下来带你实现一个认证服务器和资源服务器。
编程实现Authorization Code方式登录
老样子我们还是用Spring的解决方案。
但是去Spring的官网上查看相关资料,你查不到关于authorization server相关的详细文档,只有简单几句。
可以看到Spring OAuth project已经被废弃了,相关的OAuth的登录和资源服务器功能,已经迁移到了Spring Security。如果你使用之前的Spring OAuth project,即使你标记了@EnableAuthorizationServer,也会发现该注解已经被废弃了。怎么办?
经过我的发现,spring 顶不住网友的压力,又开始做认证服务器了。
有兴趣可以看看这个博客,大致意思是说,原来spring不想做这个认证服务器,因为市面上有相关的开源框架,但是发现各位热心网友吐槽说我所有的服务都是用你spring全家桶写的,你现在不给我支持认证服务器,不厚道!Spring顶不住压力,开始搞了,目前是在实验阶段,已经发布了0.1.1版本,尝鲜的同学可以去github上去看。
[email protected]:spring-projects-experimental/spring-authorization-server.git
Spring-authorization-server 构建
git上面已经放出,我们这就上手
如果你是windows,你鼠标右键单击,有一个Git bash here,执行clone命令
gitclone [email protected]:spring-projects-experimental/spring-authorization-server.git
可以用IDEA打开项目:
直接运行spring-authorizaion-server里面的samples。
一共有三个项目:
authorizationserver
client
resourceserver
这里我们只关注authorizationserver和resourceserver即可。
认证服务器
先看一下authorizationserver项目的目录结构
和普通的Spring boot项目没有差别
application.yml : 声明该authorization server(认证服务器)将在9000端口上启动。
server:port: 9000logging:level:root:INFOorg.springframework.web:INFOorg.springframework.security:INFOorg.springframework.security.oauth2:INFO#org.springframework.boot.autoconfigure:DEBUG
DefaultSecurityConfig代码如下:
/***@authorJoe Grandja*@since0.1.0 *///开始spring security 的web支持@EnableWebSecuritypublicclassDefaultSecurityConfig{// @formatter:off//定义 SecurityFilterChain@BeanSecurityFilterChaindefaultSecurityFilterChain(HttpSecurity http)throwsException{ http .authorizeRequests(authorizeRequests ->//任何访问地址都需要认证 authorizeRequests.anyRequest().authenticated() ) .formLogin(withDefaults());returnhttp.build(); }// @formatter:on// 配置用户@BeanUserDetailsServiceusers(){ UserDetails user = User.withDefaultPasswordEncoder().username("user1").password("password").roles("USER") .build();returnnewInMemoryUserDetailsManager(user); }// @formatter:on}
上述代码实现两个功能:
该项目下所有资源都必须经过认证
配置了一个用户名为user1密码为password的角色是USER的账号
再来看看AuthorizationServerConfig里面的主要配置
@BeanpublicRegisteredClientRepositoryregisteredClientRepository(){ RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("messaging-client").clientSecret("{noop}secret") .clientAuthenticationMethod(ClientAuthenticationMethod.BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc").redirectUri("http://127.0.0.1:8080/authorized")//为方便调试,可以添加上允许跳转到baidu.redirectUti("http://www.baidu.com") .scope(OidcScopes.OPENID).scope("message.read").scope("message.write").clientSettings(clientSettings -> clientSettings.requireUserConsent(true)) .build();returnnewInMemoryRegisteredClientRepository(registeredClient); }
声明了一个messaging-client的客户端,还记得刚才的GitHub和GitLab案例吗?你可以想象我们现在正在写GitHub的代码(认证服务器模块),然后我们在认证服务器模块里面配置了GitLab的客户端。为了方便理解,你可以把messaging-client写成GitLab,然后这个模块名字叫做Github-authorization-server。当然实际中不是写死的,一定是去数据查的,但是对咱们的这个OAuth2.0的流程无所谓。
至此,认证服务器的主要配置完成。
资源服务器
项目的目录结构如下
资源服务器更加简单了,一个配置类,一个controller。
注意资源服务器中的application.yml配置
spring:security:oauth2:resourceserver:jwt:issuer-uri:http://auth-server:9000
配置上认证服务器的地址
至此完成。我们来测试一下,OAuth2 作为一个协议,必定有其规范。rfc6749就是其协议规范,spring-authorization-server也是基于该规范来实现的。
那么如何测试上面的配置确实是可以用的呢?现在spring也没有相关文档,这时我们可以借助其rfc规范文档来进行辅助测试。
测试
打开postman,调用资源服务器的接口,可以看到直接调用会返回401未授权,证明该资源接口确实被保护起来了。
那么如何访问该资源呢?
我们来借助rfc6749文档,找到第四章部分
可以看到很详细的说明请求的步骤以及参数怎么设置。
主要参数有
response_type 固定写"code" ,对应Authorization Code认证方式
client_id 客户端id,在认证服务器我们配置的是messaging-client
redirecr_uri,回调的地址,会携带code到该地址
scope,客户端想要申请用户授予的权限
注意在资源服务器的客户端配置里,我多加了一个百度的地址,为了方便测试。
根据实例,在浏览器上输入
http://127.0.0.1:9000/oauth2/authorize?response_type=code&client_id=messaging-client&redirect_uri=http://www.baidu.com&scope=messages.read
这时跳转到登录页面
输入我们上面在认证服务器内配置的账户密码 user1 passwod, 点击后出现如下用户授权的页面,也就是我们一开始的你(user1)是否允许messaging-client使用它的message.read权限。
点击确认后,会发现跳转到了百度。然而在地址栏会有一个code,这个code很重要,是为了后续申请token使用,复制粘贴下来。
继续看rfc6749文档,查看如何获取token
根据上述参数,postman调用如下,可以看到获取到了access_token。
注意Authorization这个header,需要加上Basic:xxxx
其中xxxx的值是messaging-client:secret的base64编码字符串,也就是说是client的id:screct的拼接起来的字符串的base64编码过的字符串。
获取到了token之后,我们再去访问资源服务器中被保护的资源,注意需要在Headers里加上Authorization,值为Bearer xxxx
其中xxxx是上一步获取到的access_token.
至此,授权码模式已经完成。
目前spring-authorization-server只支持到了如下功能
其中像password 还没有实现,还是在开发阶段,如果是对OAuth2不能等的小伙伴,例如spring所说,有其他开源的项目,例如keycloak,有兴趣的小伙伴可以结合rfc文档对keycloak进行测试,官方网站是 https://www.keycloak.org/
总结
本文对rfc规范以及spring-authorization-server对其实现入手,实现了一个授权码模式(Authorization Code)的第三方客户端登录,理清什么是授权服务器,什么是资源服务器,什么是资源拥有者,什么是客户端是非常重要的。
目前spring-authorization-server还是在开发阶段,已经支持了部分的OAuth2协议,有时间的小伙伴可以去贡献代码,将来大家用的都是你写的代码,大家唱的都是你写的歌!