由于
@EnableJpaAuditing
注释,自定义 AuditorAware
类将被调用到 getCurrentAuditor()
,以便填充审核字段 lastCreatedBy
和 lastModifiedBy
。
@Component
@RequiredArgsConstructor
public class AuditorAwareImpl implements AuditorAware<User> {
private final UserRepository userRepository;
@Override
public Optional<User> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return Optional.of(authentication)
.map(Authentication::getName)
.flatMap(userRepository::findByUsername);
}
}
此类需要调用存储库才能获取该信息(从安全上下文中提取用户名/主体并获取用户)。
这在一次保存一个实体的情况下工作得很好,但是当使用
saveAll()
进行批量插入时,该操作会变成一个循环,以便为每个实体/行调用 getCurrentAuditor()
。
在拥有审计数据的同时实现高性能批量插入的最佳方法是什么?就我而言,我正在处理来自同一用户的批量导入,因此所有记录都将具有相同的审核值。
据我了解,您正在使用 Spring Security 资源服务器,因此您可以使用自定义声明转换器 在我的项目中我使用了以下配置
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(exchanges ->
exchanges
.requestMatchers("/v3/api-docs/**",
"/v3/api-docs.yaml",
"/swagger-ui/**",
"/swagger-ui.html")
.permitAll()
.requestMatchers("/protected/**")
.authenticated())
.csrf(httpSecurityCsrfConfigurer -> {
httpSecurityCsrfConfigurer.disable();
})
.oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer.jwt(jwtConfigurer -> {
jwtConfigurer.decoder(jwtDecoder());
}))
.headers(headerSpec -> {
headerSpec.contentSecurityPolicy(contentSecurityPolicySpec -> {
contentSecurityPolicySpec.policyDirectives(
"script-src 'strict-dynamic' 'nonce-rAnd0m123' 'unsafe-inline' http: https:;" +
"default-src 'self'; "+
"object-src 'none';" +
"base-uri 'none';" +
"require-trusted-types-for 'script';"
);
}).xssProtection(xssProtectionSpec -> {
/*According to this https://docs.spring.io/spring-security/reference/features/exploits/headers.html#headers-xss-protection
and this https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#x-xss-protection
we need to disable X-XSS-Protection and give precedence to CSP https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#content-security-policy-csp
*/
xssProtectionSpec.headerValue(XXssProtectionHeaderWriter.HeaderValue.DISABLED);
});
})
.build();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// @Bean
public JwtDecoder jwtDecoder() {
try {
NimbusJwtDecoder decoder = JwtDecoders.fromOidcIssuerLocation(env.getProperty("issuer-uri"));
decoder.setClaimSetConverter(new OauthSubClaimAdapter());
return decoder;
} catch (Exception e) {
throw new IllegalStateException("Impossibile proseguire. Avvenuto errore nella creazione del jwtDecoder",
e);
}
}
虽然我的 OauthSubClaimAdapter 是:
public class OauthSubClaimAdapter implements Converter<Map<String, Object>, Map<String, Object>> {
public static final String LOGGED_USER_INFO = "loggedUserInfo";
private final MappedJwtClaimSetConverter delegate = MappedJwtClaimSetConverter.withDefaults(
Collections.emptyMap());
@Override
public Map<String, Object> convert(Map<String, Object> claims) {
Map<String, Object> convertedClaims = this.delegate.convert(claims);
if (null == convertedClaims) {
throw new IllegalStateException(
String.format("Impossible continue; no converted claim found in %s",
getClass().getName()));
}
String username = (String) convertedClaims.get("sub");
//Here you can put your logic in order to enrich the user recovered from the JWT token
convertedClaims.put(LOGGED_USER_INFO, enrichedUser);
return convertedClaims;
}
}
在您的身份验证对象中,您现在可以检索所有还包含 LOGGED_USER_INFO 对象的 JWT 声明