#spring #spring-security #oauth-2.0 #jwt #principal
#spring #spring-безопасность #oauth-2.0 #jwt #участник
Вопрос:
Я использую oauth2 и единый вход в моем приложении angular. Мой серверный сервер rest проверяет токен аутентификации, который отправляется по каждому запросу. Теперь я хочу использовать утверждение oid для загрузки пользователя из моей базы данных и сохранения его в принципале. Также я хочу добавить полномочия пользователя в «GrantedAuthorities».
FooService
[...]
public Foo getFooByOid(String oid) throws FooNotFoundException {
return fooRepository.findByOid(oid)
.orElseThrow(() -> new FooNotFoundException("Foo with oid: " oid " not found"));
}
SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(auth -> auth
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> {
oauth2.jwt();
});
}
}
Spring.security.oauth2.resourceserver.jwt.jwk-set-uri и spring.security.oauth2.resourceserver.jwt.issuer-uri определены в application.properties
После этого мой принципал содержит все утверждения, заголовки и токен:
Может ли кто-нибудь помочь мне, как это сделать? Спасибо
Ответ №1:
Ответ Гэри Арчера правильный, но в нем есть некоторые подводные камни.
Полный простой пример в Kotlin:
Пользовательский интерфейс, специфичный для вашего приложения:
interface User {
val id : String
val email : String
val displayName : String
val avatar : String
val roles : Set<String>
val domain : String
get() = this.email.split('@').last()
}
Участник должен реализовать этот интерфейс:
class ClaimsNotFull(claim: String) : Exception("Claims not full, not found $claim")
data class IdelPrincipal(
override val id: String,
override val email: String,
override val displayName: String,
override val avatar: String,
override val roles: Set<String>,
) : User {
companion object {
fun fromJwt(jwt: Jwt): IdelPrincipal {
fun loadClaim(key: String): String = jwt.claims.getOrElse(key) {throw ClaimsNotFull(key)} as String
return IdelPrincipal(
id = jwt.subject,
email = loadClaim("email"),
displayName = loadClaim("displayName"),
avatar = loadClaim("avatar"),
roles = loadClaim("roles").split(",").toSet()
)
}
fun copyToClaims(user : User, jwt: JWTClaimsSet.Builder) : JWTClaimsSet.Builder {
return jwt.subject(user.id)
.claim("email", user.email)
.claim("displayName",user.displayName)
.claim("avatar",user.avatar)
.claim("roles",user.roles.joinToString(","))
}
}
}
Пользовательский токен и JwtConvertor:
class IdelAuthenticationToken(val jwt: Jwt, val user: IdelPrincipal) :
AbstractAuthenticationToken(IdelAuthorities.from(user.roles)) {
override fun getCredentials() = jwt // borrowed from JwtAuthenticationToken
override fun getPrincipal() = user
override fun isAuthenticated() = true // decoding of jwt is authentication
}
class IdelPrincipalJwtConvertor : Converter<Jwt, IdelAuthenticationToken> {
override fun convert(jwt: Jwt): IdelAuthenticationToken {
val principal = IdelPrincipal.fromJwt(jwt)
return IdelAuthenticationToken(jwt,principal)
}
}
Настройка Spring Security:
override fun configure(http: HttpSecurity) {
http.authorizeRequests {
it.anyRequest().authenticated()
}
.csrf{it.ignoringAntMatchers("/token")}
.oauth2ResourceServer {it.jwt()}
.oauth2ResourceServer().jwt {cstm ->
cstm.jwtAuthenticationConverter(IdelPrincipalJwtConvertor())
}
http.exceptionHandling {
it
.authenticationEntryPoint(BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(BearerTokenAccessDeniedHandler())
}
http .sessionManagement {it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)}
http.anonymous().disable()
}
И метод в вашем контроллере для тестирования:
@GetMapping("/me2")
fun me2(@AuthenticationPrincipal user : User) : User {
val result = MeResult(
id = user.id,
domain = user.domain,
displayName = user.displayName,
avatar = user.avatar,
email = user.email,
authorities = user.roles.toList()
)
return user
}
Это возврат:
{
"id": "10777",
"email": "leonid.vygovsky@gmail.com",
"displayName": "Leonid Vygovskiy",
"avatar": "",
"roles": [
"ROLE_USER"
],
"domain": "gmail.com"
}
Комментарии:
1. Это правильно, поскольку позволяет избежать повторной проверки jwt самостоятельно
Ответ №2:
Вы можете сделать это с помощью пользовательского AuthenticationManager, если вы пишете такой код:
.oauth2ResourceServer.authenticationManagerResolver(request -> new CustomAuthenticationManager(request));
Свойства, которые вы устанавливаете на oauth2ResourceServer, влияют на поведение класса BearerTokenAuthenticationFilter Spring
Затем вам нужно будет самостоятельно проверить JWT, а также добавить свою пользовательскую обработку утверждений сверху, а затем кэшировать результаты для последующих запросов с тем же токеном, что сложно.
МОЙ ПРИМЕР
У меня есть довольно полный образец, который ведет себя следующим образом, который может работать на вашем компьютере и из которого вы, возможно, можете позаимствовать некоторые идеи — хотя это довольно продвинутый образец: