1 λΆ„ μ†Œμš”

1. Opaque κ°œλ… 및 API μ„€μ •

βœ“ κ°œλ…

  • Opaque 토큰은 인가 μ„œλ²„μ—μ„œ ν˜ΈμŠ€νŠΈν•˜λŠ” OAuth 2.0 Introspection μ—”λ“œν¬μΈνŠΈλ‘œ κ²€μ¦ν•œλ‹€.
  • Bearer 토큰이 λ¦¬μ†ŒμŠ€ μ„œλ²„μ—μ„œ μ²˜λ¦¬ν•˜λŠ” 자체 검증이라면 Opaque 토큰은 μΈκ°€μ„œλ²„μ—μ„œ μ²˜λ¦¬ν•˜λŠ” 원격 검증이라고 λ³Ό 수 μžˆλ‹€

βœ“ ν™˜κ²½ μ„€μ •

  • 두 κ°€μ§€ μ„€μ •λ§Œ ν•˜λ©΄ μΈκ°€μ„œλ²„μ™€μ˜ Instrospection 검증이 κ°€λŠ₯ν•˜λ‹€.

    β‘  ν•„μš”ν•œ μ˜μ‘΄μ„±μ„ μΆ”κ°€ν•œλ‹€

      runtimeOnly 'com.nimbusds:oauth2-oidc-sdk:9.35'
    

    β‘‘ introspection μ—”λ“œν¬μΈνŠΈ 상세 정보λ₯Ό μ„€μ •ν•œλ‹€

    spring:
      security:
          oauth2:
              resourceserver:
                  opaquetoken:
                      introspection-uri: http://localhost:8080/realms/oauth2/protocol/openid-connect/introspect
                      client-id: oauth2-client-app
                      client-secret: CQueEWXZYmv7IIZVxbvh2uwxptXVaRcX
    
    • http://localhost:8080/realms/oauth2/protocol/openid-connect/introspect λŠ” 인가 μ„œλ²„κ°€ ν˜ΈμŠ€νŠΈν•˜λŠ” introspection μ—”λ“œν¬μΈνŠΈμ΄λ‹€
    • client-id 와 client-secret 은 μ—”λ“œν¬μΈνŠΈ μš”μ²­μ— μ‚¬μš©ν•  ν΄λΌμ΄μ–ΈνŠΈ 자격증λͺ…이닀.

βœ“ μ„€μ • 클래슀

@Configuration(proxyBeanMethods = false)
static class OAuth2ResourceServerConfig {
    @Bean
    SecurityFilterChain jwtSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
        http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaque);
        return http.build();
    }
}

βœ“ OpaqueTokenConfigurer / μ΄ˆκΈ°ν™” κ³Όμ •



2. 토큰 검사 및 ν”„λ‘œμ„ΈμŠ€ 이해

βœ“ OpaqueTokenIntrospector

  • λ¬Έμžμ—΄ 토큰을 RestTemplate 을 μ‚¬μš©ν•˜μ—¬ μΈκ°€μ„œλ²„ μ—”λ“œν¬μΈνŠΈλ‘œ μš”μ²­ν•œλ‹€
  • 토큰이 κ²€μ¦λ˜λ©΄ μ΅œμ’… OAuth2AuthenticatedPrincipal νƒ€μž…μ˜ 객체둜 λ””μ½”λ”©ν•˜μ—¬ λ°˜ν™˜ν•œλ‹€.
  • OAuth2AuthenticatedPrincipal 은 BearerTokenAuthentication 의 principal 속성에 μ €μž₯λœλ‹€

βœ“ CustomOpaqueTokenIntrospector

  • OpaqueTokenIntrospector μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜μ—¬ μ»€μŠ€ν…€ν•œ κ΅¬ν˜„μ²΄λ₯Ό λ§Œλ“€μ–΄ μž¬μ •μ˜ ν•  수 μžˆλ‹€
  • 검증 ν›„ 리턴 νƒ€μž…μ΄ OAuth2AuthenticatedPrincipal 이기 λ•Œλ¬Έμ— μΈκ°€μ„œλ²„μ—μ„œ λ°›μ•„μ˜¨ ν΄λ ˆμž„ 정보λ₯Ό ν™œμš©ν•΄μ„œ μ—¬λŸ¬κ°€μ§€ μ»€μŠ€ν…€ν•œ μž‘μ—…μ΄ κ°€λŠ₯ν•˜λ‹€
http 
    .oauth2ResourceServer(oauth2 -> oauth2
        .opaqueToken(opaqueToken -> opaqueToken
            .introspector(customOpaqueTokenIntrospector())
        )
    );
@Bean
public OpaqueTokenIntrospector customOpaqueTokenIntrospector() {
    return new CustomOpaqueTokenIntrospector();
}
public class CustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
    private OpaqueTokenIntrospector delegate = 
        new NimbusOpaqueTokenIntrospector(" http://localhost:8080/realms/oauth2/protocol/openid-connect/introspect ", "client", "secret");

    public OAuth2AuthenticatedPrincipal introspect(String token) {
        OAuth2AuthenticatedPrincipal principal = this.delegate.introspect(token); // μΈκ°€μ„œλ²„λ‘œ λΆ€ν„° 받은 ν΄λ ˆμž„ 정보λ₯Ό μ €μž₯ν•˜κ³  μžˆλ‹€
        
        return new DefaultOAuth2AuthenticatedPrincipal(
            principal.getName(), principal.getAttributes(), extractAuthorities(principal)); // μ»€μŠ€ν…€ν•˜κ²Œ κΆŒν•œ λ§€ν•‘ ν•œλ‹€
    }

    private Collection<GrantedAuthority> extractAuthorities(OAuth2AuthenticatedPrincipal principal) {
        List<String> scopes = principal.getAttribute(OAuth2IntrospectionClaimNames.SCOPE);

        return scopes.stream()
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());
    }
}

μΉ΄ν…Œκ³ λ¦¬:

μ—…λ°μ΄νŠΈ:

λŒ“κΈ€λ‚¨κΈ°κΈ°