mirror of
https://github.com/spring-projects/spring-security.git
synced 2026-03-01 00:24:46 +00:00
Add Docs for Custom Jwt Principal Converters
Issue gh-6237 Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
This commit is contained in:
parent
c208410a91
commit
e30d9240c9
@ -1673,3 +1673,19 @@ NOTE: Spring isn't a cache provider, so you'll need to make sure to include the
|
||||
|
||||
NOTE: Whether it's socket or cache timeouts, you may instead want to work with Nimbus directly.
|
||||
To do so, remember that `NimbusJwtDecoder` ships with a constructor that takes Nimbus's `JWTProcessor`.
|
||||
|
||||
[[custom-principal]]
|
||||
== Customizing the Principal
|
||||
|
||||
Sometimes it can be helpful to look up a pre-existing user based on the JWTs subject or other identifier.
|
||||
|
||||
[[custom-user-details-service]]
|
||||
=== Wiring a UserDetailsService
|
||||
|
||||
For example, if your application has a `UserDetailsService` and you want the corresponding user in the resulting `Authentication`, you can create a class to adapt your `UserDetails` into an `OAuth2AuthenticatedPrincipal`:
|
||||
|
||||
include-code::./UserDetailsJwtPrincipalConverter[tag=custom-converter,indent=0]
|
||||
|
||||
And then apply your principal converter to a `JwtAuthenticationConverter` `@Bean` like so:
|
||||
|
||||
include-code::./UserDetailsJwtPrincipalConverterConfiguration[tag=configure-converter,indent=0]
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.customuserdetailsservice;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
// tag::custom-converter[]
|
||||
@Component
|
||||
public final class UserDetailsJwtPrincipalConverter implements Converter<Jwt, OAuth2AuthenticatedPrincipal> {
|
||||
|
||||
private final UserDetailsService users;
|
||||
|
||||
public UserDetailsJwtPrincipalConverter(UserDetailsService users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthenticatedPrincipal convert(Jwt jwt) {
|
||||
UserDetails user = this.users.loadUserByUsername(jwt.getSubject());
|
||||
return new JwtUser(jwt, user);
|
||||
}
|
||||
|
||||
private static final class JwtUser extends User implements OAuth2AuthenticatedPrincipal {
|
||||
|
||||
private final Jwt jwt;
|
||||
|
||||
private JwtUser(Jwt jwt, UserDetails user) {
|
||||
super(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
|
||||
user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
|
||||
this.jwt = jwt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.jwt.getSubject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return this.jwt.getClaims();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// end::custom-converter[]
|
||||
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.customuserdetailsservice;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
|
||||
@Configuration
|
||||
class UserDetailsJwtPrincipalConverterConfiguration {
|
||||
|
||||
// tag::configure-converter[]
|
||||
@Bean
|
||||
JwtAuthenticationConverter authenticationConverter(UserDetailsJwtPrincipalConverter principalConverter) {
|
||||
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
|
||||
converter.setJwtPrincipalConverter(principalConverter);
|
||||
return converter;
|
||||
}
|
||||
// end::configure-converter[]
|
||||
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.oauth2.resourceserver.customuserdetailsservice;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.TestJwts;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class UserDetailsJwtPrincipalConverterTests {
|
||||
|
||||
@Test
|
||||
void convertWhenUserFoundThenPrincipalIsUserDetails() {
|
||||
UserDetailsService users = (username) -> User.withDefaultPasswordEncoder()
|
||||
.username(username)
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
UserDetailsJwtPrincipalConverter principalConverter = new UserDetailsJwtPrincipalConverter(users);
|
||||
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
|
||||
converter.setJwtPrincipalConverter(principalConverter);
|
||||
Jwt jwt = TestJwts.jwt().subject("user").build();
|
||||
OAuth2AuthenticatedPrincipal principal = (OAuth2AuthenticatedPrincipal) converter.convert(jwt).getPrincipal();
|
||||
assertThat(principal.getName()).isEqualTo("user");
|
||||
assertThat(principal.getAttributes()).containsKey("sub");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.customuserdetailsservice
|
||||
|
||||
import org.springframework.core.convert.converter.Converter
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal
|
||||
import org.springframework.security.oauth2.jwt.Jwt
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
// tag::custom-converter[]
|
||||
@Component
|
||||
class UserDetailsJwtPrincipalConverter(private val users: UserDetailsService) : Converter<Jwt, OAuth2AuthenticatedPrincipal> {
|
||||
|
||||
override fun convert(jwt: Jwt): OAuth2AuthenticatedPrincipal {
|
||||
val user = users.loadUserByUsername(jwt.subject)
|
||||
return JwtUser(jwt, user)
|
||||
}
|
||||
|
||||
private class JwtUser(private val jwt: Jwt, user: UserDetails) :
|
||||
User(user.username, user.password, user.isEnabled, user.isAccountNonExpired, user.isCredentialsNonExpired, user.isAccountNonLocked, user.authorities),
|
||||
OAuth2AuthenticatedPrincipal {
|
||||
|
||||
override fun getName(): String = jwt.subject
|
||||
|
||||
override fun getAttributes(): Map<String, Any> = jwt.claims
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// end::custom-converter[]
|
||||
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.customuserdetailsservice
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
|
||||
|
||||
@Configuration
|
||||
open class UserDetailsJwtPrincipalConverterConfiguration {
|
||||
|
||||
// tag::configure-converter[]
|
||||
@Bean
|
||||
open fun authenticationConverter(principalConverter: UserDetailsJwtPrincipalConverter): JwtAuthenticationConverter {
|
||||
val converter = JwtAuthenticationConverter()
|
||||
converter.setJwtPrincipalConverter(principalConverter)
|
||||
return converter
|
||||
}
|
||||
// end::configure-converter[]
|
||||
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.oauth2.resourceserver.customuserdetailsservice
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal
|
||||
import org.springframework.security.oauth2.jwt.TestJwts
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
|
||||
|
||||
class UserDetailsJwtPrincipalConverterTests {
|
||||
|
||||
@Test
|
||||
fun convertWhenUserFoundThenPrincipalIsUserDetails() {
|
||||
@Suppress("DEPRECATION")
|
||||
val users = { username: String ->
|
||||
User.withDefaultPasswordEncoder()
|
||||
.username(username)
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
}
|
||||
val principalConverter = UserDetailsJwtPrincipalConverter(users)
|
||||
val converter = JwtAuthenticationConverter()
|
||||
converter.setJwtPrincipalConverter(principalConverter)
|
||||
val jwt = TestJwts.jwt().subject("user").build()
|
||||
val principal = converter.convert(jwt).principal as OAuth2AuthenticatedPrincipal
|
||||
assertThat(principal.name).isEqualTo("user")
|
||||
assertThat(principal.attributes).containsKey("sub")
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user