SpringSecurity实现过滤器
@toc
1、概述
在Spring Security中,通常HTTP过滤器会管理必须应用于请求的每个职责。过滤器形成了职责链。过滤器会接受请求、执行其逻辑,并最终将请求委托给链中的下一个过滤器。
2、Spring Security架构中实现过滤器
Spring Security架构中国的过滤器是典型的HTTP过滤器。可以通过javax.servlet包实现Filter接口来创建过滤器。对于其他任何HTTP过滤器,需要重写doFilter()方法来实现其逻辑。此方法会接收ServletRequest、ServletResponse和FilterChain作为参数。
- ServletRequest:表示HTTP请求。使用ServletRequest对象检索关于请求的详细信息。
- ServletResponse:表示HTTP响应。使用ServletResponse对象在将响应发送回客户但或顺着过滤器链更进一步执行之前修改该响应。
- FilterChain:表示过滤器链。使用FilterChain对象将请求转发给链中的下一个过滤器。
过滤器链表示过滤器的集合,这些过滤器会按照已经定义的顺序执行操作。Spring Security提供了一些过滤器实现和它们的预定义执行顺序。在所提供的过滤器中:
-
BasicAuthenticationFilter负责HTTP Basic身份验证(如果使用它的话)
-
CsrfFilter负责跨站请求(CSRF)防护。
-
CorsFilter负责跨源资源共享(CORS)规则。
不需要链接所有过滤器,因为可能不会从代码中直接接触到它们,但是我们需要链接过滤器链是如何工作的,并了解其中的一些实现。
3、在过滤器链中现有过滤器之前添加过滤器
考虑一个简单的场景,我们希望确保任何请求都有一个名为Request-Id的头信息。假设应用程序使用这个头信息跟踪请求,并且这个头信息是必须的。同时,我们希望应用程序执行身份验证之前验证这些假设。身份验证过程可能涉及查询数据库或其他消耗资源的操作,如果请求的格式无效,则我们不希望应用程序执行这些操作。那么应该怎么做呢?要解决当前的这个需求,只需两个步骤,最终的过滤器链如下图。
- 实现该过滤器。创建一个RequestValidationFilter类,用于检查请求中是否存在所需的头信息。
- 将该过滤器添加到过滤器链。要在配置类中完成此处理,需要重写configure()方法。
3.1 实现一个自定义过滤器
public class RequestValidationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
var httpRequest = (HttpServletRequest) request;
var httpResponse = (HttpServletResponse) response;
String requestId = httpRequest.getHeader("Request-Id");
if (requestId == null || requestId.isBlank()) {
httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
filterChain.doFilter(request, response);
}
}
在doFilter()方法内部,我们编写了该过滤器的逻辑。在这个示例中,将检查Request-Id头信息是否存在。如果存在,则通过调用doFilter()方法将请求转发给链中的下一个过滤器。如果头信息不存在,则将在响应上设置HTTP状态400 Bad Request,而不将其转发到链中的下一个过滤器。
3.2 在身份验证之前配置自定义过滤器
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(
new RequestValidationFilter(),
BasicAuthenticationFilter.class)
.authorizeRequests()
.anyRequest()
.permitAll();
}
}
这里使用了HttpSecurity对象的addFilterBefore()方法,因为我们希望应用程序在身份验证之前执行这个自定义过滤器,这个方法可以接收两个参数:
- 希望添加到链中的自定义过滤器的一个实例。
- 在其之前添加新实例的过滤器类型。对于本示例,因为要求在身份验证之前执行过滤器逻辑,所以需要在身份验证过滤器之前添加自定义过滤器实例。类BasicAuthenticationFilter定义了身份验证过滤器的默认类型。
3.3 控制器类
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello!";
}
}
3.4 测试
不添加Request-Id头信息
添加头信息
4、在过滤器链中已有的过滤器之后添加过滤器
假设必须在身份验证过程之后执行一些逻辑。这方面的例子包括,在某些身份验证事件发生后通知不同的系统,或者只是为了达成日志记录和跟踪目的。
对于本示例而言,需要通过在身份验证过滤器之后添加一个过滤器来记录所有成功的身份验证事件。我们认为通过身份验证过滤器的是一个成功的身份验证事件,并且希望记录它。
4.1 定义一个过滤器来记录请求
public class AuthenticationLoggingFilter implements Filter {
private final Logger logger =
Logger.getLogger(AuthenticationLoggingFilter.class.getName());
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
var httpRequest = (HttpServletRequest) request;
String requestId = httpRequest.getHeader("Request-Id");
logger.info("Successfully authenticated request with id " + requestId);
filterChain.doFilter(request, response);
}
}
要在链中的身份验证过滤器之后添加自定义过滤器,可以调用HttpSecurity的addFilterAfter()方法,代码如下:
4.2 在过滤器链中的现有过滤器之后添加自定义过滤器
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(
new RequestValidationFilter(),
BasicAuthenticationFilter.class)
.addFilterAfter(
new AuthenticationLoggingFilter(),
BasicAuthenticationFilter.class)
.authorizeRequests()
.anyRequest()
.permitAll();
}
}
4.3 测试
控制台会打印如下信息:
5、在过滤器链中另一个过滤器的位置添加一个过滤器
假设不打算使用HTTP Basic身份验证流程,而是要实现一些不同的处理。相较于使用用户名和密码作为应用程序对用户进行身份验证的输入凭据,这里需要应用另一种方法。可能会遇到的一些场景示例是:
- 基于用户身份验证的静态头信息值得标识。
- 使用对称密钥对身份验证请求进行签名。
- 在身份验证过程中使用一次性密码(OTP)。
接下来实现一个示例来展示如何应用自定义过滤器。为了保持示例得相关性和直观性,要将重点放在配置上,并考虑实现一个简单的身份验证逻辑。在我们得场景中,有一个静态得密钥值,它对所有的请求都是相同的。要进行身份验证,用户必须在Authorization头信息中添加正确的静态密钥值。
首先要实现名为StaticKeyAuthenticationFilter的过滤器类。这个类从属性文件中读取静态密钥的值,并验证Authorization头信息的值是否与该值相等。如果值相同,则过滤器会将请求转发给过滤器链中的下一个组件。如果不相等,则过滤器会将值401 Unauthorized设置为响应的HTTP状态,而不转发过滤器链中的请求。
5.1 StaticKeyAuthenticationFilter类的定义
@Component
public class StaticKeyAuthenticationFilter implements Filter {
@Value("${authorization.key}")
private String authorizationKey;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
var httpRequest = (HttpServletRequest) request;
var httpResponse = (HttpServletResponse) response;
String authentication = httpRequest.getHeader("Authorization");
if (authorizationKey.equals(authentication)) {
filterChain.doFilter(request, response);
} else {
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}
一旦定义了过滤器,就可以使用addFilterAt()方法将其添加到过滤器链中BasicAuthenticationFilter类所在的位置。但请记住,在指定位置添加过滤器时,Spring Security并不会指定它是该位置上的唯一过滤器。可以在链中的相同位置添加更多的过滤器。在这种情况下,Spring Security不会保证这些操作的执行顺序。
提示:
建议不要在同一位置添加多个过滤器。当在同一位置添加更多的过滤器时,它们的使用顺序价格不会被定义。有一个明确的调用过滤器的顺序是有意义的。有一个已知的顺序可以使应用程序更易于理解和维护。
5.2 将过滤器添加到配置类中
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Autowired
private StaticKeyAuthenticationFilter filter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAt(filter,
BasicAuthenticationFilter.class)
.authorizeRequests()
.anyRequest()
.permitAll();
}
}
要测试该应用程序,还需要一个端点。为此,我们定义了一个控制器。
5.3 控制器
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello!";
}
}
5.4 application.properties配置文件
这里为服务器上的静态密钥添加一个值,如下所示:
5.5 测试
可以看到,正确调用。
现在故意将头信息乱写,再次调用,则响应状态为HTTP 401 Unauorized。
在这个示例中,由于没有配置UserDetailsService,因此SpringBoot会自动配置一个UserDetailsService,但在本示例中,根本不需要UserDetailsSerivice,因为用户的概念并不存在。其中只会验证调用服务器上缎带你的用户请求是否包含给定的值。应用程序场景通常没有这么简单,常常需要一个UserDetailsService。
6、总结
- Web应用程序架构的第一层会拦截HTTP请求,这是一个过滤器链。就像Spring Security架构中的其他组件一样,可以对其进行自定义以满足需求。
- 可以通过在现有过滤器之前、之后或在现有过滤器的当前位置添加新过滤器来自定义过滤器链。
- 在现有过滤器的相同位置上可以设置多个过滤器。在这种情况下,将不会定义过滤器执行的顺序。
- 更改过滤器链有助于自定义身份验证和授权,以精确地匹配应用程序的需求。
- 点赞
- 收藏
- 关注作者
评论(0)