Данные Spring Boot JPA @CreatedBy и @UpdatedBy не заполнены аутентификацией с помощью OIDC - PullRequest
0 голосов
/ 20 октября 2018

Я бы хотел, чтобы Spring JPA проводил аудит для работы с Spring Boot, я аутентифицируюсь с Keycloak с использованием новейшей функции Spring Security.

springBootVersion = '2.1.0.RC1'

Я следую примеру группы безопасности Spring https://github.com/jzheaux/messaging-app/tree/springone2018-demo/resource-server

ResourceServerConfig.kt

@EnableWebSecurity
class OAuth2ResourceServerSecurityConfiguration(val resourceServerProperties: OAuth2ResourceServerProperties) : WebSecurityConfigurerAdapter() {

    @Throws(Exception::class)
    override fun configure(http: HttpSecurity) {
        http
                .authorizeRequests()
                .antMatchers("/api/**").authenticated()
                .anyRequest().anonymous()
                .and()
                .oauth2ResourceServer()
                .authenticationEntryPoint(MoreInformativeAuthenticationEntryPoint())
                .jwt()
                .jwtAuthenticationConverter(GrantedAuthoritiesExtractor())
                .decoder(jwtDecoder())

    }

    private fun jwtDecoder(): JwtDecoder {
        val issuerUri = this.resourceServerProperties.jwt.issuerUri

        val jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuerUri) as NimbusJwtDecoderJwkSupport

        val withIssuer = JwtValidators.createDefaultWithIssuer(issuerUri)
        val withAudience = DelegatingOAuth2TokenValidator(withIssuer, AudienceValidator())
        jwtDecoder.setJwtValidator(withAudience)

        return jwtDecoder
    }
}

class MoreInformativeAuthenticationEntryPoint : AuthenticationEntryPoint {
    private val delegate = BearerTokenAuthenticationEntryPoint()

    private val mapper = ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL)

    @Throws(IOException::class, ServletException::class)
    override fun commence(request: HttpServletRequest, response: HttpServletResponse,
                          reason: AuthenticationException) {

        this.delegate.commence(request, response, reason)

        if (reason.cause is JwtValidationException) {
            val validationException = reason.cause as JwtValidationException
            val errors = validationException.errors
            this.mapper.writeValue(response.writer, errors)
        }
    }
}

class GrantedAuthoritiesExtractor : JwtAuthenticationConverter() {
    override fun extractAuthorities(jwt: Jwt): Collection<GrantedAuthority> {
        val scopes = jwt.claims["scope"].toString().split(" ")
        return scopes.map { SimpleGrantedAuthority(it) }
    }
}

class AudienceValidator : OAuth2TokenValidator<Jwt> {

    override fun validate(token: Jwt): OAuth2TokenValidatorResult {
        val audience = token.audience
        return if (!CollectionUtils.isEmpty(audience) && audience.contains("mobile-client")) {
            OAuth2TokenValidatorResult.success()
        } else {
            OAuth2TokenValidatorResult.failure(MISSING_AUDIENCE)
        }
    }

    companion object {
        private val MISSING_AUDIENCE = BearerTokenError("invalid_token", HttpStatus.UNAUTHORIZED,
                "The token is missing a required audience.", null)
    }
}

application.yaml

spring:
  application:
    name: sociter
  datasource:
    url: jdbc:postgresql://localhost:5432/sociter
    username: postgres
    password: 123123
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: update
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://localhost:8080/auth/realms/sociter/protocol/openid-connect/certs
          issuer-uri: http://localhost:8080/auth/realms/sociter

JpaAuditingConfiguration.kt

@Configuration
@EnableJpaAuditing
(auditorAwareRef = "auditorProvider")
class JpaAuditingConfiguration {

    @Bean
    fun auditorProvider(): AuditorAware<String> {
        return if (SecurityContextHolder.getContext().authentication != null) {
            val oauth2 = SecurityContextHolder.getContext().authentication as JwtAuthenticationToken
        val claims = oauth2.token.claims
        val userId = claims["sub"]
        AuditorAware { Optional.of(userId.toString()) }
        } else
            AuditorAware { Optional.of("Unknown") }
    }
}

