java 如何在使用Spring Security和Auditing时提高JPA批处理插入的性能?

pinkon5k  于 2023-11-15  发布在  Java
关注(0)|答案(3)|浏览(92)

bounty将在14小时后过期。回答此问题可获得+100声望奖励。alexsaleh希望引起更多关注此问题。

由于@EnableJpaAuditing注解,自定义AuditorAware类被调用到getCurrentAuditor(),以填充审计字段lastCreatedBylastModifiedBy

@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()
在具有审计数据的同时具有高性能批处理插入的最佳方法是什么?在我的示例中,我处理的是来自同一用户的批量导入,因此所有记录都将具有相同的审计值。

ars1skjm

ars1skjm1#

我们有类似的问题,我记得最终我们使用二级缓存来提高性能,你也可以缓存这个方法的结果findByUsername
看看这些样本:
https://www.baeldung.com/spring-cache-tutorial
https://medium.com/@himani.prasad016/caching-in-hibernate-3ad4f479fcc0
Spring中的saveAll()只是在后台调用save()

public <S extends T> List<S> saveAll(Iterable<S> entities) {

    Assert.notNull(entities, "Entities must not be null");

    List<S> result = new ArrayList<>();

    for (S entity : entities) {
        result.add(save(entity));
    }

    return result;
}

字符串

hyrbngr7

hyrbngr72#

您可以实现CustomAuthenticationConverter(扩展org.springframework.core.convert.converter.Converter
将JWT token转换为
CustomAuthenticationToken(扩展org.springframework.security.authentication.AbstractAuthenticationToken
在此转换器中,您可以将数据库中的User信息放入CustomAuthenticationToken.details字段中。
CustomAuthenticationToken可通过以下方式访问:
(CustomAuthenticationToken) SecurityContextHolder.getContext().getAuthentication()
您可以在SecurityFilterChain配置中注册自定义转换器。

@RequiredArgsConstructor
public class CustomAuthenticationConverter implements Converter<Jwt, CustomAuthenticationToken> {

    private final UserRepository userRepository;

    @Override
    public CustomAuthenticationToken convert(final Jwt jwt) {
        //TODO: Convert JWT to Authentication object.
        User user = userRepository.findByUsername(username);
        CustomAuthenticationToken authentication = new CustomAuthenticationToken();
        authentication.setDetails(user);
        return authentication;
    }
}

@Component
public class AuditorAwareImpl implements AuditorAware<User> {

    @Override
    public Optional<User> getCurrentAuditor() {
        CustomAuthenticationToken authentication = (CustomAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        return Optional.of(authentication)
                .map(Authentication::getDetails)
                .map(User.class::cast);
    }
}

@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final UserRepository userRepository;

    @Bean
    protected SecurityFilterChain configure(final HttpSecurity httpSecurity) throws Exception {
        ...

        httpSecurity
            .jwtAuthenticationConverter(new CustomAuthenticationConverter(userRepository));
        return httpSecurity.build();

    }
}

字符串
这样您就不需要一直调用数据库,因为SecurityContext中有User信息。

uqdfh47h

uqdfh47h3#

据我所知,您正在使用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声明

相关问题