스프링 시큐리티 기본 구성

- 인증 필터가 요청을 가로챈다.
- 인증 책임이 인증 관리자에게 위임된다.
- 인증 관리자는 인증 논리를 구현하는 인증 공급자를 이용한다.
- 인증 공급자는 사용자 세부 정보 서비스로 사용자를 찾고 암호 인코더로 암호를 검증한다.
- 인증 결과가 필터에 반환된다.
- 인증된 엔티티에 관한 세부 정보가 보안 컨텍스트에 저장된다.
사용자 세부 정보 서비스
사용자 관리: UserDetailsService, UserDetailsManager 인터페이스 이용
사용자 인증하는 기능만 필요한 경우 UserDetailsService를 구현하고, 사용자 관리하는 기능이 필요한 경우 UserDetailsManager를 구현하면 된다.
UserDetailsService
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetailsManager
public interface UserDetailsManager extends UserDetailsService {
void createUser(UserDetails user);
void updateUser(UserDetails user);
void deleteUser(String username);
void changePassword(String oldPassword, String newPassword);
boolean userExists(String username);
}
사용자 정의는 UserDetails 계약을 준수
UserDetails 인터페이스
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
default boolean isAccountNonExpired() {
return true;
}
default boolean isAccountNonLocked() {
return true;
}
default boolean isCredentialsNonExpired() {
return true;
}
default boolean isEnabled() {
return true;
}
}
- getUsername(), getPassword(): 사용자 이름과 암호 반환
- getAuthorities() : 사용자에게 부여된 권한의 그룹을 반환하도록 구현
- 사용자 제한을 구현하려면 아래 메서드를 재정의해서 True를 반환하도록 하면 된다.
- isAccountNonExpired(): 계정 만료
- isAccountNonLocked(): 계정 잠금
- isCredentialsNonExpired(): 자격 증명 만료
- isEnabled(): 계정 비활성화
사용자 권한 GrantedAuthority 계약(권한) 준수
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
- 권한을 만들려면 나중에 권한 부여 규칙을 작성할 때 참조할 수 있게 해당 이용 권리의 이름만 찾으면 된다.
- SimpleGrantedAuthority 클래스를 이용해 권한 인스턴스를 만드는 것도 가능하며, 람다 식을 이용할 수도 있다.
암호 인코더
시스템 암호는 일반 텍스트로 관리하지 않고, 공격자가 암호를 읽고 훔치기 어렵게 하기 위한 일종의 변환 과정을 거친다.
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
- encode(CharSequence rawPassword): 주어진 문자열을 변환해 반환한다. 주어진 암호의 해시를 제공하거나 암호화를 수행하는 일을 한다.
- matches(CharSequence rawPassword, String encodedPassword): 지정된 암호를 인증 프로세스에서 알려진 자격 증명의 집합을 대상으로 비교한다.
- upgradeEncoding(String encodedPassword): 계약에서 기본값 false를 반환한다. true를 반환하도록 메서드를 재정의하면 인코딩 된 암호를 보안 향상을 위해 다시 인코딩한다.
- encode() 메서드에서 반환된 문자열은 항상 같은 PasswordEncoder의 match() 메서드로 검증할 수 있어야 한다.
인증 공급자
스프링 시큐리티는 AuthenticationProvider 계약으로 모든 맞춤형 인증 논리를 정의할 수 있다.
Authentication
인증 프로세스의 필수 인터페이스로, 인증 요청 이벤트를 나타내며 애플리케이션에 접근을 요청한 엔티티의 세부 정보를 담는다.
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
- isAuthenticated() : 인증 프로세스가 끝났으면, True를 반환하고 아직 진행 중이면 False를 반환한다.
- getCredentials() : 인증 프로세스에 이용된 암호나, 비밀을 반환한다.
- getAuthorities(): 인증된 요청에 허가된 권한의 컬렉션을 반환한다.
인증 관리자
AuthenticationProvider
AuthenticationProvider는 인증 논리를 처리한다. AuthenticationProvider 인터페이스의 기본 구현은 시스템의 사용자를 찾는 책임을 UserDetailsService에 위임하고, PasswordEncoder로 인증 프로세스에서 암호를 관리한다.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
- 인증이 실패하면 메서드는 AuthenticationException을 투척해야 한다.
- 메서드가 현재 AuthenticationProvider 구현에서 지원되지 않는 인증 객체를 받으면 null을 반환해야 한다. 이렇게 하면 HTTP 필터 수준에서 분리된 여러 Authentication 형식을 사용할 가능성이 생긴다.
- 메서드는 완전히 인증된 객체를 나타내는 Authentication 인스턴스를 반환해야 한다. 이 인스턴스에 대해 isAuthenticated() 메서드는 true()를 반환하며, 인증된 엔티티의 모든 필수 세부 정보가 포함된다. 또한 일반적으로 애플리케이션은 이 인스턴스에서 암호와 같은 민감한 데이터를 제거해야 한다. 인증한 후에는 암호가 더는 필요 없으며, 이러한 세부 정보를 그냥 두면 원치 않게 유출될 우려가 있다.
- AuthenticationProvider의 Supports() 메서드는 현재 AuthenticationProvider 가 Authentication 객체로 제공된 형식을 지원하면 true를 반환하도록 구현한다.
보안 컨텍스트
SecurityContext
인증된 이후 인증된 엔티티에 대한 세부 정보가 필요할 경우 사용
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
- MODE_THREADLOCAL: 각 스레드가 보안 컨텍스트에 각자의 세부 정보를 저장할 수 있게 해준다. 요청당 스레드 방식의 웹 애플리케이션에서는 각 요청이 개별 스레드를 가지므로 이는 일반적인 접근이다.
- MODE_INHERITABLETHREADLOCAL: MODE_THREADLOCAL과 비슷하지만 비동기 메서드의 경우 보안 컨텍스트를 다음 스레드로 복사하도록 스프링 시큐리티에 지시한다. 이 방식으로 @Async 메서드를 실행하는 새 스레드가 보안 컨텍스트를 상속하게 할 수 있다.
- MODE_GLOBAL: 애플리케이션의 모든 스레드가 같은 보안 컨텍스트 인스턴스를 보게 한다.
권한

