1. 스프링 시큐리티 인증, 인가 흐름
- 인증(Authentication)은 사용자가 누구인지 확인하는 과정
- 인가(Authorization)는 그 사용자가 어떤 리소스에 접근할 수 있는지 결정하는 과정
1.1 인증 흐름
- 클라이언트가 ID와 비밀번호와 같은 자격 증명을 입력하여 로그인 요청을 보내고 이는 Authentication 객체로 변환됨
- AuthenticationManager가 인증 요청을 처리하는데 AuthenticationManager는 여러 개의 AuthenticationProvider에게 인증 처리를 위임
- 각각의 AuthenticaitonProvider는 DB 기반 인증, LDAP 기반 인증 등과 같은 특정 인증 방식에 대해 인증 시도
- 기본적으로 DaoAuthenticaitonProvider가 많이 사용되며 이는 사용자 정보를 UserDetailsService에서 조회하고 사용자의 패스워드를 검증
- AuthenticcationProvider는 사용자의 자격 증명을 확인하기 위해 UserDetailsService를 호출하여 사용자 정보를 가져오고 이때 UserDetailsService는 사용자 이름에 해당하는 UserDetails 객체를 반환
- UserDetails 객체에 담긴 사용자 정보와 클라이언트가 입력한 자격증명을 비교하는데 패스워드는 일반적으로 PasswordEncoder를 사용해 해시된 값과 비교
- 사용자가 유효한 자격 증명을 제공한 경우 AuthenticationManager는 인증이 성공했다고 판단하고 Authentication 객체를 SecurityContext에 저장하며 이때 인증된 Authentication 객체는 인증된 사용자의 세부 정보를 포함
- 인증 성공했을 경우 Spring Security는 인증된 사용자 정보를 SecurityContextHolder에 저장하고 애플리케이션의 흐름을 계속 진행시킴
- 인증 실패 시 AuthenticationException 예외가 발생하며 인증 실패 페이지로 리다이렉트 됨
1.2 인가 흐름
인증이 완료된 후, 사용자가 애플리케이션 내의 리소스에 접근할 때 인가가 이루어지며 인가는 앞서 언급했다시피 해당 사용자가 요청한 리소스에 접근할 수 있는 권한이 있는지를 확인하는 과정입니다.
- 사용자가 특정 URL에 접근을 시도하면 Spring Security의 필터 체인이 이를 가로채고 적절한 인가 검사를 수행
- 일반적으로 SecurityFilterChain에 등록된 여러 필터가 존재하며 인가는 주로 FilterSecurityInterceptor에 의해 처리됨
- FilterSecurityInterceptor는 요청에 대한 인가 결정을 내리기 위해 AccessDecisionManager를 호출하며 해당 리소스에 대한 접근을 허용할지 여부를 결정
- FilterSecurityInterceptor는 요청 URl, 메서드 등과 같은 요청 정보에 매핑된 ConfigAttributes를 확인하며 해당 리소스에 접근 가능한 권한 목록을 얻음
- AccessDecisionManager는 인증된 Authentication의 권한과 ConfigAttributes에 정의된 접근 조건을 비교하여 접근 여부를 결정
- 사용자에게 적절한 권한이 부여된 경우 요청이 통과되어 리소스에 접근을 허용
- 사용자가 리소스에 대한 권한 없을 경우 AccessDeniedException 예외가 발생하고 403 Forbidden 응답 반환

2. GrantedAuthority
- GrantedAuthority를 활용하여 권한 부여 기능 적용 가능
- 적용 시 사용자마다 다른 권한을 가지게 되며 권한에 따라 특정 작업만 수행할 수 있음
- 사용자, 권한, 기능은 서로 엮여 있기 때문에 GrantedAuthority 뿐만 아니라 UserDetails 등도 다 함께 연결되어 있음
- 사용자는 하나 이상의 권한을 가질 수 있으며 UserDetailsService를 통해 사용자에 대한 정보를 취득할 수 있음
- UserDetails는 하나 이상의 GrantedAuthority를 가질 수 있음


