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

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

图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学习目录,感谢阅读!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏西安-晁州

Spring boot应用踩坑集锦

spring boot是spring的一种开发套件,是spring cloud的基础框架,要学习spring cloud微服务是绕不开的,遇到一些踩坑问题在这里...

534
来自专栏布尔

用c#添加Networkservice对文件夹的写权限

      一般的bs程序都会涉及到上传文件,这就要求network service用户有对文件夹的写权限,通常情况下我们都是用打包时调用打包程序的脚本去设置权...

2036
来自专栏软件测试经验与教训

ADB获取APP崩溃日志

在测试Android APP的过程中遇到crash时,我们都需要把崩溃日志导出来作为附件传到bug管理工具中,今天分享一下我用的方式。

652
来自专栏漫漫全栈路

.NET Core 实战笔记2-从命令开始

上一篇简要介绍了 .NET Core平台,本篇对dotnet命令进行讲解。

2271
来自专栏码神联盟

碎片化 | 第一阶段-03-Java语言环境搭建-视频

如清晰度低,可转PC网页观看高清版本: http://v.qq.com/x/page/v0565h4wpb6.html ---- 什么是jre、什么是JDK。...

35211
来自专栏电光石火

MyBatis Generator自动创建代码

        这两天需要用到MyBatis的代码自动生成的功能,由于MyBatis属于一种半自动的ORM框架,所以主要的工作就是配置Mapping映射文件,...

1616
来自专栏程序猿

SSM项目中配置文件的解说

“ 讲解一下基于SSM框架的Java Web项目中的配置文件,必要的注释,我已经添加到了代码中,可以下载。”

1023
来自专栏电光石火

MyBatis Generator自动创建代码

        这两天需要用到MyBatis的代码自动生成的功能,由于MyBatis属于一种半自动的ORM框架,所以主要的工作就是配置Mapping映射文件,但...

2155
来自专栏SpringBoot 核心技术

第一章:用一个HelloWord来阐述SpringBoot的简单与快速简介 本章目的系统要求开始构建项目项目目录结构初尝试运行项目编写HelloWordController总结

862
来自专栏林德熙的博客

VisualStudio 扩展开发 安装 Visual Studio SDK添加菜单增加选项传到商店获取工程所有项目升级 2017

本文主要:如何开发一个 visual Studio 扩展,其实扩展也叫插件。 那么就是如何开发一个 vs插件。 本文也记录了我调试 VisualStudio 半...

1272

扫码关注云+社区