BaseEntity.kt

@MappedSuperclass
@JsonIgnoreProperties(value = ["createdOn, updatedOn"], allowGetters = true)
@EntityListeners(AuditingEntityListener::class)
abstract class BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    val id: UUID = UUID.randomUUID()

    @Column(nullable = false, updatable = false)
    @CreatedDate
    var createdOn: LocalDateTime = LocalDateTime.now()

    @Column(nullable = true)
    @LastModifiedDate
    var updatedOn: LocalDateTime? = null

    @Column(nullable = true, updatable = false)
    @CreatedBy
    var createdBy: String? = null

    @Column(nullable = true)
    @LastModifiedBy
    var updatedBy: String? = null
}

Я получаю для createBy и updatedBy значение Неизвестно.Во время отладки bean-компонент auditorProvider вызывается и устанавливает для пользователя значение Unknown, но при передаче access_token, если условие все еще ложно.

Не уверен, что мне не хватает.

Ответы [ 2 ]

0 голосов
/ 31 октября 2018

Ответ Аруна Патры выше работает правильно для Java.Мне пришлось сделать следующий путь с Kotlin.

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
class JpaAuditingConfiguration {

    @Bean
    fun auditorProvider(): AuditorAware<String> {
        return CustomAuditorAware()
    }
}

private class CustomAuditorAware : AuditorAware<String> {
    override fun getCurrentAuditor(): Optional<String> {
        return if (SecurityContextHolder.getContext().authentication != null) {
            val oauth2 = SecurityContextHolder.getContext().authentication as JwtAuthenticationToken
            val loggedInUserId = oauth2.token.claims["sub"].toString()
            Optional.of(loggedInUserId)
        } else {
            Optional.of("Unknown")
        }
    }
}
0 голосов
/ 29 октября 2018

Мне удалось воспроизвести вашу проблему, но в эквивалентной настройке Java.Проблема в вашем JpaAuditingConfiguration классе.Если вы внимательно наблюдаете за своим текущим классом JpaAuditingConfiguration, то вот что происходит:

  1. Во время инициализации Spring функция auditorProvider() попытается сгенерировать бин.
  2. Условие аутентификациипроверяется там заранее (во время запуска приложения), и этот поток (который запускает приложение Spring Boot) вовсе НЕ является аутентифицированным потоком.Следовательно, он возвращает AuditorAware экземпляр, который всегда будет возвращать Unknown.

Вам нужно изменить этот класс следующим образом (извините, я написал его на Java, пожалуйста, преобразуйте его в Kotlin):

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class JPAAuditConfig {

    @Bean
    public AuditorAware<String> auditorProvider() {
        return new AuditorAware<String>() {
            @Override
            public String getCurrentAuditor() {
                if (SecurityContextHolder.getContext().getAuthentication() != null) {
                    OAuth2Authentication auth = (OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication();
                    Object principal = auth.getUserAuthentication().getPrincipal();
                    CustomUserDetails userDetails = (CustomUserDetails) principal;
                    return userDetails.getUsername();
                } else {
                    return "Unknown";
                }
            }
        };
    }
}

Вы можете попробовать это.Кроме того, я подозреваю, что с вашей текущей настройкой вы будете обновлены и созданы правильно.Если да, это означает, что вся магия JPA и EntityListener фактически работает.Вам просто нужно вернуть правильную реализацию AuditorAware во время выполнения.

Также обратите внимание, что моя конфигурация не использует JwtAuthenticationToken, и я использую реализацию CustomUserDetails.Но это не связано с вашей проблемой, и вы, конечно, можете использовать свой текущий тип токена (JwtAuthenticationToken).Просто у меня было запущено и запущено собственное маленькое приложение, в котором я повторил вашу проблему.

...