前言
大部分系统开发的第一个功能,基本上都是用户注册、登录,这两个看似简单的功能,实则是任何系统安全的基石。Spring Security 想必都很熟悉,作为一个安全管理框架,提供了丰富的认证机制、授权模型以及安全防护措施,极大简化了安全性的开发过程。本文就围绕 Spring Security 的架构原理、配置方法以及高级特性,高效的构建出一个安全的Web应用。
Spring Security 原理
本质
Spring Security 本质上就是一个过滤器链,所有的访问请求都会经过这些过滤器。如下图
不了解过滤器的可以自行前往了解。
图中的 SecurityFilterChain
就是 Spring Security 的过滤器链,认证、授权以及**安全防护(XSS、CSRF等)**都是在这些过滤器中完成的。
当项目中引入 Spring Security 启动后,一些默认的 SecurityFilterChain
过滤器会被装载,如下:
其中几个过滤器的命名直接体现了其功能,例如LogoutFilter
、UsernamePasswordAuthenticationFilter
、DefaultLoginPageGeneratingFilter
等。
从DefaultLoginPageGeneratingFilter
生成页面就可以得知:Spring Security 默认是前后端不分离的。
所以,对于现在大多数前后端分离的项目,我们可以通过配置自定义需要哪些过滤器以及如何处理该功能,例如如何认证、认证失败、授权失败的响应等。
认证流程
开发者使用 Spring Security 最关心应该就是认证了,默认会提供表单认证,也就是生成一个表单页面,填写用户名和密码。如下图
这一功能就是DefaultLoginPageGeneratingFilter
这个过滤器完成的。
用户名和密码默认由InMemoryUserDetailsManager
提供,存储在内存中,这里自然需要我们后续去配置自定义实现。
输入用户名密码后就可以去执行认证流程了,如下图
认证的流程中,我们还需要留意几个接口:
SessionAuthenticationStrategy
,认证成功后,会将用户会话存储在HttpSession
中,在后续会基于HttpSession
进行身份验证,但是对于无状态的应用,这种方式是不可取的。AuthenticationFailureHandler
和AuthenticationSuccessHandler
,默认的认证成功和失败后,会重定向跳转到某个页面,对于前后端分离的项目,这种方式时不可取的,依然需要我们后续去配置自定义实现。
权限验证
通常,在认证时会将用户的权限信息(GrantedAuthority
)加载给UserDetails
,在后续的过滤器会对其进行验证,也就是ExceptionTranslationFilter
和AuthorizationFilter
。
这两个有什么区别?
Spring Security 支持请求URL和方法两种授权方式,即在配置中设置authorizeHttpRequests
以及在方法上面的@PreAuthorize
, @PostAuthorize
注解。
以下是两者验证权限的流程
自定义配置
Spring Security 如何知道我们想要求所有用户都经过身份认证?如何知道我们想要支持基于表单的身份认证?实际上,全靠一个配置类去自定义设置,也就是上面所说的 SecurityFilterChain
。
接下来,就基于配置类构建一个前后端分离集成 Spring Security 的Web应用。
SpringBoot 集成 Spring Security
创建脚手架
SpringBoot 集成 Spring Security 很简单,去spring或者阿里脚手架下载一个demo,引入相关依赖就可以了,主要是这两个
项目启动后就会弹出【认证流程】中的登录界面。
如何配置就看自己的需求了,本文就以前后端分离、无状态为需求,结合JWT进行配置并实现。
Spring Security 如何配置
梳理下前后端分离、无状态以及结合JWT,应该如何配置Spring Security。
- 前后端分离需要配置以下几项:
- 访问失败、认证失败、授权失败等响应改为json格式。
- 由于是前后端分离,所以允许跨域请求。
- 无状态需要配置以下几项:
- 对session的管理要设置为无状态。
- 因为没有session,所以关闭csrf。
- 在JWT中解析安全上下文(SecurityContext)验证,而不是在ThreadLocal中。
前后端分离&无状态配置
以上有几个事项直接配置即可,例如cors、csrf、session无状态、自定义登录等。
需要自定义实现的配置事项如下:
- 自定义登录配置后,需要自己写登录认证接口。
- 未认证时访问接口,默认会由
LoginUrlAuthenticationEntryPoint
处理,会重定向到某个URL,需要配置自定义为json响应。 - 认证失败后,默认会由
SimpleUrlAuthenticationFailureHandler
处理,会重定向到某个URL,需要配置自定义为json响应。 - 认证成功后,默认会由
SavedRequestAwareAuthenticationSuccessHandler
处理,会重定向到某个URL,需要配置自定义为json响应。 - 认证成功后,请求未授权的接口,默认会由
AccessDeniedHandlerImpl
处理,会重定向到某个URL,需要配置自定义为json响应。 - 注销成功后,默认会由
SimpleUrlAuthenticationSuccessHandler
,会重定向到某个URL,需要配置自定义为json响应。 - 整个流程中涉及到的安全上下文(SecurityContext),通过自定义过滤器,在token中解析并设置到当前请求的上下文。
整个配置大概是这样
自定义认证逻辑
通过以上简单的配置,就已经实现了前后端分离架构和无状态会话管理。除此之外,项目中还需要实现自定义认证逻辑。
以上的配置来看,当post
请求/login
,传入username
和password
就会执行认证流程,不需要写/login
接口。
那我们需要写哪些代码实现自定义认证逻辑?
认证流程(看图)中的AuthenticationManager
,会调用 UserDetailsService
接口中的loadUserByUsername()
,最终返回UserDetails
对象交给Spring Security管理。
具体来说,我们需要创建两个类,一个实现UserDetailsService
接口,重写loadUserByUsername()
加载用户信息,一个实现UserDetails
,定义业务中的用户信息。
loadUserByUsername()
中,我们只需要做几件事:
- 根据username获取用户信息,包括密码、角色和权限什么的,获取来源自定义,通常是关系型数据库,例如MySql。
- 如果该用户不存在,抛出异常即可。
- 将用户信息封装到
UserDetails
中,返回。
注意,这里不需要多此一举校验密码,认证流程中会有这一步骤。你只需将数据库中加密的密码交个UserDetails即可。
不需要写
/login
接口,认证成功后我想返回token怎么办?
如果有这样的疑问,那你一定是没好好看上面认证流程以及配置:代码中的formConfig.successHandler
配置就是用来响应数据的。
完整的代码
为了方便大家观看,我将所有的代码放在一个文件中,如下图
注意:这只是一个示例代码,供大家学习了解Spring Security,可以参考,不建议直接在项目中使用。有任何问题可以随时交流。
总结
Spring Security 本质上就是一连串的过滤器,当一个请求来临时,这些过滤器会对该请求一一处理,包括登录认证、权限验证以及其他安全防护。也因其灵活性,我们只需有简单的配置加上一些代码就可以快速构建出一个安全性极高的Web应用。对于每次集成Spring Security都需要百度的开发者,希望在看完这篇文章的原理+实战后,可以让你摆脱此烦恼。