스프링 보안: OAuth2 클레임을 역할과 매핑하여 리소스 서버 엔드포인트 보호
Spring Boot을 사용하여 리소스 서버를 설정하고 Spring Security에서 제공하는 OAuth2를 사용하는 엔드포인트를 보호합니다.Spring Boot을 사용하고 있습니다.2.1.8.RELEASE
예를 들어 Spring Security를 사용합니다.5.1.6.RELEASE
.
인증 서버로서 Keyclock을 사용하고 있습니다.리소스 서버에서 인증, 액세스 토큰 발급 및 토큰 유효성 검사 사이의 모든 프로세스가 올바르게 작동합니다.다음으로 발행된 토큰과 디코딩된 토큰(일부 부분이 절단된 토큰)의 예를 나타냅니다.
{
"jti": "5df54cac-8b06-4d36-b642-186bbd647fbf",
"exp": 1570048999,
"aud": [
"myservice",
"account"
],
"azp": "myservice",
"realm_access": {
"roles": [
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"myservice": {
"roles": [
"ROLE_user",
"ROLE_admin"
]
},
"account": {
"roles": [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
"scope": "openid email offline_access microprofile-jwt profile address phone",
}
액세스 토큰 내의 정보를 사용하여 다른 엔드포인트에 조건부 허가를 제공하도록 Spring Security를 설정하려면 어떻게 해야 합니까?
최종적으로 다음과 같은 컨트롤러를 작성합니다.
@RestController
public class Controller {
@Secured("ROLE_user")
@GetMapping("userinfo")
public String userinfo() {
return "not too sensitive action";
}
@Secured("ROLE_admin")
@GetMapping("administration")
public String administration() {
return "TOOOO sensitive action";
}
}
좀 더 시간을 들여 본 결과, 커스텀을 구현한 솔루션을 찾을 수 있었습니다.jwtAuthenticationConverter
권한 컬렉션에 리소스 고유의 역할을 추가할 수 있습니다.
http.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(new JwtAuthenticationConverter()
{
@Override
protected Collection<GrantedAuthority> extractAuthorities(final Jwt jwt)
{
Collection<GrantedAuthority> authorities = super.extractAuthorities(jwt);
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
Map<String, Object> resource = null;
Collection<String> resourceRoles = null;
if (resourceAccess != null &&
(resource = (Map<String, Object>) resourceAccess.get("my-resource-id")) !=
null && (resourceRoles = (Collection<String>) resource.get("roles")) != null)
authorities.addAll(resourceRoles.stream()
.map(x -> new SimpleGrantedAuthority("ROLE_" + x))
.collect(Collectors.toSet()));
return authorities;
}
});
여기서 my-resource-id는 resource_access 클레임에 표시되는 리소스 ID와 ResourceServerSecurityConfigr의 API와 관련된 값입니다.
주의해 주세요extractAuthorities
실제로는 권장되지 않기 때문에 보다 미래 대비적인 솔루션은 완전한 컨버터를 구현하는 것입니다.
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken>
{
private static Collection<? extends GrantedAuthority> extractResourceRoles(final Jwt jwt, final String resourceId)
{
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
Map<String, Object> resource;
Collection<String> resourceRoles;
if (resourceAccess != null && (resource = (Map<String, Object>) resourceAccess.get(resourceId)) != null &&
(resourceRoles = (Collection<String>) resource.get("roles")) != null)
return resourceRoles.stream()
.map(x -> new SimpleGrantedAuthority("ROLE_" + x))
.collect(Collectors.toSet());
return Collections.emptySet();
}
private final JwtGrantedAuthoritiesConverter defaultGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
private final String resourceId;
public CustomJwtAuthenticationConverter(String resourceId)
{
this.resourceId = resourceId;
}
@Override
public AbstractAuthenticationToken convert(final Jwt source)
{
Collection<GrantedAuthority> authorities = Stream.concat(defaultGrantedAuthoritiesConverter.convert(source)
.stream(),
extractResourceRoles(source, resourceId).stream())
.collect(Collectors.toSet());
return new JwtAuthenticationToken(source, authorities);
}
}
Spring Boot 2.1.9를 사용하여 두 솔루션을 모두 테스트했습니다.릴리스, 스프링 보안 5.2.0.릴리스 및 Keycloak 7.0.0 도커 공식 이미지.
일반적으로 말하면, 실제 인가 서버가 무엇이든(즉,IdentityServer4, Keycloak...) 클레임을 Spring Security 조성금으로 전환하는 것이 적절한 장소인 것 같습니다.
여기 또 다른 해결책이 있습니다.
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
}
문제가 발생하는 것은 JWT의 resource_server->client_id 아래에 역할이 배치되어 있기 때문입니다.그런 다음 이를 추출하려면 사용자 지정 토큰 변환기가 필요합니다.
롤과 같은 최상위 클레임 이름으로 역할을 표시하는 클라이언트 매퍼를 사용하도록 keyclock을 구성할 수 있습니다.JwtGranted만 필요하므로 Spring Security 구성이 간단해집니다.Authorities Converter with authoritiesClaimName은 @hillel_guy가 채택한 접근방식으로 설정되어 있습니다.
Keyclock 클라이언트매퍼는 다음과 같이 설정됩니다.
@했듯이 @hille_guy를 사용하여AbstractHttpConfigurer
가야 할 길이에요.이것은 스프링 부트 2.3.4와 스프링 보안 5.3.4에서 심리스하게 동작했습니다.다음 OAuth2ResourceServerConfigr에 대해서는 spring-security API 매뉴얼을 참조하십시오.
갱신하다
코멘트에 기재되어 있는 완전한 예:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String JWT_ROLE_NAME = "roles";
private static final String ROLE_PREFIX = "ROLES_";
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.and().csrf().disable()
.cors()
.and().oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
}
private JwtAuthenticationConverter jwtAuthenticationConverter() {
// create a custom JWT converter to map the roles from the token as granted authorities
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(JWT_ROLE_NAME); // default is: scope, scp
jwtGrantedAuthoritiesConverter.setAuthorityPrefix(ROLE_PREFIX ); // default is: SCOPE_
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
}
제작하고 싶었어요roles
가 scope
.
이게 도움이 됐으면 좋겠다.
2022년 갱신
리소스 서버 보안을 구성하는 일련의 튜토리얼과 샘플을 관리하고 있습니다.
- 서블릿 어플리케이션과 리액티브어플리케이션 모두
- 디코딩, JWT 및 인스펙티브액세스 토큰
- 커스텀 " " " " " "
Authentication
(예: - 물론 Keyclock을 포함한 임의의 OIDC 인가 서버(대부분의 샘플은 복수의 레름/아이덴티티 프로바이더를 서포트하고 있습니다.
또한 레포에는 maven-central에 게시된 libs 집합이 포함되어 있습니다.
- 유닛 및 통합 테스트 중 OAuth2 ID 모의(당국 및 오픈)ID 클레임(개인 클레임
- 속성 파일에서 리소스 서버 구성(역할, 역할 접두사 및 사례 처리, CORS 구성, 세션 관리, 퍼블릭 루트 등에 대한 소스 클레임 포함)
JWT 디코더가 있는 서블릿 샘플
@EnableMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig {}
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/master
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,resource_access.spring-addons-public.roles,resource_access.spring-addons-confidential.roles
com.c4-soft.springaddons.security.cors[0].path=/sample/**
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.3</version>
</dependency>
아뇨, 더 이상 필요 없습니다.
모의 인증을 사용한 유닛 테스트
★★★@Component
를 사용하지 않음(「http request」)@Service
,@Repository
의 개요)
@Import({ SecurityConfig.class, SecretRepo.class })
@AutoConfigureAddonsSecurity
class SecretRepoTest {
// auto-wire tested component
@Autowired
SecretRepo secretRepo;
@Test
void whenNotAuthenticatedThenThrows() {
// call tested components methods directly (do not use MockMvc nor WebTestClient)
assertThrows(Exception.class, () -> secretRepo.findSecretByUsername("ch4mpy"));
}
@Test
@WithMockJwtAuth(claims = @OpenIdClaims(preferredUsername = "Tonton Pirate"))
void whenAuthenticatedAsSomeoneElseThenThrows() {
assertThrows(Exception.class, () -> secretRepo.findSecretByUsername("ch4mpy"));
}
@Test
@WithMockJwtAuth(claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
void whenAuthenticatedWithSameUsernameThenReturns() {
assertEquals("Don't ever tell it", secretRepo.findSecretByUsername("ch4mpy"));
}
}
★★★@Controller
:@WebMvcTest
ᄂ, ᄂ, ᄂ, ᄂ, ᄂ, ᄂ, ᄂ, ᄂ, ᄂ, ᄂ, ᄂ, ᄂ@WebfluxTest
대화)
@WebMvcTest(GreetingController.class) // Use WebFluxTest or WebMvcTest
@AutoConfigureAddonsWebSecurity // If your web-security depends on it, setup spring-addons security
@Import({ SecurityConfig.class }) // Import your web-security configuration
class GreetingControllerAnnotatedTest {
// Mock controller injected dependencies
@MockBean
private MessageService messageService;
@Autowired
MockMvcSupport api;
@BeforeEach
public void setUp() {
when(messageService.greet(any())).thenAnswer(invocation -> {
final JwtAuthenticationToken auth = invocation.getArgument(0, JwtAuthenticationToken.class);
return String.format("Hello %s! You are granted with %s.", auth.getName(), auth.getAuthorities());
});
when(messageService.getSecret()).thenReturn("Secret message");
}
@Test
void greetWitoutAuthentication() throws Exception {
api.get("/greet").andExpect(status().isUnauthorized());
}
@Test
@WithMockAuthentication(authType = JwtAuthenticationToken.class, principalType = Jwt.class, authorities = "ROLE_AUTHORIZED_PERSONNEL")
void greetWithDefaultMockAuthentication() throws Exception {
api.get("/greet").andExpect(content().string("Hello user! You are granted with [ROLE_AUTHORIZED_PERSONNEL]."));
}
}
고도의 사용 사례
합니다.Authentication
구현: 프라이빗 클레임을 보안과 관련되지만 역할이 아닌 것으로 해석(Java 코드에 노출)합니다(예에서는 사용자 간에 위임 부여).
또한 스프링 보안 SpEL을 확장하여 다음과 같은 DSL을 구축하는 방법도 보여 줍니다.
@GetMapping("greet/on-behalf-of/{username}")
@PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')")
public String getGreetingFor(@PathVariable("username") String username) {
return ...;
}
Azure AD 선서를 사용하는 경우, 보다 쉬운 방법이 있습니다.
http
.cors()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(new AADJwtBearerTokenAuthenticationConverter("roles", "ROLE_"));
ADDJwtBearerTokenAuthenticationConverter를 사용하면 첫 번째 인수로 클레임 이름을 추가하고 두 번째 인수로 역할 접두사를 추가할 수 있습니다.
라이브러리를 찾을 수 있도록 가져오기:
import com.azure.spring.aad.webapi.AADJwtBearerTokenAuthenticationConverter;
언급URL : https://stackoverflow.com/questions/58205510/spring-security-mapping-oauth2-claims-with-roles-to-secure-resource-server-endp
'source' 카테고리의 다른 글
Spring Boot에서 사용하는 데이터베이스 스키마 변경 (0) | 2023.03.12 |
---|---|
봄 + 휴지 상태:계획 캐시 메모리 사용량 쿼리 (0) | 2023.03.12 |
Oracle에서 모든 사용자 테이블/시퀀스 삭제 (0) | 2023.03.12 |
Swift 4의 JSONDecoder를 사용하면 키가 누락된 경우 옵션 속성 대신 기본값을 사용할 수 있습니까? (0) | 2023.03.12 |
위치 "/"의 일치하는 리프 경로에 요소가 없습니다. (0) | 2023.03.12 |