3. 엔드 포인트 접근 제한
- 스프링 시큐리티에서 제공하는 다음 메서드를 활용하면 사용자마다 특정 엔드 포인트에 접근을 제한할 수 있음
- hasAuthority()
- hasAnyAuthority()
- access()
3.1 hasAuthority()
- 제한을 구성하는 하나의 권한을 파라미터로 전달받고 해당 권한이 있는 사용자는 엔드포인트로 접근할 수 있음
3.2 hasAnyAuthority()
- 제한을 구성하는 하나 이상의 권한을 파라미터로 전달받을 수 있으며 이 중 하나라도 있을 때 허용
3.3 Spring Security 5 버전, SpringBoot 2.X 버전의 access().*
- 정규 표현식처럼 스프링에서도 SpEL 표현식을 제공하며 이를 활용해서 권한 부여 규칙을 정의
- 자율성을 극대화하는 장점도 존재하지만 가독성이 떨어지고 디버깅이 어려운 단점이 존재
3.4 Spring Security 6 버전, SpringBoot 3.X 버전의 access().*
- 커스텀 AuthorizationManager를 만들어 활용
3.5 denyAll
- denyAll 활용 시 모든 요청을 기본적으로 거부하며 특정 요청만 허용하고 싶을 때 활용 가능
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
public class SecurityConfig { | |
@Bean | |
public UserDetailsService userDetailsService() { | |
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); | |
UserDetails jaimemin = User.withUsername("jaimemin") | |
.password("1234") | |
.authorities("READ") | |
.roles("MANAGER") | |
.authorities("ROLE_MANAGER") | |
.build(); | |
manager.createUser(jaimemin); | |
return manager; | |
} | |
@Bean | |
public PasswordEncoder passwordEncoder() { | |
return NoOpPasswordEncoder.getInstance(); | |
} | |
@Bean | |
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { | |
httpSecurity.httpBasic(Customizer.withDefaults()); | |
// httpSecurity.authorizeHttpRequests(c -> c.anyRequest().permitAll()); // 모든 요청 허용 | |
// httpSecurity.authorizeHttpRequests(c -> c.anyRequest().hasAuthority("WRITE")); // WRITE 권한이 있는 사용자만 허용 | |
// httpSecurity.authorizeHttpRequests( | |
// c -> c.anyRequest().hasAnyAuthority("READ", "WRITE")); // READ, WRITE 권한이 있는 사용자만 허용 | |
httpSecurity.authorizeHttpRequests(c -> c.anyRequest().access(customAuthManager())); // WRITE 권한이 있는 사용자만 허용 | |
// httpSecurity.authorizeHttpRequests(c -> c.anyRequest().hasRole("MANAGER")); // WRITE 권한이 있는 사용자만 허용 | |
// httpSecurity.authorizeHttpRequests(c -> c.anyRequest().denyAll()); // 모든 요청 거부 | |
return httpSecurity.build(); | |
} | |
private AuthorizationManager<RequestAuthorizationContext> customAuthManager() { | |
return new AuthorizationManager<RequestAuthorizationContext>() { | |
@Override | |
public AuthorizationDecision check(Supplier<Authentication> authentication, | |
RequestAuthorizationContext object) { | |
Authentication auth = authentication.get(); | |
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities(); | |
boolean granted = authorities.stream().anyMatch(each -> each.getAuthority().equals("READ")); | |
return new AuthorizationDecision(granted); | |
} | |
}; | |
} | |
} |

