自定义过滤器被调用两次
Custom filter gets called twice
我正在使用 Okta 在 Spring Boot REST(ful) API 资源服务器中处理身份验证和授权。我从他们提供的示例开始 here。为了处理用户,我实现了一个过滤器来存储一个实体(在我的关系数据库中),其中包含每个请求之前的 uid
声明,因为我的应用程序的每个路由都需要身份验证。这是过滤器的代码:
@Component
@RequiredArgsConstructor
public class AppUserRegistrationFilter extends OncePerRequestFilter {
private final AppUserService appUserService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
appUserService.checkAppUserRegistration(jwtAuthenticationToken);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
这是网页配置的代码class:
@Configuration
@RequiredArgsConstructor
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final AppUserService appUserService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.jwt();
http
.cors();
http
.addFilterAfter(new AppUserRegistrationFilter(appUserService), BearerTokenAuthenticationFilter.class);
Okta.configureResourceServer401ResponseBody(http);
}
}
AppUserRepository
没有什么特别的,它只是扩展 JpaRepository<AppUser, Long>
和 AppUser
是包含 uid 和电子邮件字符串以及几个用于注册和最后的 LocalDateTime 字段的实体访问时间。
最后,这是 AppUserService 实现方法的代码 class:
@Override
public void checkAppUserRegistration(JwtAuthenticationToken jwtAuthenticationToken) {
String uid = (String) jwtAuthenticationToken.getTokenAttributes().get("uid");
Optional<AppUser> appUserOptional = appUserRepository.findByUid(uid);
LocalDateTime now = LocalDateTime.now();
// By default, the name maps to the sub claim according to Spring Security docs
String email = jwtAuthenticationToken.getName();
if (appUserOptional.isEmpty()) {
AppUser newUser = AppUser.builder()
.uid(uid)
.email(email)
.registrationDateTime(now)
.lastAccessDateTime(now)
.build();
appUserRepository.save(newUser);
} else {
AppUser appUser = appUserOptional.get();
if (!appUser.getEmail().equals(email)) {
// User changed email address, update accordingly
appUser.setEmail(email);
}
appUser.setLastAccessDateTime(now);
appUserRepository.save(appUser);
}
}
如果我启动前端应用程序,即 this one,我使用从 Okta 开发人员仪表板创建的用户登录,然后检查“消息”部分,在后端我看到用户是正确创建但随后过滤器立即再次调用,如果我检查数据库,我发现注册日期和上次访问日期不同。
为什么过滤器被调用两次?这是正常行为吗?
Spring Boot 将自动注册任何扩展 Filter
到 Servlet 容器的 Spring bean。
参见Spring引导文档:
Registering Servlets, Filters, and Listeners as Spring Beans
Any Servlet
, Filter,
or servlet *Listener
instance that is a Spring bean is registered with the embedded container.
因为你的 AppUserRegistrationFilter
class 是用 @Component
注释的,Spring 会自动为 class 创建一个单例 bean,因此它是自动的-注册到 Servlet 容器。
当您调用 http.addFilterAfter(new AppUserRegistrationFilter(appUserService), BearerTokenAuthenticationFilter.class);
时,您正在手动创建 class 的另一个实例,并向 Servlet 容器手动注册过滤器。
因此您的代码同时注册了 class 的两个不同实例。
解决方法:不要调用addFilterAfter()
或删除@Component
注解.
我正在使用 Okta 在 Spring Boot REST(ful) API 资源服务器中处理身份验证和授权。我从他们提供的示例开始 here。为了处理用户,我实现了一个过滤器来存储一个实体(在我的关系数据库中),其中包含每个请求之前的 uid
声明,因为我的应用程序的每个路由都需要身份验证。这是过滤器的代码:
@Component
@RequiredArgsConstructor
public class AppUserRegistrationFilter extends OncePerRequestFilter {
private final AppUserService appUserService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
appUserService.checkAppUserRegistration(jwtAuthenticationToken);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
这是网页配置的代码class:
@Configuration
@RequiredArgsConstructor
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final AppUserService appUserService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.jwt();
http
.cors();
http
.addFilterAfter(new AppUserRegistrationFilter(appUserService), BearerTokenAuthenticationFilter.class);
Okta.configureResourceServer401ResponseBody(http);
}
}
AppUserRepository
没有什么特别的,它只是扩展 JpaRepository<AppUser, Long>
和 AppUser
是包含 uid 和电子邮件字符串以及几个用于注册和最后的 LocalDateTime 字段的实体访问时间。
最后,这是 AppUserService 实现方法的代码 class:
@Override
public void checkAppUserRegistration(JwtAuthenticationToken jwtAuthenticationToken) {
String uid = (String) jwtAuthenticationToken.getTokenAttributes().get("uid");
Optional<AppUser> appUserOptional = appUserRepository.findByUid(uid);
LocalDateTime now = LocalDateTime.now();
// By default, the name maps to the sub claim according to Spring Security docs
String email = jwtAuthenticationToken.getName();
if (appUserOptional.isEmpty()) {
AppUser newUser = AppUser.builder()
.uid(uid)
.email(email)
.registrationDateTime(now)
.lastAccessDateTime(now)
.build();
appUserRepository.save(newUser);
} else {
AppUser appUser = appUserOptional.get();
if (!appUser.getEmail().equals(email)) {
// User changed email address, update accordingly
appUser.setEmail(email);
}
appUser.setLastAccessDateTime(now);
appUserRepository.save(appUser);
}
}
如果我启动前端应用程序,即 this one,我使用从 Okta 开发人员仪表板创建的用户登录,然后检查“消息”部分,在后端我看到用户是正确创建但随后过滤器立即再次调用,如果我检查数据库,我发现注册日期和上次访问日期不同。
为什么过滤器被调用两次?这是正常行为吗?
Spring Boot 将自动注册任何扩展 Filter
到 Servlet 容器的 Spring bean。
参见Spring引导文档:
Registering Servlets, Filters, and Listeners as Spring Beans
Any
Servlet
,Filter,
or servlet*Listener
instance that is a Spring bean is registered with the embedded container.
因为你的 AppUserRegistrationFilter
class 是用 @Component
注释的,Spring 会自动为 class 创建一个单例 bean,因此它是自动的-注册到 Servlet 容器。
当您调用 http.addFilterAfter(new AppUserRegistrationFilter(appUserService), BearerTokenAuthenticationFilter.class);
时,您正在手动创建 class 的另一个实例,并向 Servlet 容器手动注册过滤器。
因此您的代码同时注册了 class 的两个不同实例。
解决方法:不要调用addFilterAfter()
或删除@Component
注解.