OAuth
是一个关于授权的开放网络标准,在全世界得到的广泛的应用,目前是2.0的版本。OAuth2在“客户端”与“服务提供商”之间,设置了一个授权层(authorization layer)。“客户端”不能直接登录“服务提供商”,只能登录授权层,以此将用户与客户端分离。“客户端”登录需要OAuth
提供的令牌,否则将提示认证失败而导致客户端无法访问服务。下面我们就来讲解下SpringBoot
项目中是如何配置使用OAuth2服务器
端,并让OAuth2
整合SpringSecurity
来保护我们的REST接口。
本章目标
基于SpringBoot项目提供一个继承OAuth2安全框架的REST API服务端,必须获取访问授权令牌后才可以访问资源。
OAuth2授权方式
我们在文章开始已经说过了,我们的保护资源必须通过授权得到的令牌才可以访问。那么我们这个授权令牌要通过什么方式获取呢?
OAuth2为我们提供了四种授权方式:
1、授权码模式(authorization code)
2、简化模式(implicit)
3、密码模式(resource owner password credentials)
4、客户端模式(client credentials)
授权码模式
授权码相对其他三种来说是功能比较完整、流程最安全严谨的授权方式,通过客户端的后台服务器与服务提供商的认证服务器交互来完成。流程如下图2所示:
简化模式
这种模式不通过服务器端程序来完成,直接由浏览器发送请求获取令牌,令牌是完全暴露在浏览器中的,这种模式极力不推崇。流程如下图3所示:
密码模式
密码模式也是比较常用到的一种,客户端向授权服务器提供用户名、密码然后得到授权令牌。这种模式不过有种弊端,我们的客户端需要存储用户输入的密码,但是对于用户来说信任度不高的平台是不可能让他们输入密码的。流程如下图4所示:
客户端模式
客户端模式是客户端以自己的名义去授权服务器申请授权令牌,并不是完全意义上的授权。如下图5所示:
上述简单的介绍了OAuth2内部的四种授权方式,我们下面使用密码模式
来进行测试,并且我们使用数据库中的用户数据来做验证处理,下面我们先来构建项目。
构建项目
我们使用IndeiiJ IDEA工具来构建一个SpringBoot项目,目前最新版本的是1.5.3,应该是昨天刚正式发布。项目我们预先引入几个模块,Web、JPA、MySQL、Security、SpringSecurityOAuth2、Druid等,项目结构如下图6所示:
项目构建完成后我们要配置数据库表结构,因为我们要是数据库内保存AccessToken以及RefershToken还有我们的SpringSecurity用户验证信息以及用户角色信息等。
配置数据库
安全用户信息表
用户信息表包含了简单的登录名、密码、邮箱、状态等。表结构如下图7所示:
安全角色信息表
角色信息表结构如下图8所示:
用户角色关联表
用户与角色关联表结构如下图9所示:
AccessToken信息表
我们使用的是SpringSecurityOAuth2提供的Jdbc方式进行操作Token,所以需要根据标准创建对应的表结构,access_token信息表结构如下图10所示:
RefreshToken信息表
刷新Token时需要用到refresh_token信息表结构如下图11所示:
我们的数据库表结构已经建完了,下面我们只需要创建用户信息、角色信息的实体即可,因为OAuth2内部操作数据库使用的JdbcTemplate我们只需要传入一个DataSource对象就可以了,实体并不需要配置。
创建用户实体
用户实体如下图12所示:
创建角色实体
角色实体如下图13所示:
用户实体以及角色实体是用来配置SpringSecurity时用到的实体,我们配置SpringSecurity时需要使用SpringDataJPA从数据库中读取数据,下我们来配置UserJPA以及AuthorityJPA。
UserJPA
配置访问数据库获取用户信息,代码如下图14所示:
我们在UserJPA内添加了一个自定义查询,使用了HQL语法来构建的语句,根据用户名不区分大小写进行查询。
Application.yml配置文件
我们从之前的项目中第十三章:SpringBoot实战SpringDataJPA中源码复制一个application.yml配置文件到项目resources下(注意:需要修改对应的数据库配置),如下图所示:
AuthorityJPA
配置访问数据库中的角色列表,代码如下图15所示:
下面我们来配置两个控制器用来区分我们配置OAuth2是否已经生效。
HelloWorldController
我在HelloWorldController内只添加一个字符串的输出,这个控制器我们开放,让SpringSecurity不去管理,配置将会在下面展现,控制器代码如下图16所示:
SecureController
这个控制器是需要我们获取授权Token后使用Token才可以访问到的,代码如下图17所示:
综上所述我们的项目基础的构建已经完成,大家都知道SpringSecurity在使用数据库的数据时需要自定义UserDetailsService用来从数据库中根据用户名查询用户信息以及角色信息并返回给SpringSecurity存放到内存中。
自定义UserDetailsService
我们创建一个名叫HengYuUserDetailsService的类并且实现UserDetailsService接口,代码如下图18所示:
我们在HengYuUserDetailsService类中做了从数据库读取用户的操作,如果没有查询到用户直接抛出异常提示,如果查询到并且设置对应的角色后返回SpringSecurity内置的User对象实例。
开启SpringSecurity配置
下面我们来配置SpringSecurity相关的内容,我们新创建一个配置类SecurityConfiguration,代码如下图19所示:
我们在配置类中注入了上面我们自定义的HengYuUserDetailsService以及用户密码验证规则,我们使用ignoring()方法排除了HelloWorldController内的公开方法,这里可以配置通配符的形式排除。
配置安全资源服务器
下面我们开始配置相关OAuth2的内容,我们创建一个OAuth2总配置类OAuth2Configuration,类内添加一个子类用于配置资源服务器,如下图20所示:
我们在OAuth2Configuration配置类中添加子类ResourceServerConfiguration继承自ResourceServerConfigurerAdapter完成资源服务器的配置,使用@EnableResourceServer注解来开启资源服务器,因为整合SpringSecurity的缘故,我们需要配置登出时清空对应的access_token控制以及自定义401错误内容(authenticationEntryPoint),在配置类中我们排除了对/hello公开地址拦截以及/secure下的所有地址都必须授权才可以访问。
自定义401错误码内容
我们上图已经用到了对应的类CustomAuthenticationEntryPoint,该类是用来配置如果没有权限访问接口时我们返回的错误码以及错误内容,代码如下图21所示:
定义登出控制
当我们退出系统时需要访问SpringSecrutiy的logout方法来清空对应的session信息,那我们退出后改用户的access_token还依然存在那就危险了,一旦别人知道该token就可以使用之前登录用户的权限来操作业务。logout控制代码如下图22所示:
开启OAuth2验证服务器
我们还是在OAuth2Configuration配置类中添加一个子类,用于开启OAuth2的验证服务器,代码如下图23、24所示:
图23中我们创建了一个名叫AuthorizationServerConfiguration
的类继承自AuthorizationServerConfigurerAdapter
并且实现了EnvironmentAware
(读取properties文件需要)接口,并使用@EnableAuthorizationServer
注解开启了验证服务器,可以看到我们使用SpringSecurityOAuth2
内定义的JdbcStore
来操作数据库中的Token
,当然需要有需要我们可以通过SpringDataJPA
自定义Sotre
。
图24中我们的OAuth2的客户端配置并没有从数据库中读取而是使用了内存中获取,因为本章的内容比较多,所以在后期文章中我们会再次讲到如何从数据库中获取clients进行验证。我们在创建客户端信息时使用到了application.properties配置文件的自定义配置,具体配置内容如下图25所示:
运行测试
项目编写完成,接下来我们使用SpringBootApplication形式来运行项目进行测试,运行项目时查询控制台输出日志是否正确!
我们先来使用Postman工具访问一下我们公开的地址127.0.0.1:8080/hello,如下图26所示:
可以看到我们是可以正确的访问到接口输出内容的,下面我们再来访问一下被oauth2管理的地址127.0.0.1:8080/secure,如下图27所示:
我们可以看到直接给我们返回了一个页面,这样就不对了,我们应该得到一个401的错误码以及自定义的信息才对,当然我们需要添加一些配置来完成这个功能,我们打开application.properties配置文件添加如下图28配置:
图中画红色框的就是我们新添加的配置内容,这个配置的意思时,将我们的资源拦截的过滤器运行顺序放到第3个执行,也就是在oauth2的认证服务器后面执行,我们重启下项目再来访问下刚才的地址,输出内容如下图29所示:
可以看到正如我们预期一样,返回了401错误以及我们自定义的错误码”Access Denied“,下面我们来获取access_token。
获取AccessToken
我们在获取token之前需要在数据库中添加几条对应的数据,具体的SQL我会放到源码项目的resources目录下,文章地址有源码地址。我们来访问/oauth/token地址获取access_token,如下图30所示:
可以看到我们访问的地址,grant_type使用到了password模式,我们在上面的配置中就是配置我们的客户端(yuqiyu_home_pc)可以执行的模式有两种:password、refresh_token。获取access_token需要添加客户端的授权信息clientid、secret,通过Postman工具的头授权信息即可输出对应的值就可以完成Basic Auth的加密串生成。
成功访问后oauth2给我们返回了几个参数:
access_token
:本地访问获取到的access_token,会自动写入到数据库中。 token_type
:获取到的access_token的授权方式 refersh_token
:刷新token时所用到的授权token expires_in
:有效期(从获取开始计时,值秒后过期) scope
:客户端的接口操作权限(read:读,write:写)
使用AccessToken访问
我们使用获取到的access_token值来访问对应的地址http://127.0.0.1:8080/secure?access_token=9ca7fd9b-1289-440b-b1a1-0303782f660e,效果如下图31所示:
可以看到我们已经可以正常的访问到数据内容了,证明我们的access_token是有效的。当我们用到的token已经过期时效果如下图32所示:
oauth2告诉我们需要刷新Token了,您传入的token值已经过期了。
刷新AccessToken
我们的access_token过期我们需要刷新后返回新的token,使用新token才能继续操作数据接口。刷新access_token如下图33所示:
看到上图33红色框内的值了吗?这个就是我们之前获取token时,oauth2给我们返回的refresh_token值,我们需要用到该值来进行刷新token。新的token值得有效期可以看到又是我们配置的默认1800秒,刷新token时oauth2还是给我们返回了一个refersh_token值,该值要作为下次刷新token时使用。
总结
综上内容就是本章的全部内容,本章的内容比较多希望读者可以仔细阅读,本章主要讲解了SpringBoot作为框架基础上配置SpringSecurity安全框架整合OAuth2安全框架做双重安全,讲解如果通过数据库的形式获取到授权用户信息以及角色列表,通过内存配置的OAuth2的客户端配置来获取access_token以及如何使用access_token访问受保护的资源接口。
本章代码已经上到码云:
SpringBoot配套源码地址:https://gitee.com/hengboy/spring-boot-chapter
SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter
SpringBoot相关系列文章请访问:目录:SpringBoot学习目录
QueryDSL相关系列文章请访问:QueryDSL通用查询框架学习目录
SpringDataJPA相关系列文章请访问:目录:SpringDataJPA学习目录
SpringBoot相关文章请访问:目录:SpringBoot学习目录,感谢阅读!
欢迎加入QQ技术交流群,共同进步。