부연 설명
- 커스텀 AuthorizationManager를 만들어 READ 권한이 있는 계정에 대해서는 403 Forbidden을 반환하도록 SecurityConfig를 설정했으므로 READ 권한이 있는 jaimemin 계정으로 로그인 시도 시 403 Forbidden 반환
4. RequestMatchers
- 특정한 요청 그룹에 권한 부여 제약 조건을 적용하기 위해 Matcher 활용 가능
- Spring Security 5 버전까지는 mvcMatcher, antMatcher, regexMatcher를 활용했지만
- Spring Security 6 버전부터는 기본적으로 requestMatcher를 활용
- Spring Security 6에서 RequestMatchers는 요청 URL, HTTP 메서드, 헤더 등 다양한 요청 조건에 따라 보안 정책을 세밀하게 설정할 수 있는 기능을 제공
- 이전 버전에서 사용되던 antMatchers(), mvcMatchers()와 같은 메서드가 사라지고, 더 유연하고 통일된 방식으로 보안 규칙을 정의할 수 있도록 requestMatchers()가 도입
- 단순히 URL 패턴뿐만 아니라 HTTP 메서드, 헤더, 매개변수 등을 기준으로 요청을 구분할 수 있음
- requestMatchers() 메서드는 요청 매칭을 처리하는 데 사용되며 authorizeHttpRequests()와 함께 사용하여 특정 요청에 대해 인가 규칙을 설정 가능
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Configuration | |
public class SecurityConfig { | |
@Bean | |
public UserDetailsService userDetailsService() { | |
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); | |
UserDetails jaimemin = User.withUsername("jaimemin") | |
.password("1234") | |
.roles("ADMIN") | |
.build(); | |
manager.createUser(jaimemin); | |
return manager; | |
} | |
@Bean | |
public PasswordEncoder passwordEncoder() { | |
return NoOpPasswordEncoder.getInstance(); | |
} | |
@Bean | |
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { | |
httpSecurity.httpBasic(Customizer.withDefaults()); | |
httpSecurity.authorizeHttpRequests(c -> c.requestMatchers(HttpMethod.GET, "/api/v1/hello") | |
.authenticated()); // HTTP GET 방식으로 /api/v1/hello 를 요청하면 앱이 사용자를 인증해야 함 | |
httpSecurity.authorizeHttpRequests(c -> c.requestMatchers(HttpMethod.POST, "/api/v1/hello") | |
.permitAll()); // HTTP POST 방식으로 /api/v1/hello 를 요청하면 모두 허용함 | |
httpSecurity.authorizeHttpRequests(c -> c.requestMatchers("/api/v1/hello/**") | |
.authenticated()); // /api/v1/hello/** 식은 접두사 /api/v1/hello 가 붙은 모든 경로를 의미함 | |
httpSecurity.authorizeHttpRequests(c -> c.anyRequest() | |
.denyAll()); // 나머지 요청은 모두 거부함 | |
return httpSecurity.build(); | |
} | |
} |
비고
1. 역할(ROLE) vs 권한(Authority)
1.1 역할
- 사용자의 직책이나 기능에 따른 권한의 집합
- 여러 권한을 그룹화하여 역할로 정의하고 이를 사용해 보다 간단하게 인가 처리 가능
- 스프링 시큐리티에서는 역할을 항상 `ROLE_`이라는 prefix와 함께 사용
- ex) hasRole("ADMIN"과 같은 방식으로 사용자의 역할을 검사할 경우 Spring Security는 내부적으로 `ROLE_ADMIN`이라는 권한을 찾아 검사
- 역할은 다수의 권한을 묶어서 처리하거나 사용자 그룹별로 리소스에 대한 접근을 관리하는 데 사용
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 역할 기반 인가 | |
http.authorizeHttpRequests() | |
.antMatchers("/admin/**").hasRole("ADMIN") // "ROLE_ADMIN" 권한이 있는 사용자만 접근 허용 | |
.anyRequest().authenticated(); |
1.2 권한
- 특정 작업을 수행할 수 있는 능력을 나타내며 사용자가 시스템 내에서 어떤 행동을 수행할 수 있는지에 대한 세밀한 권한을 정의하는 개념
- 권한은 특정 리소스에 대한 구체적인 접근 권한을 부여하는 데 사용됨
- 권한은 문자열로 표현되며 특정 작업이나 리소스에 대한 접근 권한을 나타냄
- ex) `READ`, `WRITE`, `DELETE`와 같은 권한이 있음
- 단, 권한에는 `ROLE_` 접두어가 붙지 않으며, 이를 통해 역할과 구분됨
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 권한 기반 인가 | |
http.authorizeHttpRequests() | |
.antMatchers("/documents/**").hasAuthority("WRITE") // "WRITE" 권한이 있는 사용자만 접근 허용 | |
.anyRequest().authenticated(); |
참고
https://fastcampus.co.kr/classroom/240071
반응형
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security] CSRF & CORS (0) | 2024.10.08 |
---|---|
[Spring Security] 필터 체인과 커스텀 필터 (0) | 2024.10.08 |
[Spring Security] 아키텍처 간단 정리 (0) | 2024.09.13 |
[Spring Security] OAuth 2.0 개요 (0) | 2023.12.21 |
[Spring Security] Remember-me를 통해 로그인 유지하기 (0) | 2022.03.27 |