Spring Security 사용 후 로그인 처리 과정
Spring Security를 사용하기 전에는 서블릿이나 컨트롤러에서 사용자 입력(ID, Password)을 검증하여 처리했습니다.
그러나 Spring Security를 사용하게 되면 앞단에 Spring Security의 AuthenticationManager를 통해 인증처리를 담당합니다.
인증이 성공되면 UserDetails 객체 만들어 Controller에 넘겨줍니다.
- 실패하면 로그인 페이지를 계속해서 클라이언트에게 반환합니다.
자세한 로그인 과정은 아래의 포스팅에서 확인가능합니다.▼
스프링 시큐리티를 통한 간편한 접근 제어 설정
스프링 시큐리티를 사용하면 복잡한 필터를 직접 구현하지 않고도 간편하게 접근 제어를 설정할 수 있습니다.
- 간편한 URL 패턴 관리
- 직접
url.startsWith("/api/user")와 같은 방식으로URL을 필터링하지 않아도 됩니다. - 대신, requestMatchers("/api/user/**"). permitAll()을 사용하여 /api/user로 시작하는 모든 요청을 간단히 허용할 수 있습니다.
- 직접
- 다양한 보안 기능 제공
- 스프링 시큐리티는 hasRole, hasAuthority와 같은 다양한 기능을 통해 세부적인 접근 제어를 지원합니다.
- 예를 들어, 역할 기반으로 접근을 제한하거나, 특정 권한이 있는 사용자만 접근할 수 있도록 설정할 수 있습니다.
// 1. 빈 SecurityContext 생성
SecurityContext context = SecurityContextHolder.createEmptyContext();
// 2. 인증 객체 생성 (주체(principal), 자격 증명(credentials), 권한(authorities) 설정)
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
// 3. 생성한 인증 객체를 SecurityContext에 설정
context.setAuthentication(authentication);
// 4. 설정한 SecurityContext를 SecurityContextHolder에 저장
SecurityContextHolder.setContext(context);
- SecurityContext 생성: SecurityContext는 현재 스레드에서 사용자의 인증 정보를 저장하는 컨테이너 역할을 합니다.
- Authentication 객체 생성: UsernamePasswordAuthenticationToken을 사용하여 인증된 사용자 정보를 담은 Authentication 객체를 생성합니다. 여기에는 사용자의 ID(주체), 자격 증명(비밀번호 등), 권한 정보가 포함됩니다.
- SecurityContext에 인증 객체 설정: 생성한 Authentication 객체를 SecurityContext에 설정하여 현재 사용자에 대한 인증 정보를 저장합니다.
- SecurityContextHolder에 설정: SecurityContextHolder를 사용하여 설정한 SecurityContext를 스레드 로컬에 저장합니다. 이를 통해 현재 스레드에서 사용자 인증 정보를 관리할 수 있습니다.
이 과정을 통해 사용자 인증이 완료되었음을 스프링 시큐리티는 인식하게 됩니다. SecurityContextHolder에 저장된 SecurityContext는 이후 요청 처리 과정에서 사용자 인증 정보를 확인하고, 접근 제어를 수행하는 데 사용됩니다.
Spring Security 로그인 처리 동작 확인
Spring Security 인증 처리 과정
사용 코드_ WebSecurityConfig
WebSecurityConfig는 스프링 시큐리티를 설정하여 HTTP 요청에 대한 보안 규칙과 인증 처리를 정의하는 클래스입니다.
- @Configuration
- 이 클래스가 스프링의 설정 클래스를 나타내며, 스프링 컨테이너에 빈으로 등록될 수 있음을 나타냅니다.
- @EnableWebSecurity
- 스프링 시큐리티의 설정을 활성화하여 보안 필터 체인을 사용할 수 있도록 합니다.
http.csrf((csrf) -> csrf.disable());
테스트를 위해 CSRF(Cross-Site Request Forgery) 공격에 대한 보호를 비활성화했습니다.
HTTP 요청 권한 설정
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정
.requestMatchers("/api/user/**").permitAll() // '/api/user/'로 시작하는 요청 모두 접근 허가
.anyRequest().authenticated() // 그 외 모든 요청 인증처리
);
- requestMatchers(PathRequest.toStaticResources(). atCommonLocations()). permitAll()
- 정적 리소스(예: CSS, JavaScript, 이미지 등)에 대한 접근을 모든 사용자에게 허용합니다.
- requestMatchers("/api/user/**"). permitAll()
- /api/user/로 시작하는 URL에 대해
인증없이 접근을 허용합니다.- 이는 로그인, 회원가입 등의 요청이 인증 없이 접근 가능하도록 합니다.
- /api/user/로 시작하는 URL에 대해
- anyRequest(). authenticated()
- 위의 두 가지 패턴에 포함되지 않는 모든 요청은 인증을 요구합니다.
- 즉,
인증되지 않은 사용자는접근할 수 없습니다.
http.formLogin((formLogin) ->
formLogin
// 로그인 View 제공 (GET /api/user/login-page)
.loginPage("/api/user/login-page")
// 로그인 처리 (POST /api/user/login)
.loginProcessingUrl("/api/user/login")
// 로그인 처리 후 성공 시 URL
.defaultSuccessUrl("/")
// 로그인 처리 후 실패 시 URL
.failureUrl("/api/user/login-page?error")
.permitAll()
);
- loginPage("/api/user/login-page")
- 로그인 페이지 URL을 설정합니다.
- 서버를 실행하고 로그인 페이지로 들어가 보면 위 그림처럼 Security가 제공하는
Default 로그인 페이지가 아닌, 내가 만든 로그인 페이지가 정상적으로 나오는 것을 확인할 수 있습니다.
- loginProcessingUrl("/api/user/login")
- 로그인 처리 요청 URL을 설정합니다.
- 로그인 폼에서 POST 요청을 보낼 때 이 URL이 사용됩니다.
- defaultSuccessUrl("/")
- 로그인 성공 후 리다이렉트될 URL을 설정합니다.
- 기본적으로 홈 페이지로 리다이렉트 됩니다.
- failureUrl("/api/user/login-page? error")
- 로그인 실패 시 리다이렉트 될 URL을 설정합니다.
- 로그인 페이지로 리다이렉트 되며, 쿼리 파라미터 error를 추가하여 실패를 표시합니다.
- permitAll()
- 로그인 페이지 및 로그인 처리 URL에 대한 접근을 모든 사용자에게 허용합니다.
return http.build();
- http.build()
- 설정한 HttpSecurity 객체를 SecurityFilterChain으로 빌드하여 반환합니다.
- 이 객체는 스프링 시큐리티가 HTTP 요청을 처리하는 데 사용됩니다.
사용 코드_ UserDetailsServiceImpl
UserDetailsService 인터페이스를 구현한 UserDetailsServiceImpl 클래스에서 loadUserByUsername메서드를 만듭니다.
- 사용자 조회
- loadUserByUsername 메서드가 호출되면, username을 사용하여 UserRepository에서 사용자 정보를 조회합니다.
- 사용자 존재 여부 확인
사용자가 존재하지 않으면, UsernameNotFoundException을 던져 인증 실패를 처리합니다.
- UserDetailsImpl 생성 및 반환
- 사용자 정보가 존재하면, UserDetailsImpl 객체를 생성하여 반환합니다.
- 이 객체는 스프링 시큐리티의 Authentication 객체에 포함되어 인증 처리를 담당합니다
앞서 알아본 포스팅에서의 Authentication의 principal에는 UserDetials가 들어가 있다고 알아보았습니다.
@AuthenticationPrincipal 어노테이션을 사용하여 Authentication 인증 객체 Principal에 들어있던 UserDetails를 가져온 것입니다.
사용 코드_ ProductController
ProductController 클래스는 인증된 사용자의 정보를 이용하여 /api/products 경로에 대한 GET 요청을 처리합니다.
@AuthenticationPrincipal 어노테이션을 통해 인증된 사용자의 정보를 직접 주입받아 활용하며, 사용자의 정보(사용자 이름과 이메일)를 콘솔에 출력하고 홈 페이지로 리다이렉트 합니다.
"/api/products"로 접속하여 Log를 확인해 보면 user.getUsername()와 getEmail()이 정상적으로 나오는 것을 볼 수 있습니다.
@AuthenticationPrincipal 어노테이션
@AuthenticationPrincipal 어노테이션은 Spring Security에서 인증된 사용자의 정보를 쉽게 주입받을 수 있도록 도와주는 어노테이션입니다.
이 어노테이션은 컨트롤러 메서드의 매개변수에 사용되어 현재 인증된 사용자의 Principal 객체를 주입하는 데 유용합니다.
@Authentication 사용 방법
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.sparta.springauth.security.UserDetailsImpl;
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/profile")
public String getUserProfile(@AuthenticationPrincipal UserDetailsImpl userDetails) {
// 현재 인증된 사용자의 정보
String username = userDetails.getUsername();
String email = userDetails.getEmail();
return "Username: " + username + ", Email: " + email;
}
}
- 컨트롤러 메서드에 주입
- @AuthenticationPrincipal 어노테이션을 메서드 파라미터에 적용하여 인증된 사용자의 정보를 직접 주입받을 수 있습니다.
- 주로 UserDetails나 사용자 정의 UserDetails 구현 클래스를 주입받는 데 사용됩니다.
- 자동 주입
- 스프링 시큐리티가 Authentication 객체에서 Principal을 추출하고, 이를 매개변수로 주입합니다.
@Authentication 주요 특징
- 간편한 접근
- 인증된 사용자의 정보를 컨트롤러 메서드에서 직접 주입받을 수 있어 코드가 간결해지고, 인증 관련 로직을 직접 다룰 필요가 없습니다.
- 유연성
- @AuthenticationPrincipal 어노테이션을 사용하여 UserDetails 또는 사용자가 정의한 인증 객체를 직접 받아올 수 있습니다.
- 주입할 타입
- @AuthenticationPrincipal은 Principal 타입이 아닌 구체적인 UserDetails 타입을 사용할 수 있습니다.
- 이 타입은 스프링 시큐리티의 인증된 사용자 정보를 담고 있는 객체입니다.
@Authentication 내부 동작
- Authentication 객체
- @AuthenticationPrincipal은 현재 SecurityContext에서 Authentication 객체를 가져와 Principal을 추출합니다.
- Principal 객체
- 기본적으로 Principal은 UserDetails 구현체이거나, 사용자 정의 객체일 수 있습니다.
'Framework > Spring Security' 카테고리의 다른 글
[Spring Security] CSRF 토큰을 사용한 Spring Boot의 CSRF 공격 방어하기 (0) | 2024.08.20 |
---|---|
[Spring Security] Spring Security란❓ (0) | 2024.08.04 |