Spring Security OAuth2 简单使用配置及部分源码讲解
原创内容,转载请注明出处!
关于为什么要写这篇Blog:
因为工作需要,需要为公司开发一个产品后台系统,该系统需要管理我们公司所开发的各个应用的一些功能, 如数据分析,客户开通之类的,说白了就是这个后台系统是给我们公司内部人员用的, 然后这个后台系统主要就是调用其他应用的一些接口而已.
由于每个应用的接口都涉及到鉴权这方面的内容,所以后台系统并没有办法直接去调用它们, 后来想到我们可以单独开发一个OAuth2认证服务,为客户端(后台系统)授权token, 客户端携带token调用这些接口,调用接口前包一层验证token有效性的逻辑,验证通过后即可访问,这样就可以在不影响原因产品接口上的逻辑而实现第三方应用的接口调用鉴权.
公司中所有产品服务端都是使用Spring Security 做为安全框架的, 后来发现有Spring Security OAuth2 这个框架, 那当然是使用亲儿子最好了.
关于Spring Security OAuth2 (以下简称SSO):
网上关于它的介绍有很多, 我在这里就简单的说一下, SSO(Spring Security OAuth2) 是基于Spring Security 之上的一个为我们提供OAuth2实现的一个框架, 具体实现方式和Spring Security 差不多, 也是包含 Filter ,AuthenticationManager ,AuthenticationToken , UserDetails, UserDetailsService 这些核心的抽象的实现.
所以有一定的Spring Security 的基础学习SSO会方便很多.
关于OAuth2我就不做过多介绍了,网上有很多资料..
下面说一下SSO的一些核心概念,在理解完这些概念后再看代码才会知道当前正在做些什么, 我尽量用比较通俗易懂的语言来介绍这些概念,为了方便大家理解.
首先说一下OAuth2的提供端:
OAuth2需要提供什么? 当然是提供资源的保护,以及生成Token,验证Token这两块内容, 下面分来介绍一下.
AuthorizationServer (授权服务):
授权服务的作用就是用来管理access_token的包括生成,验证,校验等一些服务, 还包括配置客户端的一些信息, 以及授权,验证端点的一些配置内容, 具体配置下面会讲到.
ResourceServer(资源服务):
资源服务主要是来管理我们需要被保护的资源的一些配置, 还有一些token验证方式等..
上述两个核心服务,可以是同一个应用内的,也可以是分别不同的应用. 意思就是, 我的授权服务可能是单独一个应用, 它可以用来管理多个资源服务应用的令牌的生成, 资源服务一般会集成在我们本身的应用中对外提供资源.
说的比较简短,可能看完还是会云里雾里的, 没关系,接下来我会一步一步分析这两块内容, 首先理解一个大致的概念即可.
代码样例:
首先说一下测试应用的概观, 待会儿分别会配置两个应用, 一个是授权服务应用跑在8080端口, 一个是资源服务应用跑在8081端口.
我会用代码实现以下操作, 资源服务应用提供了两个资源, 一个未受保护, 一个受OAuth2保护, 我会首先在8080端口申请access_token 然后携带access_token访问8081端口的受保护资源.
首先来看一下项目的目录结构:
我们先从oauth2-authorization授权服务来开始说起:
在这里我会告诉大家token的生成, 客户端的配置, 以及token的存储方式等信息.
首先看一下pom.xml:
oauth2的包需要单独引入.
接下来看一下AuthorizationServerConfiguration代码:
@EnableAuthorizationServer注解用于开启授权服务配置, 让我们看看这个注解能为我们做了什么
该注解主要帮我们在应用中引入的两个配置, 第一个是授权服务的端点配置, 第二个是授权服务的访问安全配置
这两个配置主要是在ApplicationContext中注册的一些列关于授权服务的Bean.
接下来看一些第一个configure方法:
AuthorizationServerSecurityConfigurer
这个类用于配置授权服务的访问安全等配置, 在这里我并没有做过多的配置, 只是配置了允许从表单认证客户端,以及校验access_token端点的访问安全配置成必须认证后才能访问.
第二个configure方法
ClientDetailsServiceConfigurer
这个类用于配置客户端的一些信息, 这里是一个重点,为了简单起见当前我是在内存中配置的一个客户端的信息, 待会儿需用使用这个客户端的信息来申请access_token, 这里再多讲一嘴, 我们可以把客户端信息配置在不同的存储环境下, SSO为我们提供了内存中存储方式以及JDBC的存储方式, 在生产环境中我们必定要将客户端信息保存在数据库中的, 来看一下这个类源代码分析一下:
具体主要注意这三个方法, 后面两个从语义可以看出来分别是SSO为我们提供的默认存储方式, 在内存以及JDBC的方式,
可以看到它们是通过ClientDetailsServiceBuilder进行配置的, 这里使用到创建者模式, 如果你希望简单的将客户端信息配置在数据库中那么使用JDBC的方式来获取客户端信息也是可以的, 但是这样就有一个缺点, 数据表必须按照Spring 指定的方式进行建立, 不是很方便我们进行定制化及扩展, 在生产环境中我建议推荐使用第一个函数 withClientDetails
ClientDetailsService是一个接口, 用于获取客户端的信息, 并封装到 ClientDetails中, 熟悉Spring Security 可以察觉到这里对应了UserDetailsService 和 UserDetails
好了扯的有点远了不好意思,还是让我们看看当前的详细配置吧,
可以看到我在内存中所配置的客户端信息为, client_id = client_1 , client_secret = 123456
redirectUris的配置是用于当你使用 authorization_code 这种授权模式时使用的, 当然这都是一些OAuth2的概念了这里就不讲太多, 知道OAuth2的几种授权类型的同学一定知道以上配置是用来干嘛的.
authorizedGrantTypes("client_credentials", "password", "authorization_code", "refresh_token")
这一行代码主要是客户端使用的授权类型.
接着看第三行configure方法
AuthorizationServerEndpointsConfigurer
这个类用于授权服务端点的配置, 可以定制一些token的生成服务,以及token的存储服务等内容..
这里重点讲解一下以下几项配置:
AuthorizationServerTokenServices 是一个接口,提供了以下方法:
方法的作用都可以通过方法名语义化判断出来,这里就不做过多解释.
SSO为我们提供了一个默认的TokenService 服务用于生成token以及获取token
主要关注这两点
TokenStore也是一个接口, tokenService会使用它来将生成的token进行存储,或从它这儿获取token
TokenStore提供的以上方法来管理token , SSO为我们提供了很多默认实现, 在生产环境中我们一般是不需要实现它的, 只需要使用默认实现即可,让我们看看具体有哪些实现
语义化判断出每个实现的方式, 我们这边使用RedisTokenStore来作为本次应用的token存储方式.
ClientDetailsService 上面有提到过它的作用.
接着在看我的第二个配置authorizationCodeServices方法
当你grant_type使用authorization_code授权类型时, 那么你最好指定一个code的存储方式, 默认不配置的话是存储在内存中的,
authorizationCodeServices方法的入参是上面这个接口, 这个接口用于生成code以及消费code.
由于SSO对该接口提供的默认实现中并没有提供redis的实现,所以我们必须自己实现一些内容, 但是SSO提供了一个抽象的实现类RandomValueAuthorizationCodeServices 可以知道该抽象是为我们提供了code的生成, 但是关于code的存储及消费还是得我们自己实现, 那我们就继承这个类
以上关于授权服务的相关配置就搞定了, 但是还没完, SSO 为我们提供了一些授权端点用于获取及验证token, 默认端点路径是:
/oauth/authorize 用于获取授权code
/oauth/token 用户获取token
/oauth/check_token 用于校验token
等一些端点, 关于端点的具体代码位置这里我提供给大家方便大家debug
这个包下的类是SSO提供的一些默认端点.
好了, 那我们还有什么没有配置完呢, 大家别忘了我们项目中引入了Spring Boot Starter Security 如果我们不进行配置的, 默认是对所有路径进行拦截的, 我们需要将一些/oauth/**的一些路径放行, 还有一点就是当你使用Password, Authorization_code 授权模式时需要有用户信息进行支撑, 让我们看一下具体的配置信息
这边就是Spring Security 一些基础配置了, 我在内存中配置了一个用户, 由于我并没有写/login的controller而且也没有登录页面, 所以我自己写了一个Filter来收集登录信息进行认证的操作.
到这里关于授权服务的配置就算完成了.
接下来我们具体说说资源服务的配置:
首先看下pom.xml
和授权服务一样,然后我在资源服务中写了两个资源:
其中get方法是不受保护的,任何人都可以访问. post受到OAuth2的保护.
接下来看看具体的资源服务配置:
@EnableResourceServer 注解用于启用资源服务, 这个注解引入ResourceServerConfiguration这个配置类,
查看源代码我们重点关注上面标注的信息, 是不是见到两个很熟悉的名词, 以及ResourceServerConfigurer 资源服务配置器,
由于我们的资源服务配置继承自ResourceServerConfigurerAdapter这个适配器, 而这个适配器又继承自ResourceServerConfigurer 所以到时我们自己的配置会被这个配置类收集然后进行迭代调用.
重点关注我们自己的配置信息,
第一个configure方法:
ResourceServerSecurityConfigurer
用于类用于配置tokenStore, tokenService,resourceId等一些配置
在代码中我只配置了一个resourceId以及TokenStore, 配置这个TokenStore主要是为了让资源服务从Redis中自己进行校验不调用授权服务的/oauth/check_token 这个端点. 可以看到不管在授权服务和资源服务中我都没有对tokenService进行配置, 因为目前并没有什么需要单独定制配置信息, 所以使用SSO默认提供的DefaultTokenService可以满足使用了.
第二个configure方法:
主要用于对需要被保护的资源进行配置的, 可以看到我对/resource/** POST方式开启了认证保护, 并使用access方法限制该URL的scope必须要有select作用域才能访问, 对了关于作用域名称这里不要被我代码混淆了, 这个作用域名称你们爱叫什么都行, 只要你获取的token的scope包含这个名称即可.
还有一点配置了一个Bean
OAuth2WebSecurityExpressionHandler
这个Bean用于处理SSO的EL表达式.
资源服务的配置就这么点, 下面我们使用authorization_code 和 client_credentials 这两种授权模式进行演示:
首先启动两个应用, 注意 8080是授权服务器, 8081是资源服务器
我们先来测试不携带access_token 访问未受保护的资源以及受保护的资源, 结果如下:
可以看到get请求可以正常访问.
当使用POST请求访问时返回以上错误信息.
由此证明我们的资源服务已经对我们的资源进行了保护. 那下面我们开始进行授权的操作
authorization_code 授权模式:
第一步获取授权code
访问以下端点: /oauth/authorize
后面的参数中我们主要使用到了在授权服务中在内存配置好的客户端信息以及重定向网址, 注意这些参数必须和配置好的完全匹配.
而response_type=code 是 authorization_code 授权的固定参数不用管, scope表示你申请的scope范围.
访问后重定向之前设置好的redirect页面并拼接了code参数, 这个code就是我们用于申请access_token的必要参数.
接下来调用/oauth/token端点来申请access_token
携带以上参数来申请token, 然后授权服务器给我们返回了以上内容,
其中access_token就是我们用来访问受保护资源的一个令牌, refresh_token 是当access_token过期后我们需要使用它来申请新的token, 它的存活时间比access_token要长.
接下来我们携带access_token访问受保护的资源
已经可以正常访问到受保护的资源.
下面使用client_credentials授权模式进行演示
直接携带client_id 和 client_secret 来获取access_token即可
成功得到响应信息.
以上便是本次介绍的主要内容, 当然关于SSO的配置还有很多, 不可能在这里一次性说完. 如果上面有我理解不到位或者理解有误的欢迎指出,我及时修正. 下一篇blog计划会走一遍授权到资源认证的源码流程, 让大家理解更透彻一点.