初始SpringSecurity安全框架
在介绍之前,我们先来体验一下SpringSecurity。
我们在一个新建springboot2的项目中,来引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 首先,用户通过Controller向未授权的资源发出未经身份验证的GetMapper请求/private。
- 然后在浏览器URL中输入项目的启动端口+private,你就会发现自己跳转到了login登录页面,打开idea发现控制台多了一串字符串,通过用户名=user + 密码=字符串可以登录成功,并访问到private,这就是接下来要讲的。
认证(你是谁?)
表单登录
本节检查基于表单的登录在 Spring 安全性中的工作原理。 首先,我们看到用户如何被重定向到登录表单:
loginurlauthenticationentrypoint.png
- 一、Spring Security的AuthorizationFilter设置了anyRequest().authenticated(); //默认拦截所有的请求。
- 二、AuthorizationFilter如果访问被拦截则会通过ExceptionTranslationFilter(处理安全异常的安全过滤器)处理.AccessDeniedException异常。
- 三、由于用户未经过身份验证,因此ExceptionTranslationFilter会启动身份验证,并使用配置的 AuthenticationEntryPoint 将重定向到登录页面。
- 四、重定向到login页面
用户提交用户名和密码后,将对用户名和密码进行身份验证。
身份验证处理过滤器:
usernamepasswordauthenticationfilter.png
在此拦截器中验证用户名和密码是否正确,并指向到不同的拦截器链进行处理。
授权(你能做什么)
我们可以使用authorizeHttpRequests来定制不同的放行策略。
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
// ...
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/resources/**", "/signup", "/about").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/db/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasRole('DBA')"))
// .requestMatchers("/db/**").access(AuthorizationManagers.allOf(AuthorityAuthorizationManager.hasRole("ADMIN"), AuthorityAuthorizationManager.hasRole("DBA")))
.anyRequest().denyAll()
);
return http.build();
}
- 指定了多个授权规则。 每个规则都按照声明的顺序进行考虑。
- 我们指定了任何用户都可以访问的多个 URL 模式。 具体而言,如果 URL 以“/resources/”开头、等于“/signup”或等于“/about”,则任何用户都可以访问请求。
- 任何以“/admin/”开头的 URL 都将仅限于具有“ROLE_ADMIN”角色的用户。 您会注意到,由于我们正在调用该方法,因此我们不需要指定“ROLE_”前缀。hasRole
- 任何以“/db/”开头的 URL 都要求用户同时具有“ROLE_ADMIN”和“ROLE_DBA”。 您会注意到,由于我们使用的是表达式,因此不需要指定“ROLE_”前缀。hasRole
- 4中的相同规则,可以通过组合多个.AuthorizationManager
- 任何尚未匹配的 URL 都将被拒绝访问。 如果您不想意外忘记更新授权规则,这是一个很好的策略。
请求匹配
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/user/**").hasRole("USER")
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
}
- 配置为仅应用于以HttpSecurity/api/
- 允许具有该角色的用户访问以 开头的网址/user/USER
- 允许具有该角色的用户访问以 开头的网址/admin/ADMIN
- 任何其他不符合上述规则的请求都需要身份验证
防范漏洞利用
针对跨站点请求伪造攻击(CSRF)攻击
在SpringSecurity中,默认是启用了防范CSRF策略,但可以设置禁用CSRF
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable());
return http.build();
}
}
在 Spring Security 中,专门提供了一个 CsrfFilter 来实现对 CSRF 的保护。CsrfFilter 拦截请求,并允许使用 GET、HEAD、TRACE 和 OPTIONS 等 HTTP 方法的请求。而针对 PUT、POST、DELETE 等可能会修改数据的其他请求,CsrfFilter 则希望接收包含 csrf_token 的消息头。如果这个消息头不存在或包含不正确的 csrf_token 值,应用程序将拒绝该请求并将响应的状态设置为 403。
CSRF攻击的原理是这样的:
CSRF攻击
CSRF的攻击者只是利用了通过认证的Cookie。
CsrfFilter就在返回给客户端的表单里隐藏域的Value里和请求头中放置随机值,并在服务端也放置这个随机值,在用户发送请求时来校验这个随机值是否正确,如果不正确则拒绝访问。
表单里的随机字符串
但是在微服务中不能使用这种方式,因为客户端访问了一次实例,下发了Token。但微服务的多模块及负载均衡的特性,我们下一次携带Token发送的请求,未必是上一次的实例。
后端可以把随机字符串存入到Redis里,响应给前端,前端携带字符串访问Redis来验证。