엑세스 제한
- 사용자 역할을 기준으로 모든 엔드포인트에 대한 접근을 제한
@Configuration
public class ProjectConfig exetends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService(){
var manager = new InMemoryUserDetailsManager();
UserDetails user1 = User.withUsername("john")
.password("12345")
//.authorities("ROLE_ADMIN")
.roles("ADMIN") //역할을 지정한다.
.build();
UserDetails user2 = User.withUsername("jane")
.password("12345")
//.authorities("ROLE_MANAGER")
.roles("MANAGER")
.build();
manager.createUser(user1);
manager.createUser(user2);
return manager;
}
@Override
protected void configurer(HttpSecurity http) throws Exception{
http.httpBasic();
http.authorizeRequests()
.anyRequest().hasRole("ADMIN"); //엔드포인트에 접근할 수 있는 역할을 지정한다, ROLE_ 접두사가 없는 것에 주의
}
}
- hasRole(): 애플리케이션이 요청을 승인할 하나의 역할 이름을 매개 변수로 받는다.
- hasAnyRole(): 애플리케이션이 요청을 승인할 여러 역할 이름을 매개 변수로 받는다.
- access(): 애플리케이션이 요청을 승인할 역할을 스프링 식으로 지정한다. 역할을 지정하는 데는 hasRole() 또는 hasAnyRole()을 SpEL 식으로 이용할 수 있다.
- hasRole(), hasAnyRole()을 우선적으로 사용하고, 이러한 메서드로 해결할 수 없을 때 사용하자
- roles() 메서드로 역할을 지정할 수 있는데, GrantedAuthority 객체를 만들고 지정한 이름에 자동으로 ROLE_ 접두사를 추가한다
- 모든 엔드포인트에 대한 접근 제한
@Configuration
public class ProjectConfig exetends WebSecurityConfigurerAdapter {
@Override
protected void configurer(HttpSecurity http) throws Exception{
http.httpBasic();
http.authorizeRequests()
.anyRequest().denyAll();//denyAll()로 모든 사용자의 접근을 제한
}
}
제한 적용
MVC 선택기: 경로에 MVC 식을 이용해 엔드포인트를 선택한다.
- mvcMatchers(HttpMethod method, String... patterns): 제한을 적용할 Http 방식과 경로를 모두 지정할 수 있다. 같은 경로에 대해 HTTP 방식별로 다른 제한을 적용할 때 유리하다.
- mvcMatchers(String... patterns): 경로만을 기준으로 권한 부여 제한을 적용할 때 더 쉽고 간단하게 이용할 수 있다. 이 메서드를 이용하면, 자동으로 해당 경로의 모든 HTTP 방식에 제한이 적용된다.
앤트 선택기: 경로에 앤트 식을 이용해 엔드포인트를 선택한다.
- antMatchers(HttpMethod method, String patterns): 제한을 적용할 HTTP 방식과 경로를 참조할 앤트 패턴을 모두 지정할 수 있다. 같은 경로 그룹에 대해 HTTP 방식별로 다른 제한을 적용할 때 유용하다.
- antMatchers(String patterns): 경로만을 기준으로 권한 부여 제한을 적용할 때 더 쉽고 간단하게 이용할 수 있다. 모든 HTTP 방식에 자동으로 제한이 적용된다.
- antMatchers(HttpMethod): antMatchers(httpMethod, "/**")와 같은 의미이며, 경로와 관계없이 특정 HTTP 방식을 지정할 수 있다.
정규식 선택기: 경로에 정규식을 이용해 엔드포인트를 선택한다.
- regexMatchers(HttpMethod method, String regix): 제한을 적용할 HTTP 방식과 경로를 참조할 정규식을 모두 지정한다. 같은 경로 그룹 내 대해 HTTP 방식별로 다른 제한을 적용할 때 유용하다.
- regexMatchers(String regix): 경로만을 기준으로 권한 부여 제한을 적용할 때 더 쉽고 간단하게 이용할 수 있다. 모든 HTTP 방식에 자동으로 제한이 적용된다.
필터 구현
스프링 시큐리티 아키텍처의 필터는 일반적인 HTTP 필터이며, 필터를 만들려면 javax.servlet 패키지의 Filter 인터페이스를 구현한다.
다른 HTTP 필터와 마찬가지로 doFilter() 메서드를 재정의하며, ServletRequest, ServletResponse, FilterChain 매개 변수를 받는다.
- 체인에서 기존 필터 앞에 필터 추가
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore( // 피렅 체인에서 인증 필터 앞에 맞춤형 필터의 인스턴스를 추가
new RequestValidationFilter(),
BasicAuthenticationFilter.class
).authorizeRequests()
.anyRequest().permitAll();
}
}
- 체인에서 기존 필터 뒤에 필터 추가
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter( //필터 체인에서 인증 필터 다음에 AuthenticationLogginFilter 인스턴스 추가
new AuthenticationLoggingFilter(),
BasicAuthenticationFilter.class
).authorizeRequests()
.anyRequest().permitAll();
}
}
- 필터 체인의 다른 필터 위치에 필터 추가
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Autowired
private StaticKeyAuthenticationFilter filter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAt( //필터 체인에서 기본 인증 필터의 위치에 필터 추가
filter,
BasicAuthenticationFilter.class
).authorizeRequests()
.anyRequest().permitAll();
}
}
CSRF 보호(CSRF 필터)
CSRF 보호의 시작점. CSRFFilter는 요청을 가로채고 GET,HEAD,TRACE, OPTIONS를 포함하는 HTTP 방식의 요청을 모두 허용하고 다른 모든 요청에는 토큰이 포함된 헤더가 있는지 확인한다.
- 이 헤더가 없거나 헤더에 잘못된 토큰 값이 포함된 경우 애플리케이션은 요청을 거부하고 응답의 상태를 '403'으로 설정한다.
- 해당 토큰은 하나의 문자열 값이며, GET,HEAD,TRACE, OPTIONS 외의 HTTP 방식을 이용할 때 요청의 헤더에 이 토큰을 추가해야 한다
CORS 보호
애플리케이션이 두 개의 서로 다른 도메인 간에 호출하는 것은 모두 금지된다. 호출이 필요할 때는 CORS를 이용해 애플리케이션이 요청을 허용할 도메인, 공유할 수 있는 세부 정보를 지정할 수 있다.
CORS 메커니즘은 HTTP 헤더를 기반으로 작동하며, 가장 중요한 헤더는 아래와 같다.
- Access-Control-Allow-Origin: 도메인의 리소스에 접근할 수 있는 외부 도메인(원본)을 저장한다.
- Access-COntrol-Allow-Methods: 다른 도메인에 대해 접근을 허용하지만 특정 HTTP 방식만 허용하고 싶을 때 일부 HTTP 방식을 지정할 수 있다.
- Access-Control-Allow-Headers: 특정 요청에 이용할 수 있는 헤더에 제한을 추가한다.
'스프링 시큐리티 인 액션' 카테고리의 다른 글
| Ch12. OAuth2가 작동하는 방법 (0) | 2025.04.29 |
|---|---|
| Ch11. 실전. 책임의 분리 (0) | 2025.04.25 |
| Ch10-2. CORS 이용 (0) | 2025.04.24 |