第十八章:SpringBoot项目中使用SpringSecurity整合OAuth2设计项目API安全接口服务

OAuth是一个关于受权的开放网络标准,在全世界获得的普遍的应用,目前是2.0的版本。OAuth2在“客户端”与“服务提供商”之间,设置了一个受权层(authorization layer)。“客户端”不能直接登陆“服务提供商”,只能登陆受权层,以此将用户与客户端分离。“客户端”登陆须要OAuth提供的令牌,不然将提示认证失败而致使客户端没法访问服务。下面咱们就来说解下SpringBoot项目中是如何配置使用OAuth2服务器端,并让OAuth2整合SpringSecurity来保护咱们的REST接口。git


知识星球

已开通知识星球,欢迎你们加入交流技术以及提问SpringBoot相关问题。spring


本章目标

基于SpringBoot项目提供一个继承OAuth2安全框架的REST API服务端,必须获取访问受权令牌后才能够访问资源。数据库

OAuth2受权方式

咱们在文章开始已经说过了,咱们的保护资源必须经过受权获得的令牌才能够访问。那么咱们这个受权令牌要经过什么方式获取呢?浏览器

OAuth2为咱们提供了四种受权方式:安全

一、受权码模式(authorization code)
二、简化模式(implicit)
三、密码模式(resource owner password credentials)
四、客户端模式(client credentials)服务器

受权码模式

受权码相对其余三种来讲是功能比较完整、流程最安全严谨的受权方式,经过客户端的后台服务器与服务提供商的认证服务器交互来完成。流程以下图2所示:网络


图2

简化模式

这种模式不经过服务器端程序来完成,直接由浏览器发送请求获取令牌,令牌是彻底暴露在浏览器中的,这种模式极力不推崇。流程以下图3所示:session


图3

密码模式

密码模式也是比较经常使用到的一种,客户端向受权服务器提供用户名、密码而后获得受权令牌。这种模式不过有种弊端,咱们的客户端须要存储用户输入的密码,可是对于用户来讲信任度不高的平台是不可能让他们输入密码的。流程以下图4所示:app


图4

客户端模式

客户端模式是客户端以本身的名义去受权服务器申请受权令牌,并非彻底意义上的受权。以下图5所示:框架


图5

上述简单的介绍了OAuth2内部的四种受权方式,咱们下面使用密码模式来进行测试,而且咱们使用数据库中的用户数据来作验证处理,下面咱们先来构建项目。

构建项目

咱们使用IndeiiJ IDEA工具来构建一个SpringBoot项目,目前最新版本的是1.5.3,应该是昨天刚正式发布。项目咱们预先引入几个模块,Web、JPA、MySQL、Security、SpringSecurityOAuth二、Druid等,项目结构以下图6所示:


图6

项目构建完成后咱们要配置数据库表结构,由于咱们要是数据库内保存AccessToken以及RefershToken还有咱们的SpringSecurity用户验证信息以及用户角色信息等。

配置数据库

安全用户信息表

用户信息表包含了简单的登陆名、密码、邮箱、状态等。表结构以下图7所示:


图7

安全角色信息表

角色信息表结构以下图8所示:


图8

用户角色关联表

用户与角色关联表结构以下图9所示:


图9

AccessToken信息表

咱们使用的是SpringSecurityOAuth2提供的Jdbc方式进行操做Token,因此须要根据标准建立对应的表结构,access_token信息表结构以下图10所示:


图10

RefreshToken信息表

刷新Token时须要用到refresh_token信息表结构以下图11所示:


图11

咱们的数据库表结构已经建完了,下面咱们只须要建立用户信息、角色信息的实体便可,由于OAuth2内部操做数据库使用的JdbcTemplate咱们只须要传入一个DataSource对象就能够了,实体并不须要配置。

建立用户实体

用户实体以下图12所示:


图12

建立角色实体

角色实体以下图13所示:


图13

用户实体以及角色实体是用来配置SpringSecurity时用到的实体,咱们配置SpringSecurity时须要使用SpringDataJPA从数据库中读取数据,下咱们来配置UserJPA以及AuthorityJPA。

UserJPA

配置访问数据库获取用户信息,代码以下图14所示:


图14

咱们在UserJPA内添加了一个自定义查询,使用了HQL语法来构建的语句,根据用户名不区分大小写进行查询。

Application.yml配置文件

咱们从以前的项目中第十三章:SpringBoot实战SpringDataJPA中源码复制一个application.yml配置文件到项目resources下(注意:须要修改对应的数据库配置),以下图所示:


.

AuthorityJPA

配置访问数据库中的角色列表,代码以下图15所示:


图15

下面咱们来配置两个控制器用来区分咱们配置OAuth2是否已经生效。

HelloWorldController

我在HelloWorldController内只添加一个字符串的输出,这个控制器咱们开放,让SpringSecurity不去管理,配置将会在下面展示,控制器代码以下图16所示:


图16

SecureController

这个控制器是须要咱们获取受权Token后使用Token才能够访问到的,代码以下图17所示:


图17

综上所述咱们的项目基础的构建已经完成,你们都知道SpringSecurity在使用数据库的数据时须要自定义UserDetailsService用来从数据库中根据用户名查询用户信息以及角色信息并返回给SpringSecurity存放到内存中。

自定义UserDetailsService

咱们建立一个名叫HengYuUserDetailsService的类而且实现UserDetailsService接口,代码以下图18所示:


图18

咱们在HengYuUserDetailsService类中作了从数据库读取用户的操做,若是没有查询到用户直接抛出异常提示,若是查询到而且设置对应的角色后返回SpringSecurity内置的User对象实例。

开启SpringSecurity配置

下面咱们来配置SpringSecurity相关的内容,咱们新建立一个配置类SecurityConfiguration,代码以下图19所示:


图19

咱们在配置类中注入了上面咱们自定义的HengYuUserDetailsService以及用户密码验证规则,咱们使用ignoring()方法排除了HelloWorldController内的公开方法,这里能够配置通配符的形式排除。

配置安全资源服务器

下面咱们开始配置相关OAuth2的内容,咱们建立一个OAuth2总配置类OAuth2Configuration,类内添加一个子类用于配置资源服务器,以下图20所示:


图20

咱们在OAuth2Configuration配置类中添加子类ResourceServerConfiguration继承自ResourceServerConfigurerAdapter完成资源服务器的配置,使用@EnableResourceServer注解来开启资源服务器,由于整合SpringSecurity的缘故,咱们须要配置登出时清空对应的access_token控制以及自定义401错误内容(authenticationEntryPoint),在配置类中咱们排除了对/hello公开地址拦截以及/secure下的全部地址都必须受权才能够访问。

自定义401错误码内容

咱们上图已经用到了对应的类CustomAuthenticationEntryPoint,该类是用来配置若是没有权限访问接口时咱们返回的错误码以及错误内容,代码以下图21所示:


图21

定义登出控制

当咱们退出系统时须要访问SpringSecrutiy的logout方法来清空对应的session信息,那咱们退出后改用户的access_token还依然存在那就危险了,一旦别人知道该token就可使用以前登陆用户的权限来操做业务。logout控制代码以下图22所示:


图22

开启OAuth2验证服务器

咱们仍是在OAuth2Configuration配置类中添加一个子类,用于开启OAuth2的验证服务器,代码以下图2三、24所示:


图23

图23中咱们建立了一个名叫AuthorizationServerConfiguration的类继承自AuthorizationServerConfigurerAdapter而且实现了EnvironmentAware(读取properties文件须要)接口,并使用@EnableAuthorizationServer注解开启了验证服务器,能够看到咱们使用SpringSecurityOAuth2内定义的JdbcStore来操做数据库中的Token,固然须要有须要咱们能够经过SpringDataJPA自定义Sotre


图24

图24中咱们的OAuth2的客户端配置并无从数据库中读取而是使用了内存中获取,由于本章的内容比较多,因此在后期文章中咱们会再次讲到如何从数据库中获取clients进行验证。咱们在建立客户端信息时使用到了application.properties配置文件的自定义配置,具体配置内容以下图25所示:


图25

运行测试

项目编写完成,接下来咱们使用SpringBootApplication形式来运行项目进行测试,运行项目时查询控制台输出日志是否正确!

咱们先来使用Postman工具访问一下咱们公开的地址127.0.0.1:8080/hello,以下图26所示:


图26

能够看到咱们是能够正确的访问到接口输出内容的,下面咱们再来访问一下被oauth2管理的地址127.0.0.1:8080/secure,以下图27所示:


图27

咱们能够看到直接给咱们返回了一个页面,这样就不对了,咱们应该获得一个401的错误码以及自定义的信息才对,固然咱们须要添加一些配置来完成这个功能,咱们打开application.properties配置文件添加以下图28配置:


图28

图中画红色框的就是咱们新添加的配置内容,这个配置的意思时,将咱们的资源拦截的过滤器运行顺序放到第3个执行,也就是在oauth2的认证服务器后面执行,咱们重启下项目再来访问下刚才的地址,输出内容以下图29所示:


图29

能够看到正如咱们预期同样,返回了401错误以及咱们自定义的错误码”Access Denied“,下面咱们来获取access_token。

获取AccessToken

咱们在获取token以前须要在数据库中添加几条对应的数据,具体的SQL我会放到源码项目的resources目录下,文章地址有源码地址。咱们来访问/oauth/token地址获取access_token,以下图30所示:


图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所示:


图31

能够看到咱们已经能够正常的访问到数据内容了,证实咱们的access_token是有效的。当咱们用到的token已通过期时效果以下图32所示:


图32

oauth2告诉咱们须要刷新Token了,您传入的token值已通过期了。

刷新AccessToken

咱们的access_token过时咱们须要刷新后返回新的token,使用新token才能继续操做数据接口。刷新access_token以下图33所示:


图33

看到上图33红色框内的值了吗?这个就是咱们以前获取token时,oauth2给咱们返回的refresh_token值,咱们须要用到该值来进行刷新token。新的token值得有效期能够看到又是咱们配置的默认1800秒,刷新token时oauth2仍是给咱们返回了一个refersh_token值,该值要做为下次刷新token时使用。

总结

综上内容就是本章的所有内容,本章的内容比较多但愿读者能够仔细阅读,本章主要讲解了SpringBoot做为框架基础上配置SpringSecurity安全框架整合OAuth2安全框架作双重安全,讲解若是经过数据库的形式获取到受权用户信息以及角色列表,经过内存配置的OAuth2的客户端配置来获取access_token以及如何使用access_token访问受保护的资源接口。

本章代码已经上到码云:

SpringBoot配套源码地址:gitee.com/hengboy/spr…

SpringCloud配套源码地址:gitee.com/hengboy/spr…

SpringBoot相关系列文章请访问:目录:SpringBoot学习目录

QueryDSL相关系列文章请访问:QueryDSL通用查询框架学习目录

SpringDataJPA相关系列文章请访问:目录:SpringDataJPA学习目录

SpringBoot相关文章请访问:目录:SpringBoot学习目录,感谢阅读!

欢迎加入QQ技术交流群,共同进步。

相关文章
相关标签/搜索