스프링 부트 공식 레퍼런스를 한글로 번역한 문서입니다.
전체 목차는 여기에 있습니다.
목차
- 7.10.1. MVC Security
- 7.10.2. WebFlux Security
- 7.10.3. OAuth2
- 7.10.4. SAML 2.0
- 7.10.5. Actuator Security
7.10. Security
클래스패스에 스프링 시큐리티가 있으면 디폴트로 웹 애플리케이션을 보호해준다. 스프링 부트는 스프링 시큐리티의 content-negotiation 전략에 따라 httpBasic
이나 formLogin
사용 여부를 결정한다. 메소드 레벨 보안을 사용하고 싶을 땐 애플리케이션에 원하는 설정으로 @EnableGlobalMethodSecurity
를 추가해도 된다. 추가 정보는 스프링 시큐리티 레퍼런스 가이드에서 확인할 수 있다.
디폴트 UserDetailsService
는 단일 사용자를 갖는다. user name은 user
이고, password는 랜덤으로 결정되며, 아래 보이는 것처럼 애플리케이션이 시작할 때 INFO 레벨로 password를 출력한다:
Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35
로그 설정을 세분화해놨다면, org.springframework.boot.autoconfigure.security 카테고리는
INFO
레벨 메세지를 출력하도록 설정돼 있는지 확인해봐야 한다.INFO
레벨을 기록하지 않게 되어 있다면 디폴트 패스워드를 출력하지 않는다.
spring.security.user.name
과 spring.security.user.password
를 제공하면 username과 password를 변경할 수 있다.
웹 애플리케이션에 기본으로 제공하는 기능은 다음과 같다:
- 인메모리 저장소를 사용하는
UserDetailsService
빈과 (웹플럭스 애플리케이션에선ReactiveUserDetailsService
), 자동으로 만들어진 패스워드를 가지고 있는 단일 사용자 (사용자와 관련된 프로퍼티는SecurityProperties.User
를 참고해라). - 애플리케이션 전체에서 사용할 (클래스패스에 액추에이터가 있을 땐 액추에이터 엔드포인트도 포함한다) 폼 기반 로그인 혹은 HTTP Basic 보안 (요청 헤더
Accept
에 따라 달라진다). - 인증 이벤트를 게시하기 위한
DefaultAuthenticationEventPublisher
.
다른 AuthenticationEventPublisher
를 사용하고 싶다면 전용 빈을 추가하면 된다.
7.10.1. MVC Security
디폴트 시큐리티 설정은 SecurityAutoConfiguration
과 UserDetailsServiceAutoConfiguration
에서 구현한다. SecurityAutoConfiguration
은 웹 보안을 위해 SpringBootWebSecurityConfiguration
을 임포트하며, UserDetailsServiceAutoConfiguration
은 웹 애플리케이션이 아니더라도 관련 있는 인증 설정을 구성해준다. 디폴트 웹 애플리케이션 시큐리티 설정을 완전히 꺼버리거나, OAuth2 클라이언트나 리소스 서버같은 스프링 시큐리티 컴포넌트를 여러 개 조합하려면 SecurityFilterChain
타입 빈을 추가해라 (이렇게 해도 UserDetailsService
설정이나 액추에이터의 보안은 비활성화되지 않는다).
UserDetailsService
설정도 꺼버리려면 UserDetailsService
나, AuthenticationProvider
, AuthenticationManager
타입 빈을 추가하면 된다.
커스텀 SecurityFilterChain
이나 WebSecurityConfigurerAdapter
빈을 추가해도 액세스 규칙을 재정의할 수 있다. 스프링 부트에선 좀 더 편하게 액추에이터 엔드포인트와 스태틱 리소스에 대한 액세스 규칙을 재정의할 수 있다. management.endpoints.web.base-path 프로퍼티 기반 RequestMatcher
를 생성할 때는 EndpointRequest
를 활용할 수 있다. 공통으로 사용하는 위치에 있는 리소스에 대한 RequestMatcher
를 생성할 땐 PathRequest
를 사용하면 된다.
7.10.2. WebFlux Security
웹플럭스에서도 스프링 MVC와 유사하게 spring-boot-starter-security
의존성을 추가해서 애플리케이션을 보호할 수 있다. 디폴트 시큐리티 설정은 ReactiveSecurityAutoConfiguration
과 UserDetailsServiceAutoConfiguration
에서 구현한다. ReactiveSecurityAutoConfiguration
은 웹 보안을 위해 WebFluxSecurityConfiguration
을 임포트하며, UserDetailsServiceAutoConfiguration
은 웹 애플리케이션이 아니더라도 관련 있는 인증 설정을 구성해준다. 디폴트 웹 애플리케이션 시큐리티 설정을 완전히 꺼버리려면 WebFilterChainProxy
타입 빈을 추가하면 된다 (이렇게 해도 UserDetailsService
설정이나 액추에이터의 보안은 비활성화되지 않는다).
UserDetailsService
설정도 꺼버리려면 ReactiveUserDetailsService
나 ReactiveAuthenticationManager
타입 빈을 추가하면 된다.
액세스 규칙을 바꾸거나, OAuth 2 클라이언트나 리소스 서버같은 스프링 시큐리티 컴포넌트를 여러 개 사용하고 싶을 땐 커스텀 SecurityWebFilterChain
빈을 추가하면 된다. 스프링 부트에선 좀 더 편하게 액추에이터 엔드포인트와 스태틱 리소스에 대한 액세스 규칙을 재정의할 수 있다. management.endpoints.web.base-path 프로퍼티 기반 ServerWebExchangeMatcher
를 생성할 때는 EndpointRequest
를 활용할 수 있다.
공통으로 사용하는 위치에 있는 리소스에 대한 ServerWebExchangeMatcher
를 생성할 땐 PathRequest
를 사용하면 된다.
예를 들면, 아래와 같은 코드로 시큐리티 설정을 커스텀할 수 있다:
@Configuration(proxyBeanMethods = false)
public class MyWebFluxSecurityConfiguration {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange((spec) -> {
spec.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
spec.pathMatchers("/foo", "/bar").authenticated();
});
http.formLogin();
return http.build();
}
}
7.10.3. OAuth2
OAuth2는 널리 사용하는 인증 프레임워크로, 스프링에서도 지원한다.
Client
클래스패스에 spring-security-oauth2-client
가 있으면 몇 가지 자동 설정을 통해 OAuth2/Open ID Connect 클라이언트를 활용할 수 있다. 이 설정에선 OAuth2ClientProperties
밑에 있는 프로퍼티를 사용한다. 서블릿과 리액티브 애플리케이션 모두 이 프로퍼티를 사용한다.
다음 예제처럼 spring.security.oauth2.client
프리픽스로 여러 가지 OAuth2 클라이언트와 provider를 등록할 수 있다:
spring.security.oauth2.client.registration.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.my-client-1.client-secret=password
spring.security.oauth2.client.registration.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-1.scope=user
spring.security.oauth2.client.registration.my-client-1.redirect-uri=https://my-redirect-uri.com
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=basic
spring.security.oauth2.client.registration.my-client-1.authorization-grant-type=authorization-code
spring.security.oauth2.client.registration.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.my-client-2.client-secret=password
spring.security.oauth2.client.registration.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-2.scope=email
spring.security.oauth2.client.registration.my-client-2.redirect-uri=https://my-redirect-uri.com
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=basic
spring.security.oauth2.client.registration.my-client-2.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=https://my-auth-server/oauth/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server/oauth/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server/userinfo
spring.security.oauth2.client.provider.my-oauth-provider.user-info-authentication-method=header
spring.security.oauth2.client.provider.my-oauth-provider.jwk-set-uri=https://my-auth-server/token_keys
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name
spring:
security:
oauth2:
client:
registration:
my-client-1:
client-id: "abcd"
client-secret: "password"
client-name: "Client for user scope"
provider: "my-oauth-provider"
scope: "user"
redirect-uri: "https://my-redirect-uri.com"
client-authentication-method: "basic"
authorization-grant-type: "authorization-code"
my-client-2:
client-id: "abcd"
client-secret: "password"
client-name: "Client for email scope"
provider: "my-oauth-provider"
scope: "email"
redirect-uri: "https://my-redirect-uri.com"
client-authentication-method: "basic"
authorization-grant-type: "authorization_code"
provider:
my-oauth-provider:
authorization-uri: "https://my-auth-server/oauth/authorize"
token-uri: "https://my-auth-server/oauth/token"
user-info-uri: "https://my-auth-server/userinfo"
user-info-authentication-method: "header"
jwk-set-uri: "https://my-auth-server/token_keys"
user-name-attribute: "name"
OpenID Connect 디스커버리를 지원하는 OpenID Connect provider는 설정을 더 간소화할 수 있다. 이땐 issuer-uri
에 provider의 Issuer 식별자를 URI로 설정해야 한다. 예를 들어서 issuer-uri
를 “https://example.com”으로 설정하면, “https://example.com/.well-known/openid-configuration”으로 OpenID Provider Configuration Request를 보낸다. 응답으론 OpenID Provider Configuration Response를 받을 것으로 기대한다. 다음은 issuer-uri
로 OpenID Connect provider를 설정하는 예시다:
spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
spring:
security:
oauth2:
client:
provider:
oidc-provider:
issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"
스프링 시큐리티의 OAuth2LoginAuthenticationFilter
는 기본적으로 /login/oauth2/code/*
와 매칭되는 URL만 처리한다. redirect-uri
에서 사용할 패턴을 바꾸고 싶으면 커스텀 패턴을 처리하기 위한 설정을 제공해야 한다. 예를 들어 서블릿 애플리케이션에선 다음과 유사한 자체 SecurityFilterChain
을 추가할 수 있다:
@Configuration(proxyBeanMethods = false)
public class MyOAuthClientConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
http.oauth2Login().redirectionEndpoint().baseUri("custom-callback");
return http.build();
}
}
스프링 부트는 스프링 시큐리티에서 클라이언트 registration을 관리하기 위해 사용하는
InMemoryOAuth2AuthorizedClientService
를 자동으로 설정한다.InMemoryOAuth2AuthorizedClientService
는 기능에 제한이 있으므로 개발 환경에서만 사용하길 권장한다. 프로덕션 환경에선JdbcOAuth2AuthorizedClientService
를 사용하거나 자체OAuth2AuthorizedClientService
구현체를 생성하는 게 좋다.
OAuth2 client registration for common providers
구글, 깃허브, 페이스북, 옥타 등, 많이 사용하는 OAuth2, OpenID provider를 위한 기본값들을 제공하고 있다 (각각 google
, github
, facebook
, okta
).
이런 provider를 커스텀할 필요가 없다면, provider
속성을 이 중 하나로 설정하면 되며, 이때 기본값들은 직접 유추해야 한다. 스프링 부트는 클라이언트 registration 키가 지원하는 디폴트 provider와 일치할 때도 유추해낸다.
즉, 아래 예제에 있는 두 설정은 Google provider를 사용한다:
spring.security.oauth2.client.registration.my-client.client-id=abcd
spring.security.oauth2.client.registration.my-client.client-secret=password
spring.security.oauth2.client.registration.my-client.provider=google
spring.security.oauth2.client.registration.google.client-id=abcd
spring.security.oauth2.client.registration.google.client-secret=password
spring:
security:
oauth2:
client:
registration:
my-client:
client-id: "abcd"
client-secret: "password"
provider: "google"
google:
client-id: "abcd"
client-secret: "password"
Resource Server
클래스패스에 spring-security-oauth2-resource-server
가 있으면 스프링 부트에서 OAuth2 리소스 서버를 설정할 수 있다. JWT 설정에선 다음 예시처럼 JWK Set URI나 Issuer URI를 지정해야 한다:
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://example.com/oauth2/default/v1/keys
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: "https://example.com/oauth2/default/v1/keys"
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"
인증 서버가 JWK Set URI를 지원하지 않을 때는 JWT 서명을 확인할 때 사용하는 공개 키로 리소스 서버를 구성할 수 있다. 이땐 spring.security.oauth2.resourceserver.jwt.public-key-location 프로퍼티를 사용하면 된다. 이 프로퍼티는 PEM으로 인코딩된 x509 형식의 공개 키를 가지고 있는 파일을 가리켜야 한다.
서블릿, 리액티브 애플리케이션 모두 같은 프로퍼티를 사용한다.
아니면 서블릿 애플리케이션에선 JwtDecoder
빈을, 리액티브 애플리케이션에선 ReactiveJwtDecoder
를 정의할 수도 있다.
JWT 대신 opaque 토큰을 사용할 때는, 아래 프로퍼티를 설정하면 introspection을 통해 토큰의 유효성을 검증할 수 있다:
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://example.com/check-token
spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id
spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret
spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: "https://example.com/check-token"
client-id: "my-client-id"
client-secret: "my-client-secret"
다시 말하지만, 서블릿과 리액티브 애플리케이션 모두 같은 프로퍼티를 사용한다.
아니면 서블릿 애플리케이션에선 자체 OpaqueTokenIntrospector
빈을, 리액티브 애플리케이션에선 ReactiveOpaqueTokenIntrospector
를 정의해도 된다.
Authorization Server
현재 스프링 시큐리티는 OAuth 2.0 Authorization Server 구현을 지원하지 않는다. 하지만 이 기능은 Spring Security OAuth 프로젝트에서 이용할 수 있으며, 궁극적으로는 스프링 시큐리티로 완전히 대체할 예정이다. 그때까지는 spring-security-oauth2-autoconfigure
모듈을 사용해서 OAuth 2.0 인증 서버를 간단히 설정할 수 있다. 상세 가이드는 문서를 참고해라.
7.10.4. SAML 2.0
Relying Party
클래스패스에 spring-security-saml2-service-provider
가 있으면 몇 가지 자동 설정을 통해 SAML 2.0 Relying Party를 활용할 수 있다. 이 설정에선 Saml2RelyingPartyProperties
밑에 있는 프로퍼티를 사용한다.
relying party registration에선 Identity ProviderIDP와 Service ProviderSP 설정이 쌍을 이룬다. 다음 예제처럼 spring.security.saml2.relyingparty
프리픽스로 여러 가지 relying party를 등록할 수 있다:
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.identityprovider.verification.credentials[0].certificate-location=path-to-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party1.identityprovider.entity-id=remote-idp-entity-id1
spring.security.saml2.relyingparty.registration.my-relying-party1.identityprovider.sso-url=https://remoteidp1.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.identityprovider.verification.credentials[0].certificate-location=path-to-other-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party2.identityprovider.entity-id=remote-idp-entity-id2
spring.security.saml2.relyingparty.registration.my-relying-party2.identityprovider.sso-url=https://remoteidp2.sso.url
spring:
security:
saml2:
relyingparty:
registration:
my-relying-party1:
signing:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
decryption:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
identityprovider:
verification:
credentials:
- certificate-location: "path-to-verification-cert"
entity-id: "remote-idp-entity-id1"
sso-url: "https://remoteidp1.sso.url"
my-relying-party2:
signing:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
decryption:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
identityprovider:
verification:
credentials:
- certificate-location: "path-to-other-verification-cert"
entity-id: "remote-idp-entity-id2"
sso-url: "https://remoteidp2.sso.url"
7.10.5. Actuator Security
보안 상의 이유로 /health
를 제외한 모든 액추에이터는 기본적으로 비활성화된다. 액추에이터는 management.endpoints.web.exposure.include 프로퍼티로 활성화할 수 있다.
클래스패스에 스프링 시큐리티가 있으면서 별 다른 WebSecurityConfigurerAdapter
나 SecurityFilterChain
빈이 없으면, /health
를 제외한 모든 액추에이터를 스프링 부트 자동 설정으로 보호한다. 커스텀 WebSecurityConfigurerAdapter
나 SecurityFilterChain
빈을 정의하면 스프링 부트 자동 설정은 취소되며, 직접 액추에이터 액세스 규칙을 완전히 제어할 수 있다.
management.endpoints.web.exposure.include를 설정하기 전에 먼저, 노출하는 액추에이터에 민감한 정보가 포함되어 있지는 않은지 확인해봐라. 앞에 방화벽을 두거나 스프링 시큐리티 등을 활용해 보호하는 방법도 있다.
Cross Site Request Forgery Protection
스프링 부트는 스프링 시큐리티의 기본값을 사용하기 때문에, 디폴트로 CSRF 보호를 활성화한다. 따라서 디폴트 시큐리티 설정을 사용하면, POST
나 (shutdown, loggers 엔드포인트), PUT
, DELETE
가 필요한 액추에이터 엔드포인트에선 403 forbidden 에러가 발생한다.
개발 중인 서비스를 사용하는 클라이언트가 브라우저가 아닐 때만 CSRF 보호를 완전히 비활성화하는 게 좋다.
CSRF 보호에 관한 추가 정보는 스프링 시큐리티 레퍼런스 가이드에서 확인할 수 있다.
Next :Working with SQL Databases
스프링 부트로 JPA, JDBC, JOOQ, R2DBC를 활용해 데이터소스, 커넥션 팩토리, 템플릿, 레포지토리 자동 설정하기
전체 목차는 여기에 있습니다.