Bean
↓
인증(Authentication) / 인가(Authorization)
↓
Cookie / Session
↓
JWT
↓
Filter
↓
Spring Security
↓
Validation
Spring IoC Container가 생성하고 관리하는 객체
일반적으로 @Component, @Service, @Repository, @Controller 등을 사용하여 자동 등록됨
@Configuration + @Bean 사용
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
비즈니스 로직보다는 기술적인 기능이나 공통 관심사를 처리하는 객체에 적합하다.
@Configuration
↓
@Bean
↓
Spring IoC Container에 Bean 등록
같은 타입의 Bean이 여러 개 존재하면 @Autowired만으로 어떤 Bean을 주입해야 하는지 판단하기 어려움
@Primary여러 Bean 중 기본적으로 주입할 Bean 지정
@Primary
@Component
public class Chicken implements Food {
}
@Qualifier특정 Bean을 명시적으로 지정.
@Qualifier("pizza")
Food food;
@Qualifier
↓
@Primary
같은 타입의 Bean이 여러 개라면
@Primary@Qualifier사용자가 실제로 누구인지 확인하는 과정
예시
인증된 사용자가 특정 리소스에 접근할 권한이 있는지 확인하는 과정
예시
인증
"너 누구야?"
↓
인가
"너 이거 볼 수 있어?"
HTTP 통신은 기본적으로 요청과 응답이 끝나면 연결을 종료하는 방식
→ 서버의 리소스 절약
서버가 이전 요청의 상태를 저장하지 않는 특성
즉, 서버는 현재 요청만으로 사용자의 이전 상태를 알 수 없다.
로그인한 사용자가 다음 요청을 보냈을 때
"이 사용자가 로그인한 사용자인가?"
를 서버가 알 수 있어야 함.
→ Cookie / Session / JWT 등의 인증 방식 사용
클라이언트에 저장되는 작은 데이터
브라우저에 저장되며 서버와 통신할 때 함께 전달 가능하다.
서버에서 클라이언트의 상태를 유지하기 위한 저장 방식
서버가 사용자의 정보를 저장하고, 클라이언트에는 Session ID를 전달
Client
↓
Login 요청
↓
Server
↓
Session 생성
↓
Session ID 발급
↓
Cookie에 Session ID 저장
↓
이후 요청마다 Session ID 전달
↓
Server가 Session 확인
| 구분 | Cookie | Session |
|---|---|---|
| 저장 위치 | Client | Server |
| 주요 역할 | 데이터 저장 | 사용자 상태 유지 |
| 대표 사용 | 팝업 설정 등 | 로그인 |
| 식별 정보 | Cookie Value | Session ID |
JSON Web Token
인증에 필요한 정보를 토큰에 담아 사용하는 인증 방식
Cookie-Session 방식과 달리 로그인 상태를 서버에 저장하지 않고 토큰 자체에 정보를 포함할 수 있다
로그인 요청
↓
ID / Password 확인
↓
JWT 생성
↓
Client에 JWT 전달
↓
Client가 JWT 저장
↓
이후 요청마다 JWT 전달
↓
Server에서 JWT 검증
↓
인증 완료
JWT는 다음 세 부분으로 구성됨
Header.Payload.Signature
토큰의 타입과 암호화 알고리즘 등의 정보
사용자 정보 등의 Claim 저장
{
"sub": "username",
"auth": "ROLE_USER"
}
Header + Payload를 Secret Key로 서명한 값
토큰이 변조되었는지 검증하는 데 사용한다.
JWT의 Payload는 암호화되어 있는 것이 아님.
따라서 누구나 Payload를 확인할 수 있다.
Secret Key가 필요한 부분은 Signature 검증 및 위변조 방지.
→ 비밀번호와 같은 민감한 정보를 Payload에 저장하면 안 된다.
JWT를 사용할 때 필요한 기능
Bearer 제거JWT
↓
Signature 검증
↓
만료 여부 확인
↓
정상 JWT인지 확인
회원가입 시 비밀번호를 평문으로 DB에 저장하면 안 됨.
평문 비밀번호
↓
암호화
↓
암호문
↓
DB 저장
암호화는 가능하지만 복호화가 불가능한 방식
평문 → 암호문
↑
X
복호화 불가능
비밀번호 저장에는 일반적으로 단방향 해시 방식 사용한다.
Spring Security에서 제공하는 비밀번호 암호화 기능
passwordEncoder.encode(password);
로그인 시에는
passwordEncoder.matches(
입력된 비밀번호,
DB에 저장된 암호화된 비밀번호
);
형태로 비교.
사용자 역할은 Enum으로 관리 가능하다.
USER
ADMIN
Spring Security에서 권한을 사용할 때는 일반적으로 ROLE_ 접두사 사용한다.
ROLE_USER
ROLE_ADMIN
Client의 요청과 Server의 응답 사이에서 공통적인 작업을 처리하는 역할
주로 다음과 같은 작업에 사용한다.
인증이나 로깅 같은 공통 기능을 Controller의 비즈니스 로직과 분리 가능.
Filter는 하나만 존재하는 것이 아니라 여러 Filter가 Chain 형태로 연결될 수 있다.
Request
↓
Filter 1
↓
Filter 2
↓
Filter 3
↓
Controller
↓
Response
chain.doFilter()현재 Filter의 처리가 끝난 후 다음 Filter로 요청 전달한다.
Spring 애플리케이션의 인증과 인가를 처리하기 위한 보안 프레임워크
직접 Filter를 구현하는 것보다 인증/인가 기능을 편리하게 구성할 수 있다.
Spring Security는 여러 Filter를 연결하여 인증과 인가를 처리한다.
Client
↓
Spring Security Filter Chain
↓
인증 / 인가
↓
Controller
인증이 실패하면 Controller까지 요청이 전달되지 않음.
현재 인증된 사용자의 정보를 저장하는 공간
SecurityContextHolder
↓
SecurityContext
↓
Authentication
↓
UserDetails
현재 인증된 사용자를 나타내는 객체
주요 정보
Spring Security가 사용하는 인증된 사용자 정보 객체.
사용자 정보와 권한을 Spring Security에서 사용할 수 있는 형태로 제공한다.
사용자 이름을 기반으로 DB에서 사용자를 조회하고 UserDetails로 변환하는 역할
username
↓
UserDetailsService
↓
DB에서 User 조회
↓
UserDetails 생성
↓
Authentication
@AuthenticationPrincipalController에서 현재 인증된 UserDetails를 쉽게 가져올 수 있음
@GetMapping("/products")
public String getProducts(
@AuthenticationPrincipal UserDetailsImpl userDetails
) {
...
}
로그인 요청을 처리하고 JWT를 생성하는 Filter
로그인 요청
↓
username / password 확인
↓
AuthenticationManager
↓
인증 성공
↓
JWT 생성
↓
Cookie에 JWT 저장
API 요청에 포함된 JWT를 검증하고 인증 정보를 설정하는 Filter
API 요청
↓
JWT 추출
↓
JWT 검증
↓
사용자 정보 추출
↓
Authentication 생성
↓
SecurityContextHolder에 저장
↓
Controller
| Filter | 역할 |
|---|---|
JwtAuthenticationFilter | 로그인 인증 및 JWT 생성 |
JwtAuthorizationFilter | JWT 검증 및 API 접근 인가 |
JWT 방식에서는 서버에 Session을 저장하지 않고 JWT를 사용하여 인증 상태를 유지
.sessionCreationPolicy(
SessionCreationPolicy.STATELESS
)
Client
↓
Session ID
↓
Server Session
Client
↓
JWT
↓
Server에서 검증
Spring Security에서 사용자의 권한을 GrantedAuthority 형태로 관리한다.
ROLE_USER
ROLE_ADMIN
SimpleGrantedAuthoritynew SimpleGrantedAuthority("ROLE_ADMIN");
사용자의 권한 정보를 Spring Security가 이해할 수 있는 형태로 변환한다.
@SecuredController의 특정 API에 필요한 권한을 지정할 수 있다.
@Secured(UserRoleEnum.Authority.ADMIN)
@GetMapping("/products/secured")
public String getProductsByAdmin() {
...
}
ROLE_ADMIN → 접근 가능
ROLE_USER → 접근 불가
즉, 인증(Authentication)과 인가(Authorization)를 분리해서 생각하는 것이 중요하다.
Client가 전달한 데이터가 올바른 형식인지 검증하는 과정
잘못된 데이터가 Service나 DB까지 전달되지 않도록 요청 단계에서 검증한다.
| Annotation | 의미 |
|---|---|
@NotNull | null 불가 |
@NotEmpty | null, 빈 문자열 불가 |
@NotBlank | null, 빈 문자열, 공백 불가 |
@Size | 문자열 길이 검증 |
@Max | 최대값 |
@Min | 최소값 |
@Positive | 양수 |
@Negative | 음수 |
@Email | 이메일 형식 |
@Pattern | 정규식 검증 |
@ValidDTO에 설정한 Validation 조건을 실제로 검사하도록 하는 역할
@PostMapping("/validation")
public ProductRequestDto testValid(
@RequestBody @Valid ProductRequestDto requestDto
) {
return requestDto;
}
Client Request
↓
DTO
↓
@Valid
↓
Validation
↓
정상 → Controller 처리
오류 → Validation Exception
Validation 과정에서 발생한 오류 정보를 확인할 때 사용한다.
public String signup(
@Valid SignupRequestDto requestDto,
BindingResult bindingResult
) {
...
}
FieldError를 통해
확인 가능.
@Component@Bean@Configuration@Primary@QualifierBearer@AuthenticationPrincipal@Securedchain.doFilter()@Valid@NotBlank@Email@Size@Positive@NegativeBindingResultFieldErrorSpring에서 인증과 인가를 처리하는 전체 흐름을 이해하는게 제일 힘들다...
흐름 기억하기!!
회원가입
↓
비밀번호 암호화
↓
DB 저장
↓
로그인
↓
Authentication
↓
JWT 생성
↓
Client에 전달
↓
API 요청
↓
JWT 검증
↓
SecurityContext에 Authentication 저장
↓
Controller 접근
↓
권한 확인