Spring Security — пользовательский участник из JWT

#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, а также добавить свою пользовательскую обработку утверждений сверху, а затем кэшировать результаты для последующих запросов с тем же токеном, что сложно.

МОЙ ПРИМЕР

У меня есть довольно полный образец, который ведет себя следующим образом, который может работать на вашем компьютере и из которого вы, возможно, можете позаимствовать некоторые идеи — хотя это довольно продвинутый образец: