1. api-client , resource server
https://www.baeldung.com/spring-security-oauth-auth-server
baeldung gihub
https://github.com/Baeldung/spring-security-oauth/tree/master/oauth-authorization-server
2. api-client server 소스분석
2.1 yml 설정
server:
port: 8080
spring:
security:
oauth2:
client:
registration:
articles-client-oidc: #클라이언트 인증
provider: spring
client-id: articles-client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: "http://127.0.0.1:8080/login/oauth2/code/{registrationId}"
scope: openid
client-name: articles-client-oidc
articles-client-authorization-code: #클라이언트 코드로 액세스 토큰 요청시 사용
provider: spring
client-id: articles-client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: "http://127.0.0.1:8080/authorized"
scope: articles.read
client-name: articles-client-authorization-code
provider:
spring:
issuer-uri: http://auth-server:9000
2.1.1 요청 응답 흐름 (시퀀스)
- 사용자 로그인 요청: 사용자가 애플리케이션에 로그인을 시도합니다.
-
리다이렉션: 클라이언트 애플리케이션은 사용자를 인증 서버의 로그인 페이지로 리다이렉션합니다. 이 때
client-id
,redirect-uri
,scope
등의 정보가 포함됩니다. 이 정보는registration
섹션에서 정의됩니다. -
사용자 인증: 사용자는 인증 서버에서 로그인을 진행합니다. 이 과정에서
issuer-uri
(인증 서버 주소)가 사용됩니다. -
인증 코드 발급: 로그인이 성공하면 인증 서버는
redirect-uri
로 사용자를 다시 리다이렉션하며, 이 때 인증 코드를 함께 전달합니다. -
인증 코드를 토큰으로 교환: 클라이언트 애플리케이션은 받은 인증 코드를 사용하여 인증 서버에 토큰을 요청합니다. 여기서
client-id
및client-secret
이 사용됩니다. - 토큰 응답: 인증 서버는 클라이언트에게 액세스 토큰(및 필요시 리프레시 토큰)을 발급합니다.
-
리소스 서버 접근: 클라이언트는 받은 토큰을 사용하여 보호된 리소스에 액세스합니다. 예를 들어,
scope: articles.read
를 사용하여 특정 API에 접근할 수 있습니다.
2.1.2 설정 파일 내의 역할
-
client-id
및client-secret
: 클라이언트의 신원을 인증 서버에 증명하는데 사용됩니다. -
redirect-uri
: 사용자가 인증 후 리다이렉션될 URI입니다. 인증 코드를 받는 데 사용됩니다. -
scope
: 클라이언트가 요청할 수 있는 권한의 범위를 정의합니다. -
issuer-uri
: 인증 서버의 주소입니다. 사용자 인증 및 토큰 발급에 사용됩니다.
2.1.3 상세설명
-
articles-client-oidc
: 최초 클라이언트 인증- 이 설정은 사용자가 최초로 로그인할 때 사용됩니다.
- 사용자가 애플리케이션에 로그인을 요청하면, 애플리케이션은 사용자를 OAuth 2.0 인증 서버의 로그인 페이지로 리다이렉션합니다.
-
articles-client-oidc
설정에 포함된client-id
,client-secret
,redirect-uri
,scope
등의 정보가 이 과정에서 사용됩니다. - 사용자가 인증 서버에서 성공적으로 인증을 완료하면, 인증 서버는
redirect-uri
로 사용자를 리디렉션하고 인증 코드를 제공합니다.
-
articles-client-authorization-code
: 리소스 서버에 대한 요청- 인증 코드를 받은 후, 애플리케이션은 이 코드를 사용하여 인증 서버에 액세스 토큰을 요청합니다. 이 때
articles-client-authorization-code
설정이 사용됩니다. -
client-id
,client-secret
는 이 과정에서 토큰 교환을 위해 사용되며,redirect-uri
는 이전 단계에서 인증 코드를 받기 위해 사용된 주소입니다. - 애플리케이션이 액세스 토큰을 받으면, 이 토큰을 사용하여 보호된 리소스에 접근할 수 있습니다. 이 경우
scope: articles.read
에 따라 특정 리소스에 대한 액세스 권한이 부여됩니다.
- 인증 코드를 받은 후, 애플리케이션은 이 코드를 사용하여 인증 서버에 액세스 토큰을 요청합니다. 이 때
요약하자면, articles-client-oidc
설정은 사용자 인증을 위한 초기 로그인 과정에 사용되며, articles-client-authorization-code
설정은 인증된 사용자가 보호된 리소스에 접근하기 위해 필요한 액세스 토큰을 얻는 과정에 사용
2.2 소스
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.oauth2Login(oauth2Login ->
oauth2Login.loginPage("/oauth2/authorization/articles-client-oidc"))
.oauth2Client(withDefaults());
return http.build();
}
}
oauth2Login.loginPage("/oauth2/authorization/articles-client-oidc"))
해당 페이지는 클라이언트 서버에서 제공되는 페이지이며, security5에 제공되는 페이지로 사용된거.
@RestController
public class ArticlesController {
private WebClient webClient;
@GetMapping(value = "/articles")
public String[] getArticles(
@RegisteredOAuth2AuthorizedClient("articles-client-authorization-code") OAuth2AuthorizedClient authorizedClient
) {
return this.webClient
.get()
.uri("http://127.0.0.1:8090/articles")
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String[].class)
.block();
}
}
OAuth2AuthorizedClient 클래스 형식의 요청에서 OAuth 인증 토큰을 가져옵니다 .
이는 적절한 식별과 함께 @RegisterdOAuth2AuthorizedClient 주석을 사용하여 Spring에 의해 자동으로 바인딩됩니다 . 우리의 경우 이전에 .yml 파일 에서 구성한 article-client-authorizaiton-code 에서 가져왔습니다
3. 테스트 호출
브라우저에서 호출 http://127.0.0.1:8080/articles
인증 url 로 리다이렉션 http://auth-server:9000/login
4. 리소스 서버
보안구성 - 인증서버 설정
인증서버에 설정한 ProviderSettings 값을 issuer-uri 로 설정
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://auth-server:9000
웹보안 구성 설정 : 리소스에 대한 권한 설정( 읽기, 모든 요청에 대한 승인요청)
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.mvcMatcher("/articles/**")
.authorizeRequests() //1.요청에 대한 인증을 시작합니다. 이 단계에서는 이후에 정의될 인증 규칙들이 적용됩니다
.mvcMatchers("/articles/**")//2.특정 경로(/articles/**)에 대한 추가적인 규칙을 정의.
// 해당 경로에 대한 보안 규칙을 세분화
.access("hasAuthority('SCOPE_articles.read')")//3./articles/** 경로에 대한 접근을 제어
// 'SCOPE_articles.read' 권한을 가지고 있어야만 해당 경로에 접근 가능
// 이 권한은 일반적으로 OAuth 2.0 토큰에 정의된 스코프에서 파생됩니다.
.and()
.oauth2ResourceServer() //3. OAuth 2.0 리소스 서버를 활성화하고 JWT (JSON Web Token)를 사용하여 인증을 수행
.jwt(); // 들어오는 요청이 올바른 JWT를 포함하고 있는지 검증하여 해당 요청이 유효한지 확인
return http.build();
}
}
5. 인증서버
@Configuration
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("articles-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
}
-
RegisteredClient.withId(UUID.randomUUID().toString())
: 클라이언트 ID를 생성합니다. 여기서는 고유한 UUID를 사용합니다. -
.clientId("articles-client")
: OAuth 클라이언트의 식별자로 "articles-client"를 지정합니다. Spring은 이를 사용하여 리소스에 액세스하려는 클라이언트를 식별합니다. -
.clientSecret("{noop}secret")
: 클라이언트의 비밀번호를 설정합니다.{noop}
은 패스워드 인코더가 사용되지 않음을 의미합니다. -
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
: 클라이언트 인증 방식을CLIENT_SECRET_BASIC
으로 설정합니다. 이는 클라이언트 ID와 비밀번호를 사용하여 인증을 수행하는 방식입니다. -
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
(인증 부여 유형): 인증 코드 부여 유형을 지원합니다. 이는 OAuth 2.0의 표준 플로우 중 하나입니다. -
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
(인증 부여 유형): 리프레시 토큰 부여 유형을 지원합니다. 이를 통해 클라이언트는 액세스 토큰을 갱신할 수 있습니다. -
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
: OAuth 인증 후 사용자를 리디렉션할 URI를 설정합니다. -
.redirectUri("http://127.0.0.1:8080/authorized")
: 추가적인 리디렉션 URI를 설정합니다. -
.scope(OidcScopes.OPENID)
: OpenID Connect 스코프를 사용합니다. -
.scope("articles.read")
: 사용자 정의 스코프인 "articles.read"를 설정합니다. 이 스코프는 클라이언트가 특정 자원에 접근할 수 있도록 허용합니다. -
.build()
:RegisteredClient
구성을 완성합니다.
5.1 상세
.redirectUri
설정에 대해 설명드리겠습니다. 이 설정은 OAuth 2.0 인증 프로세스에서 매우 중요한 부분입니다.
5.1.1. 인증 코드 흐름 (Authorization Code Flow):
- .redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
: 이 리디렉션 URI는 일반적으로 인증 코드 흐름에 사용됩니다. 사용자가 OAuth 인증을 시작하면, 사용자는 이 URI로 리디렉션됩니다. 여기서 사용자는 로그인하고 동의를 제공한 후, 인증 서버는 이 URI로 인증 코드를 리디렉션합니다. 이 인증 코드는 나중에 액세스 토큰을 얻는 데 사용됩니다.
5.1.2. 액세스 토큰 요청:
- .redirectUri("http://127.0.0.1:8080/authorized")
: 이 URI는 액세스 토큰을 받기 위한 추가적인 리디렉션 포인트로 사용될 수 있습니다. 사용자가 인증 코드를 사용하여 액세스 토큰을 요청할 때, 인증 서버는 이 URI로 사용자를 리디렉션할 수 있습니다.
리디렉션 URI의 순서는 일반적으로 중요하지 않습니다. 중요한 것은 클라이언트 애플리케이션이 이 URI들 중 하나를 사용하여 인증 서버에 리디렉션을 요청하는 것입니다. 클라이언트 애플리케이션이 인증 요청 시에 사용하는 redirect_uri
매개변수의 값은 등록된 리디렉션 URI 중 하나와 정확히 일치해야 합니다.
요약하자면, .redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
는 사용자가 로그인하고 인증 서버가 인증 코드를 반환하는 데 사용되며, .redirectUri("http://127.0.0.1:8080/authorized")
는 추가적인 리디렉션 옵션으로, 특정 상황에서 사용될 수 있습니다. 이 두 URI는 인증 프로세스의 다른 단계에서 사용되며, 그 순서는 특별한 의미를 가지지 않습니다.
5.2 구성
기본 OAuth 보안을 적용하고 기본 양식 로그인 페이지를 생성하도록 Bean을 구성
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
각 인증 서버에는 보안 도메인 간의 적절한 경계를 유지하기 위해 토큰에 대한 서명 키가 필요합니다. 2048바이트 RSA 키를 생성해 보겠습니다.
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
서명 키를 제외하고 각 인증 서버에는 고유한 발급자 URL도 있어야 합니다. ProviderSettings 빈을 생성하여 포트 9000 에서 http://auth-server 에 대한 localhost 별칭으로 설정하겠습니다 .
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("http://auth-server:9000")
.build();
}
또한 /etc/hosts 파일에 " 127.0.0.1 auth-server " 항목을 추가하겠습니다 . 이를 통해 로컬 컴퓨터에서 클라이언트와 인증 서버를 실행할 수 있으며 둘 사이의 세션 쿠키 덮어쓰기 문제를 피할 수 있습니다.
그런 다음 @EnableWebSecurity 주석이 달린 구성 클래스 를 사용하여 Spring 웹 보안 모듈을 활성화합니다
@EnableWebSecurity
public class DefaultSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
// ...
}
- 모든 요청에 대해 인증을 요구하기 위해 AuthorizeRequests.anyRequest().authenticated()를 호출
- formLogin(defaults()) 메서드 를 호출하여 양식 기반 인증을 제공하고
테스트에 사용할 예제 사용자 집합을 정의
@Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.build();
return new InMemoryUserDetailsManager(user);
}
Top comments (0)