我有一个自定义身份验证方案。我有一个REST端点,它在http uri路径中有userId
,在HTTP标头中有token
。我想检查这样的请求是否由具有有效令牌的有效用户执行。用户和令牌存储在mongo集合中。
我不知道我应该在哪个班级授权用户。
我的SecurityConfig
:
@EnableWebFluxSecurity
class SecurityConfig {
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
val build = http
.httpBasic().disable()
.formLogin().disable()
.csrf().disable()
.logout().disable()
.authenticationManager(CustomReactiveAuthenticationManager())
.securityContextRepository(CustomServerSecurityContextRepository())
.authorizeExchange().pathMatchers("/api/measurement/**").hasAuthority("ROLE_USER")
.anyExchange().permitAll().and()
return build.build()
}
@Bean
fun userDetailsService(): MapReactiveUserDetailsService {
val user = User.withDefaultPasswordEncoder()
.username("sampleDeviceIdV1")
.password("foo")
.roles("USER")
.build()
return MapReactiveUserDetailsService(user)
}
}
我的ServerSecurityContextRepository
:
class CustomServerSecurityContextRepository : ServerSecurityContextRepository {
override fun load(exchange: ServerWebExchange): Mono<SecurityContext> {
val authHeader = exchange.request.headers.getFirst(HttpHeaders.AUTHORIZATION)
val path = exchange.request.uri.path
return if (path.startsWith("/api/measurement/") && authHeader != null && authHeader.startsWith(prefix = "Bearer ")) {
val deviceId = path.drop(17)
val authToken = authHeader.drop(7)
val auth = UsernamePasswordAuthenticationToken(deviceId, authToken)
Mono.just(SecurityContextImpl(auth))
} else {
Mono.empty()
}
}
override fun save(exchange: ServerWebExchange?, context: SecurityContext?): Mono<Void> {
return Mono.empty()
}
}
出现两个问题:
ServerSecurityContextRepository
是从交易所获取用户名和令牌的好地方-还是有更好的地方呢?
我应该在哪里执行身份验证(对照mongo集合检查令牌和用户名)?
我的自定义AuthenticationManager
不会在任何地方被调用。我应该在ServerSecurityContextRepository
内执行所有操作还是在ReactiveAuthenticationManager
内执行用户和令牌验证?也许其他课程更合适?
答案 0 :(得分:2)
事实证明,网络上的某些教程是完全错误的。
我设法使用以下代码配置了所有内容:
class DeviceAuthenticationConverter : Function<ServerWebExchange, Mono<Authentication>> {
override fun apply(exchange: ServerWebExchange): Mono<Authentication> {
val authHeader: String? = exchange.request.headers.getFirst(HttpHeaders.AUTHORIZATION)
val path: String? = exchange.request.uri.path
return when {
isValidPath(path) && isValidHeader(authHeader) -> Mono.just(UsernamePasswordAuthenticationToken(path?.drop(17), authHeader?.drop(7)))
else -> Mono.empty()
}
}
private fun isValidPath(path: String?) = path != null && path.startsWith(API_MEASUREMENT)
private fun isValidHeader(authHeader: String?) = authHeader != null && authHeader.startsWith(prefix = "Bearer ")
}
和配置:
@EnableWebFluxSecurity
class SecurityConfig {
companion object {
const val API_MEASUREMENT = "/api/measurement/"
const val API_MEASUREMENT_PATH = "$API_MEASUREMENT**"
const val DEVICE = "DEVICE"
const val DEVICE_ID = "deviceId"
}
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity, authenticationManager: ReactiveAuthenticationManager) =
http
.httpBasic().disable()
.formLogin().disable()
.csrf().disable()
.logout().disable()
.authorizeExchange().pathMatchers(API_MEASUREMENT_PATH).hasRole(DEVICE)
.anyExchange().permitAll().and().addFilterAt(authenticationWebFilter(authenticationManager), AUTHENTICATION).build()
@Bean
fun userDetailsService(tokenRepository: TokenRepository) = MongoDeviceTokenReactiveUserDetailsService(tokenRepository)
@Bean
fun tokenRepository(template: ReactiveMongoTemplate, passwordEncoder: PasswordEncoder) = MongoTokenRepository(template, passwordEncoder)
@Bean
fun tokenFacade(tokenRepository: TokenRepository) = TokenFacade(tokenRepository)
@Bean
fun authManager(userDetailsService: ReactiveUserDetailsService) = UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService)
private fun authenticationWebFilter(reactiveAuthenticationManager: ReactiveAuthenticationManager) =
AuthenticationWebFilter(reactiveAuthenticationManager).apply {
setAuthenticationConverter(DeviceAuthenticationConverter())
setRequiresAuthenticationMatcher(
ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, API_MEASUREMENT_PATH)
)
}
@Bean
fun passwordEncoder() = PasswordEncoderFactories.createDelegatingPasswordEncoder()
}