- 스프링 시큐리티에서 익명으로 인증된 사용자와 인증되지 않은 사용자 간에 실제 개념적 차이는 없으며 단지 액세스 제어 속성을 구성하는 더 편리한 방법을 제공한다고 볼 수 있음
- SecurityContextHolder 가 항상 Authentication 객체를 포함하고 null 을 포함하지 않는다는 것을 규칙을 세우게 되면 클래스를 더 견고하게 작성 가능
- 인증 받은 사용자, 익명 사용자(인증 받지 못한 사용자) 두 개의 객체로 구분
- 인증 사용자와 익명 인증 사용자를 구분해서 어떤 기능을 수행하고자 할 때 유용할 수 있으며 익명 인증 객체를 세션에 저장하지 않음
- 인증 사용자, 익명 사용자 모두 시큐리티 컨텍스트에 저장
- 단, 인증 사용자는 세션에 저장하지만 익명 사용자는 인증 받지 못한 상태를 유지할 필요가 없기 때문에 세션에 저장하지 않음
- 익명 인증 사용자의 권한을 별도로 운용 가능
→ 즉, 인증된 사용자가 접근할 수 없도록 구성이 가능
익명 사용자 API 및 구조
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.anonymous(anonymous -> anonymous
.principal("guest")
.authorities("ROLE_GUEST")
);
return http.build();
}
- AnonymousAuthenticationFilter
- 현재 접속한 사용자가 인증 받지 못했으면 익명 사용자의 인증 객체를 생성
- 시큐리티 컨텍스트 안에 저장
- AnonymousAuthenticationToken
- anonymousUser (문자열) → principal에 담김
- ROLE_ANONYMOUS (권한) → authorities에 담김
스프링 MVC에서 익명 인증 사용하기
- 스프링 MVC가 HttpServletRequest#getPrincipal을 사용하여 파라미터를 해결하는데 요청이 익명일 때 이 값은 null
public String authentication(Authentication authentication){
if (authentication instanceof AnonymousAuthenticationToken) {
return "anonymous";
} else {
return "null";
}
}
- 익명 요청에서 Authentication 을 얻고 싶다면 @CurrentSecurityContext를 사용
- CurrentSecurityContextArgumentResolver 에서 요청을 가로채어 처리
public String anonymousContext(@CurrentSecurityContext SecurityContext context){
return context.getAuthentication().getName();
}
AnonymousAuthenticationFilter
- SecurityContextHolder에 Authentication 객체가 없을 경우(null) 감지하고 필요한 경우 새로운 Authentication 객체로 채움
- client GET방식으로 “/index” URL 요청
- AnonymousAuthenticationFilter가 받음
- 이전의 필터들에서 인증 받지 못하고 해당 필터까지 왔을 때 익명 처리
- Authentication의 null 유무 확인
- null이 아닐 경우 인증을 받아 다음 필터로 진행(chain.doFilter)
- null일 경우 다음 스텝으로 진행
- AnonymousAuthenticationToken 생성
- 기본값 ⇒ anoymousUser, ROLE_ANONYMOUS
- SecurityContextHolder → Authentication을 SecurityContext에 설정
IndexController
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.CurrentSecurityContext;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IndexController {
@GetMapping("/")
public String index(){
return "index";
}
@GetMapping("/loginPage")
public String login(){
return "loginPage";
}
@GetMapping("/anonymous")
public String anonymous(){
return "anonymous";
}
@GetMapping("/authentication")
public String authentication(Authentication authentication){
if (authentication instanceof AnonymousAuthenticationToken) {
return "anonymous";
} else {
return "null";
}
}
@GetMapping("/anonymousContext")
public String anonymousContext(@CurrentSecurityContext SecurityContext context){
return context.getAuthentication().getName();
}
}
SecurityConfig
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import java.io.IOException;
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests( auth -> auth
.requestMatchers("/anonymous").hasRole("GUEST")
.requestMatchers("/anonymousContext","/authentication").permitAll()
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.anonymous(anonymous -> anonymous
.principal("guest")
.authorities("ROLE_GUEST")
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService(){
UserDetails user = User.withUsername("user").password("{noop}1111").roles("USER").build();
return new InMemoryUserDetailsManager(user);
}
}
- .requestMatchers("/anonymous").hasRole("GUEST")
- /anonymous 주소에 대해서는 권한이 GUEST인 사용자만 접근 가능
- .requestMatchers("/anonymousContext","/authentication").permitAll()
- /anonymousContext, /authentication 주소에 대해서 모든 사용자 허용
테스트
- 비로그인, 로그인 각각 /anonymous 접속
- 비로그인 시 anonymous 페이지 정상 접속
- 로그인 시 Whitelabel Error Page
- SecurityConfig에서 .requestMatchers("/anonymous").hasRole("GUEST")설정으로 /anonymous에 접근 가능 권한이 GUEST로 설정했기 때문
- 비로그인, 로그인 각각 /authentication 접속
- 두 가지 모두 null
- 인증 하지 않았더라도 authentication에는 null이 담기기 때문에 anonymous를 반환받지 못함
- 비로그인, 로그인 각각 /anonymousContext 접속
- 비로그인 : guest (Security Context에 저장된 username)
- 로그인 : user (Security Context에 저장된 username)
정리
- 인증 사용자와 익명 사용자를 구분하여 어떤 기능을 수행하고자 할 때 유용
- 인증된 사용자는 접속 하지 못하는 URL → .hasRole(”GUEST”)
- AnonymousAuthenticationFilter에 도달하기 까지 인증을 받지 못한 경우 익명 사용자 객체 생성
→ AnonymousAuthenticationToken - 익명 사용자일 경우 매개변수(Authentication 변수)를 하더라도 변수에는 null이 담기기 때문에 @CurrentSecurityContext 어노테이션을 이용하여 SecurityContext에 접근
'Language > Spring Security' 카테고리의 다른 글
요청 캐시 - RequestCache / SavedRequest (0) | 2024.07.16 |
---|---|
로그 아웃 - logout() (0) | 2024.07.16 |
기억하기 인증 필터 - RememberMeAuthenticationFilter (0) | 2024.07.16 |
기억하기 인증 - rememberMe() (0) | 2024.07.16 |
기본 인증 필터 - BasicAuthenticationFilter (0) | 2024.07.16 |