一、简介
1、什么是安全框架
安全框架是对访问权限进行控制的系统架构,旨在确保应用的安全性。应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
**用户认证**是验证某个用户是否为系统中的合法主体,即用户能否访问该系统。一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。
**用户授权**是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。例如,对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。
安全框架的选择也会影响系统的安全性。例如,Shiro比Spring Security更容易使用,实现上简单一些,同时基本的授权认证Shiro也基本够用。而Spring Security社区支持度更高,社区的亲儿子,支持力度和更新维护上有优势,同时和Spring这一套的结合较好。
2、主流的安全框架
- Shiro:是一个轻量级的安全框架,上手简单,提供了许多功能,包括身份验证、授权、加密和会话管理等。它不依赖于任何容器,可以运行在JavaSE和JavaEE项目中。
- Spring Security:这是一个比较重的安全框架,但也是最流行的。它提供了许多过滤器,也方便扩展。
- Sa-Token:这是一个近期的开源框架,上手容易,只需使用注解即可。它也内置了非常多的方法和注解,方便扩展。
3、为什么使用Spring Security
- 成熟稳定:Spring Security是一个成熟的框架,经过多年的发展和改进,已经相当稳定和可靠。它拥有庞大的用户基础和活跃的社区,可以提供丰富的支持和资源。
- 开源免费:Spring Security是开源的,可以免费使用和定制。这意味着用户可以根据自己的需求进行修改和扩展,同时也可以为该项目做出贡献。
- 易于集成:Spring Security与Spring框架深度集成,因此可以方便地使用Spring的其他功能,如依赖注入、事务管理等。这使得在Spring环境下开发安全功能更加便捷。
- 丰富的功能:Spring Security提供了丰富的安全功能,包括用户认证、授权、密码加密、会话管理等。这些功能可以帮助开发者快速构建安全的应用程序,而无需从头开始实现。
- 灵活的定制:尽管Spring Security提供了丰富的功能,但它也允许开发者根据自己的需求进行定制。这使得开发者可以根据应用程序的具体需求进行调整和优化。
- 安全性高:Spring Security采用了许多先进的安全技术,如加密、身份验证、授权等,可以有效地保护应用程序的安全。同时,由于其社区的活跃性和开源性质,安全性问题可以得到及时的解决和更新。
二、引言
1、什么是Spring Security
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI和AOP功能。Spring Security为基于J2EE企业应用软件提供了全面安全服务,包括认证、授权、加密和会话管理等。它对Web安全性的支持大量地依赖于Servlet过滤器。Spring Security采用“安全层”的概念,使每一层都尽可能安全,连续的安全层可以达到全面的防护。它可以在Controller层、Service层、DAO层等以加注解的方式来保护应用程序的安全。Spring Security提供的是应用程序层的安全解决方案,一个系统的安全还需要考虑传输层和系统层的安全,例如采用Htps协议、服务器部署防火墙等。
2、Spring Security工作原理
权限框架一般包含两大核心模块:认证(Authentication)和鉴权(Authorization)。
- 认证:认证模块负责验证用户身份的合法性,生成认证令牌,并保存到服务端会话中(如TLS)。
- 鉴权:鉴权模块负责从服务端会话内获取用户身份信息,与访问的资源进行权限比对。
核心组件介绍:
- **
AuthenticationManager
**:管理身份验证,可以从多种身份验证方案中选择一种。 - **
Authentication
**:用于验证用户的身份。 - **
SecurityContextHolder
**:用于管理SecurityContext
的ThreadLocal
,以便在整个请求上下文中进行访问,方便用户访问。 - **
AccessDecisionManager
**:负责对访问受保护的资源的请求进行决策(即决定是否允许用户访问资源) - **
AccessDecisionVoter
**:是AccessDecisionManager的实现组件之一,它用于对用户请求的访问受保护的资源所需要的角色或权限进行投票。 - **
ConfigAttribute
**:用于表示受保护资源或URL需要的访问权限,它可以理解为是访问控制策略的一部分
总结:
Spring Security的基本原理是基于J2EE的拦截器原理,形成一条很长的过滤器链。当客户端发起请求时,请求会进入Spring Security的过滤器链,这个过程会判断是否为登录请求。如果是登录请求,则进行用户认证,验证用户是否为系统中的合法主体;如果不是登录请求,则进行用户授权,验证用户是否有权限执行某个操作。在过滤器链中,会根据URI找到对应的鉴权管理器,进行鉴权。 Spring Security还采用了责任链的设计模式,通过实现一些拦截器来对访问的URL进行拦截,通过对缓存中的角色进行相应判定后决定是否能访问该URL。
3、特点
- 兼容性强:
Spring Security
是一个流行的开源框架,它可以与Spring应用程序完美集成。由于它的兼容性很好,因此可以非常方便地使用它保护Web应用程序。 - 功能强大:
Spring Security
具备众多功能,包括注销、登录、角色、权限、令牌、XSS
防御、CSRF
防御等等。它还支持各种身份验证、角色和权限管理方式,如基于表单的认证、基于记住我功能的认证以及OAuth
认证等等。 - 安全可靠:
Spring Security
具有极高的安全性,它使用最新的安全标准和协议来保护Web应用程序。Spring Security
采用安全性分层的策略来保护应用程序中的各个层,例如Web
层、Service
层、DAO
层等等。除此之外,Spring Security
还支持自定义安全策略和事件响应,从而使得开发者可以根据应用程序需求定制安全保护。 - 易于使用:
Spring Security
提供了一种高度简化的方式来保护Web应用程序。它使用简单的标签和安全注解来添加安全保护,从而使得开发者可以基本不需要手动编写代码就可以完成安全保护。 - 社区广泛:
Spring Security
是一个著名的开源框架,因此它有一个庞大的用户社区。这个社区不仅提供了大量的文档、示例和教程,而且还会解答开发者的问题、修复框架中的BUG等等。这为开发者提供了无限的支持和帮助,从而可以使用Spring Security
更加自信。
三、快速入门
1、引入依赖
搭建一个Spring Boot的项目,导入相关依赖。
导入我们的security依赖。
<!-- security--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- freemarker --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
spring-boot-starter-security
包含了以下几个主要的依赖:
- spring-security-core:
Spring Security
的核心模块,提供了基于权限的访问控制以及其他安全相关功能。- spring-security-config:提供了
Spring Security
的配置实现,例如通过Java配置创建安全策略和配置Token存储等。- spring-security-web:提供了
Spring Security Web
的基本功能,例如Servlet
集成和通过HttpSecurity
配置应用程序安全策略。
2、配置
配置application.yml文件
spring: freemarker: # 设置freemarker模板后缀 suffix: .ftl # 设置freemarker模板前缀 template-loader-path: classpath:/templates/ enabled: true
3、启动测试
创建
HellController
类,并定义hello请求处理方法:
@Controller public class HelloController { @RequestMapping("/hello") public String hello(){ return "hello,spring security"; } }
在未配置spring security依赖之前,直接启动项目是可以无阻碍的任意访问请求路径的;但是配置了spring security之后需要通过认证登录后才能访问请求路径。
启动项目,系统会自动生成一个默认的随机登录密码(因为当前没有配置用户信息,配置之后就不会在生成默认登录密码)
这就是
Spring Security
为默认用户 user 生成的临时密码,是一个 UUID 字符串。
4、配置自定义账号密码
在
application.yml
文件中配置自定义用户名和密码。
spring: security: user: name: admin password: 123456
配置完成之后,请重启服务进行测试。
四、Web安全配置类
1.HttpSecurity介绍
HttpSecurity
是
Spring Security
的一个核心类,用于配置应用程序的安全策略。
HttpSecurity
类通常包含许多方法,可以用于配置以下内容:
- HTTP 请求的安全策略,例如访问控制、跨站点请求伪造 (CSRF) 防护等。
- HTTP 验证的安全策略,例如基于表单、HTTP 基本身份验证、OAuth 等。
- 访问受保护资源时所需的身份验证和授权方式。
方法说明
authorizeRequests()
用于配置如何处理请求的授权,默认情况下所有的请求都需要进行认证和授权才能访问受保护的资源
formLogin()
用于配置基于表单的身份验证,包括自定义登录页面、登录请求路径、用户名和密码的参数名称、登录成功和失败的跳转等。
httpBasic()
用于配置基于
HTTP Basic
身份验证,包括定义使用的用户名和密码、
realm
名称等。
logout()
用于配置退出登录功能,包括定义退出登录请求的URL、注销成功后的跳转URL、清除会话、删除
Remember-Me
令牌等。
csrf()
用于配置跨站请求伪造保护,包括定义
CSRF Token
的名称、保存方式、忽略某些请求等。
sessionManagement()
用于配置会话管理,包括定义并发控制、会话失效、禁用URL重定向、会话固定保护等。
rememberMe()
用于配置
Remember-Me
功能,包括定义
Remember-Me
令牌的名称、有效期、加密方法、登录成功后的处理方式等。
exceptionHandling()
用于配置自定义的异常处理,包括定义异常处理器和异常处理页面等。
headers()
用于配置HTTP响应头信息,包括定义
X-Content-Type-Options、X-XSS-Protection、Strict-Transport-Security
等头信息。
cors()
用于配置跨域资源共享,包括定义可访问的来源、
Headers
等。
addFilter()
用于向当前
HttpSecurity
中添加自定义的
Filter
。
and()
用于在配置中添加另一个安全规则,并将两个规则合并。
匹配规则:
- URL匹配
方法说明
配置一个requestMatchers()
数组,参数为request Mather
对象,其RequestMatcher
规则自定义,需要的时候放在最前面,对需要匹配的的规则进行自定义与过滤match
URL权限配置authorizeRequests()
配置一个antMatchers()
的request Mather
数组,参数为string
路径格式, 直接匹配ant
url
anyRequest()
匹配任意
url
,无参 ,最好放在最后面
保护URL
方法说明authenticated()
保护
Url
,需要用户登录
permitAll()
指定URL无需保护,一般应用与静态资源文件
hasRole(String role)
限制单个角色访问
hasAnyRole(String… roles)
允许多个角色访问
access(String attribute)
该方法使用
SPEL
, 所以可以创建复杂的限制
hasIpAddress(String ipaddressExpression)
限制
IP
地址或子网
登录formLogin
方法说明loginPage()
设置登录页面的 URL
defaultSuccessUrl()
设置登录成功后的默认跳转页面
failuerHandler()
登录失败之后的处理器
successHandler()
登录成功之后的处理器
failuerUrl()
登录失败之后系统转向的
url
,默认是
this.loginPage + “?error”
loginProcessingUrl()
设置登录请求的 URL,即表单提交的 URL
usernameParameter()
设置登录表单中用户名字段的参数名,默认为
username
passwordParameter()
设置登录表单中密码字段的参数名,默认为
password
- 登出logout
方法说明
登出logoutUrl()
, 默认是url
l/logout
登出成功后跳转的logoutSuccessUrl()
默认是url
/login?logout
logoutSuccessHandler()
登出成功处理器,设置后会把
logoutSuccessUrl
置为null
2、实现自定义的登录(案例)
①配置相关页面及请求路径
- 自定义登录页
<h1>用户登录</h1>
<form action="/userLogin" method="post">
<label>用户:</label><input type="text" name="username"/><br/>
<label>密码:</label><input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
- 创建
UserController
@Controller
public class UserController {
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
@RequestMapping("/userLogin")
public String userLogin(String username, String password) {
System.out.println("username=" + username + ",password=" + password);
return "index";
}
@RequestMapping("/admin/toAddUser")
public String toAddUser() {
return "admin/addUser";
}
@RequestMapping("/admin/toListUser")
public String toListUser() {
return "admin/listUser";
}
@RequestMapping("/admin/toResetPwd")
public String toResetPwd() {
return "admin/resetPwd";
}
@RequestMapping("/admin/toUpdateUser")
public String toUpdateUser() {
return "admin/updateUser";
}
@RequestMapping("/user/toUpdatePwd")
public String toUpdatePwd() {
return "user/updatePwd";
}
@RequestMapping("/noAccess")
public String noAccess() {
return "accessDenied";
}
}
②创建SecurityConfig配置类
创建
WebSecurityConfig
配置类,设置
@EnableWebSecurity
注解开启
Spring Security
的默认行为。
@Configuration //开启SpringSecurity的默认行为 @EnableWebSecurity public class WebSecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ http.authorizeRequests() // 开放接口访问权限,不需要登录就可以访问 .antMatchers("/toLogin").permitAll() // 其余所有请求全部需要鉴权认证 .anyRequest().authenticated() return http.build(); } }
配置完毕之后,请重启项目访问主页页面进行测试。
除了开放接口能正常访问以外,其他接口均提示403错误。
③配置自定义登录
@Configuration
//开启SpringSecurity的默认行为
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception{
http.authorizeRequests()
// 开放接口访问权限,不需要登录授权就可以访问
.antMatchers("/toLogin").permitAll()
// 其余所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.formLogin()
// 设置登录页面的 URL
.loginPage("/toLogin")
// 设置登录请求的 URL,即表单提交的 URL
.loginProcessingUrl("/userLogin")
// 设置登录表单中用户名字段的参数名,默认为username
.usernameParameter("username")
// 设置登录表单中密码字段的参数名,默认为password
.passwordParameter("password")
;
return http.build();
}
}
配置完毕之后,请重启项目访问登录页
④配置安全退出
修改
SecurityConfig
配置类,加入
Spring Security
安全退出设置。
@Configuration //开启SpringSecurity的默认行为 @EnableWebSecurity public class WebSecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ http.authorizeRequests() // 开放接口访问权限,不需要登录授权就可以访问 .antMatchers("/toLogin").permitAll() // 其余所有请求全部需要鉴权认证 .anyRequest().authenticated() .and() .formLogin() // 设置登录页面的 URL .loginPage("/toLogin") // 设置登录请求的 URL,即表单提交的 URL .loginProcessingUrl("/userLogin") // 设置登录表单中用户名字段的参数名,默认为username .usernameParameter("username") // 设置登录表单中密码字段的参数名,默认为password .passwordParameter("password") .and() .logout() // 设置安全退出的URL路径 .logoutUrl("/logout") // 设置退出成功后跳转的路径 .logoutSuccessUrl("/") ; return http.build(); } }
配置完毕之后,请重启项目。先跳转到登录页面,输入账号密码登录,然后再点击安全退出按钮测试是否成功退出。
⑤配置多用户角色访问
首先请删除或注释掉
application.yml
文件中的配置的自定义账号和密码。
spring:
freemarker:
suffix: .ftl
template-loader-path: classpath:/templates/
enabled: true
# security:
# user:
# name: admin
# password: 123456
修改
SecurityConfig
配置类,加入多用户角色配置。
@Bean
public UserDetailsService userDetailsService() {
UserDetails admin = User.withUsername("admin")
.password(bcryptPasswordEncoder().encode("123456"))
.roles("ADMIN", "USER").build();
UserDetails user = User.withUsername("user")
.password(bcryptPasswordEncoder().encode("123456"))
.roles("USER").build();
return new InMemoryUserDetailsManager(admin, user);
}
异常说明:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
解决方案:这个错误通常是因为在使用
Spring Security
进行密码验证时没有正确地配置密码编码器。在
Spring Security
中,密码编码器用于将用户提供的密码编码为安全的散列值,并将其与存储在数据库中的散列值进行比较。如果没有正确地配置密码编码器,则
Spring Security
将无法识别密码编码器的类型,并在比较密码时引发“没有映射到 id“null”的密码编码器”异常。要解决这个问题,需要在
Spring Security
配置中配置一个密码编码器,并将其用于验证用户提供的密码。常用的密码编码器有
BCryptPasswordEncoder
和
StandardPasswordEncoder
。
修改
SecurityConfig
配置类,配置密码编码器。
@Bean public PasswordEncoder bcryptPasswordEncoder() { return new BCryptPasswordEncoder(); }
重启项目,重新使用账号和密码登录进行测试。
⑥配置自定义异常处理器
修改
SecurityConfig
配置类,设置不同角色的访问权限。
@Configuration
//开启SpringSecurity的默认行为
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception{
http.authorizeRequests()
// 开放接口访问权限,不需要登录授权就可以访问
.antMatchers("/toLogin").permitAll()
// 设置角色权限
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("ADMIN","USER")
// 其余所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.formLogin()
// 设置登录页面的 URL
.loginPage("/toLogin")
// 设置登录请求的 URL,即表单提交的 URL
.loginProcessingUrl("/userLogin")
// 设置登录表单中用户名字段的参数名,默认为username
.usernameParameter("username")
// 设置登录表单中密码字段的参数名,默认为password
.passwordParameter("password")
.and()
.logout()
// 设置安全退出的URL路径
.logoutUrl("/logout")
// 设置退出成功后跳转的路径
.logoutSuccessUrl("/")
;
return http.build();
}
}
6.1、请求接口302错误
重启项目,跳转登录页重新登录。这时发现登录不成功,后台控制台也没有产生任何异常信息,通过浏览器的网络(network)查看现实登录请求接口302错误。
解决方案:关闭csrf
修改
SecurityConfig
配置类,添加关闭
csrf
配置。
http.csrf().disable();
再次重启项目,发现登录正常。
6.2、提示403错误
最后通过切换不同的用户(
user
和
admin
)来测试角色权限是否授权成功,
admin
账号访问所有接口权限正常;切换到
user
账号访问时,发现以
/admin/**
开头的接口不能访问(证明鉴权成功),但是却提示403错误。
解决方案:配置自定义异常处理器
修改
SecurityConfig
配置类,添加自定义异常处理,并设置异常处理页面。
http.exceptionHandling().accessDeniedPage("/noauth")
重启项目后发现无权限将跳转到自定义异常页面。
版权归原作者 无法自律的人 所有, 如有侵权,请联系我们删除。