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

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

免费专题文章汇总

恒宇少年在博客整理出来了SpringBoot、ApiBoot、SpringCloud的文章汇总【SpringBoot基础教程专题】,【SpringCloud基础教程专题】,【ApiBoot组件使用专题

本章目标

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

OAuth2授权方式

我们在文章开始已经说过了,我们的保护资源必须通过授权得到的令牌才可以访问。那么我们这个授权令牌要通过什么方式获取呢?

OAuth2为我们提供了四种授权方式:

1、授权码模式(authorization code)
2、简化模式(implicit)
3、密码模式(resource owner password credentials)
4、客户端模式(client credentials)

授权码模式

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

图2

简化模式

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

图3

密码模式

密码模式也是比较常用到的一种,客户端向授权服务器提供用户名、密码然后得到授权令牌。这种模式不过有种弊端,我们的客户端需要存储用户输入的密码,但是对于用户来说信任度不高的平台是不可能让他们输入密码的。流程如下图4所示:

图4

客户端模式

客户端模式是客户端以自己的名义去授权服务器申请授权令牌,并不是完全意义上的授权。如下图5所示:

图5

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

构建项目

我们使用IndeiiJ IDEA工具来构建一个SpringBoot项目,目前最新版本的是1.5.3,应该是昨天刚正式发布。项目我们预先引入几个模块,Web、JPA、MySQL、Security、SpringSecurityOAuth2、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的验证服务器,代码如下图23、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配套源码地址:https://gitee.com/hengboy/spring-boot-chapter

SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter

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

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

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

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

欢迎微信扫码加入知识星球,恒宇少年带你走以后的技术道路!!!

知识星球 - 恒宇少年

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容