- 인증 과정
- 사용자가 username과 password를 제출하면 UsernamePasswordAuthenticationFilter는 인증된 사용자의 정보가 담기는 인증 객체인 Authentication의 종류 중 하나인 UsernamePasswordAuthenticationToken을 만들어 AuthenticationManager에게 넘겨 인증을 시도합니다.
- 실패하면 SecurityContextHolder를 비웁니다.
- 성공하면 SecurityContextHolder에 Authentication를 세팅합니다.
Authentication 객체의 구성 요소
- Authentication이란? 현재 인증된 사용자를 나타내며 SecurityContext에서 가져올 수 있습니다.
- principal:
- 의미: 현재 인증된 사용자를 식별하는 정보입니다.
- 일반적인 타입: UserDetails 인스턴스가 일반적입니다. UserDetails는 사용자에 대한 정보를 담고 있는 인터페이스로, 사용자 이름(username), 비밀번호(password), 권한(authorities) 등을 포함합니다.
- 설명: 로그인 시 제공된 사용자 이름(username) 또는 사용자 객체(user)를 나타냅니다. 인증이 완료된 후에는 UserDetails 인스턴스가 여기에 저장됩니다.
- credentials:
- 의미: 인증 과정에서 사용되는 자격 증명입니다. 보통 비밀번호를 의미합니다.
- 일반적인 타입: 비밀번호 문자열(String)입니다.
- 설명: 인증 과정에서 사용된 비밀번호가 여기에 저장됩니다. 인증이 완료된 후에는 이 정보는 일반적으로 비워집니다. 이는 보안상의 이유로 비밀번호를 더 이상 저장할 필요가 없기 때문입니다.
- authorities:
- 의미: 인증된 사용자에게 부여된 권한을 나타냅니다.
- 일반적인 타입: GrantedAuthority 인터페이스를 구현한 객체들의 컬렉션입니다. GrantedAuthority는 사용자의 권한을 추상화하는 역할을 합니다.
- 설명: 사용자가 수행할 수 있는 작업의 범위를 정의합니다. 예를 들어, ROLE_USER, ROLE_ADMIN 등의 권한이 이에 해당합니다. 권한은 인증 후에 설정되며, Authentication 객체가 사용자의 권한을 기반으로 접근 제어를 수행할 수 있게 해줍니다.
package com.sparta.myselectshop.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.dto.LoginRequestDto;
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.jwt.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.io.IOException;
@Slf4j(topic = "로그인 및 JWT 생성")
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
// 이 필터가 /api/user/login 경로에서만 작동하도록 설정
setFilterProcessesUrl("/api/user/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
requestDto.getUsername(),
requestDto.getPassword(),
null
)
);
} catch (IOException e) {
log.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
String username = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
UserRoleEnum role = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getRole();
String token = jwtUtil.createToken(username, role);
response.addHeader(JwtUtil.AUTHORIZATION_HEADER, token);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
response.setStatus(401);
}
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
requestDto.getUsername(),
requestDto.getPassword(),
null
)
);
} catch (IOException e) {
log.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
ObjectMapper란?
- ObjectMapper는 Jackson 라이브러리에서 제공하는 클래스입니다. Jackson은 Java 객체와 JSON 간의 변환을 쉽게 할 수 있도록 도와주는 라이브러리입니다.
- ObjectMapper는 JSON 데이터를 Java 객체로 역직렬화(deserialize)하거나, Java 객체를 JSON 형식으로 직렬화(serialize)하는 데 사용됩니다.
request.getInputStream()
- request.getInputStream()은 HttpServletRequest 객체에서 요청 본문을 읽어들이는 데 사용됩니다.
- 이 메서드는 요청 본문을 바이트 스트림 형태로 반환합니다. 주로 POST, PUT, PATCH와 같은 HTTP 메서드에서 JSON 데이터를 서버로 전송할 때 사용됩니다.
- 예를 들어, 클라이언트가 로그인 요청을 보낼 때 사용자 이름과 비밀번호를 JSON 형식으로 본문에 담아 보낼 수 있습니다.
new ObjectMapper().readValue()
- ObjectMapper의 readValue 메서드는 JSON 형식의 데이터를 읽어 Java 객체로 변환하는 역할을 합니다.
- 이 메서드는 두 가지 인수를 받습니다:
- InputStream: JSON 데이터를 담고 있는 입력 스트림 (request.getInputStream()).
- Class<T>: JSON 데이터를 변환할 Java 클래스 (LoginRequestDto.class).
LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
즉, 위 코드는 클라이언트로부터 받은 JSON 데이터를 LoginRequestDto 객체로 변환합니다.
getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
requestDto.getUsername(),
requestDto.getPassword(),
null
)
UsernamePasswordAuthenticationToken은 스프링 시큐리티 Authentication 객체의 구현체이므로 principal, credentials, Authorities 필드를 갖습니다.
UsernamePasswordAuthenticationToken을 생성할 때 세 번째 인자인 authorities를 null로 설정하는 이유 :
- 초기 인증 요청: 초기 인증 요청 시점에서는 사용자의 권한이 결정되지 않았기 때문에, authorities는 null로 설정됩니다. 인증 관리자(authentication manager)는 이 객체를 사용하여 인증을 시도하고, 인증이 성공하면 권한을 설정한 후에 Authentication 객체를 반환합니다.
- 권한 설정은 인증 후에: 권한(authorization)은 인증(authentication)이 완료된 후, 즉 사용자가 성공적으로 로그인한 후에 설정됩니다. 따라서 초기 UsernamePasswordAuthenticationToken 객체는 권한 정보가 필요 없으며, 인증이 완료된 후에 권한이 설정된 Authentication 객체로 대체됩니다.
'스프링 시큐리티' 카테고리의 다른 글
삭제된 유저 로그인 실패 처리하는 법 (0) | 2024.08.28 |
---|