일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- storage #로컬스토리지 #세션스토리지 #백그라운드 서비스
- haproxy #wordpree #php #linux #가상화 #가상머신 #내용정리
- aws #아키텍트 #과정 #vpc #인프라 구축 #public subnet #internet gateway #연결
- aws #아키텍트 #과정 #s3 #bucket #생성 #이미지업로드
- sasac #aws 클라우드 #아키텍트 과정 #가상화 #vmbox #vmware #esxi #tar #selinux
- aws #아키텍트 #과정 #vpc #인프라 구축 #php #웹페이지 #http #public #instance
- aws #아키텍트 #과정 #vpc #인프라 구축 #public subnet #route53 #igw #연결
- 프로세스 #CPU #시공유 #커널
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #머신러닝 #딥러닝 #AI #서버 #자동화 #SQL #기본문법 #데이터베이스
- virtualbox #vmware #router #nat #pat #네트워크 구성도 #aws #ubuntu #
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #OSI #ISO #AI #서버 #자동화 #SQL #기본문법 #데이터베이스 #DBMS #Oracle #MongoDB #아키텍쳐 #DB
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #머신러닝 #AI #서버 #자동화 #SQL #KDT #기본문법 #데이터베이스 #Computer #Science #CPU #메모리
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #머신러닝 #딥러닝 #AI #서버 #자동화 #SQL #기본문법 #데이터베이스 #DBMS #Oracle #MongoDB #아키텍쳐 #DB
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #머신러닝 #AI #서버 #자동화 #SQL #기본문법 #데이터베이스 #웹개발
- tar #build #배포 #통신포트 #설정방법 #linux #apache
- aws #아키텍트 #과정 #vpc #인프라 구축 #public subnet #igw #curl #명령어 #http
- samba #가상머신 #daemon
- 비트 #바이트 #이진수
- 스파르타코딩클럽 #부트캠프 #IT #백엔드 #머신러닝 #AI #서버 #자동화 #SQL #기본문법 #데이터베이스
- aws #아키텍트 #과정 #vpc #인프라 구축 #public subnet #private subnet
- 쓰레드 #쓰레드풀 #프로세스
- 인바운드 #아웃바운드 #방화벽설정
- aws #아키텍트 #과정 #vpc #인프라 구축 #퍼블릭 #보안그룹 #생성 #http #ipv4
- aws #아키텍트 #과정 #vpc #인프라 구축
- aws #클라우드 #퍼블릭 클라우드 #아키텍트 #과정
- mysql #linux #설정 #wordpress #웹사이트 #db 연결 #
- 공간복잡도 #공간자원 #캐시메모리 #SRAM #DRAM #시간복잡도
- ubuntu #설정변경 #vmware #vmbox #linux #명령어
- oracle vmbox #rocky #linux9 #명령어 #암호화인증 #해시알고리즘
- aws #아키텍트 #과정 #vpc #인프라 구축 #퍼블릭 서브넷 #안에 #ec2 인스턴스 #ami #생성 #firewall
- Today
- Total
요리사에서 IT개발자로
(스파르타 코딩클럽) 스프링 시큐리티에서 JWT토큰 인증을 어떻게 할까? 본문
백엔드에서 정의한 WebSecurityConfig클래스에 permitAll 이 아닌 URI로 접근을 할 경우
인증절차가 되는 방법을 알아보고자 한다.
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(HttpMethod.POST, "/api/**").permitAll()
.requestMatchers(HttpMethod.POST, "/api/signup/**").permitAll()
.requestMatchers(HttpMethod.POST, "/api/users/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/challenges/**").permitAll()
.requestMatchers(HttpMethod.POST, "/api/refresh-token").permitAll()
//여기까지는 누구나 접근하는것을 허용했기 때문에
.anyRequest().authenticated())
//위에 허용된게 아니라면?
//이 로직을 탄다
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
스프링 프레임워크에서 제공하는 UsernamePasswordAuthenticationFilter 내부
위에 정의된 jwtAuthorizationFilter에서는
JWT토큰을 검증 및 인가 처리를 하는데.
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
String accessToken = jwtUtil.getTokenFromHeader(JwtUtil.AUTHORIZATION_HEADER, req);
if (StringUtils.hasText(accessToken)) {
try {
Claims info = jwtUtil.getUserInfoFromToken(accessToken);
setAuthentication(info.getSubject());
} catch (ExpiredJwtException e) {
handleExpiredAccessToken(req, res);
return;
} catch (Exception e) {
log.error("Token Error: " + e.getMessage());
SecurityContextHolder.clearContext();
return;
}
}
filterChain.doFilter(req, res);
}
위와 같은 로직을 작성 해놨다.
//getTokenFromHeader의 매개변수를 확인
String accessToken = jwtUtil.getTokenFromHeader(JwtUtil.AUTHORIZATION_HEADER, req);
getTokenFromHeader의 로직은
웹페이지의 개발자 도구를 열어보면 헤더를 넣어준다.
public String getTokenFromHeader(String headerName, HttpServletRequest request) {
//프론트 엔드의 개발자도구를 열어보면 request 부분에 헤더를 넣어준다.
//매개변수로 정의한 AUTHORIZATION_HEADER 를
String token = request.getHeader(headerName);
// 그리고 개발자도구에 제목을 AUTHORIZATION_HEADER로 정의가 되있고
// 토큰의 시작을 BEARER_PREFIX로 시작을 하게되는것이다.
if (StringUtils.hasText(token) && token.startsWith(BEARER_PREFIX)) {
return token.substring(BEARER_PREFIX.length());
}
return null;
}
보면 위와 같이 Authorization이 정의가 되어 있고 Bearer로 시작하는것을 볼 수가 있다.
// 이제 위에서 정의된 헤더와 토큰의 시작 이름을 가지고
if (StringUtils.hasText(accessToken)) {
// 액세스 토큰이 유효한 텍스트인지 확인
try {
// jwtUtil의 getUserInfoFromToken 메서드를 호출하여 액세스 토큰으로부터 클레임 정보를 추출
Claims info = jwtUtil.getUserInfoFromToken(accessToken);
// 추출된 클레임 정보에서 getSubject 메서드를 호출하여 주체(subject) 정보를 얻음
// 주체 정보를 사용하여 인증을 설정
setAuthentication(info.getSubject());
} catch (ExpiredJwtException e) {
handleExpiredAccessToken(req, res);
return;
} catch (Exception e) {
log.error("Token Error: " + e.getMessage());
SecurityContextHolder.clearContext();
return;
}
}
getUserInfoFromToken클래스를 들어가보면
public Claims getUserInfoFromToken(String token) {
// 토큰을 파싱하고 클레임 정보를 반환합니다.
return Jwts.parserBuilder()
.setSigningKey(key) // 서명 키를 설정합니다.
.build()
.parseClaimsJws(token) // 토큰을 파싱합니다.
.getBody(); // 클레임 정보를 가져옵니다.
}
파싱이란
컴퓨터 과학 및 프로그래밍에서 특정 형식으로 구성된 데이터를 분석하고 그 의미를 이해하는 과정을 의미한다.
parserBuider를 들어가보면
parserBuilder는 jwt 라이브러리에서
JWT (JSON Web Token)를 파싱하기 위해 사용하는 메서드
이 메서드는 JWT를 검증하고 클레임을 추출하는 데 필요한 파서를 구성하고 빌드하는 데 사용된다
JWT 파서 생성 및 빌드
Jwts.parserBuilder()는 JWT 파서를 생성하는 첫 단계이다.
이 메서드를 사용하여 서명 키와 같은 설정을 지정하고, 그런 다음 build() 메서드를 호출하여 파서를 빌드합니다.
요약 : 사용자의 액세스 토큰을 프로그래밍언어로 파싱한다는 것을 확인할 수 있다.
파싱을 해야되는 이유
토큰 검증
JWT는 클라이언트와 서버 간의 통신에서 인증과 정보를 안전하게 주고받기 위해 사용됩니다. 토큰을 파싱하는 과정에서 토큰이 유효한지, 서명이 올바른지, 토큰이 만료되지 않았는지를 검증할 수 있습니다.
클레임(Claims) 추출
JWT는 페이로드 부분에 클레임(claims)을 포함합니다. 클레임은 사용자의 정보, 권한, 만료 시간 등 다양한 데이터를 포함할 수 있습니다. 토큰을 파싱함으로써 이 클레임들을 추출하여 사용자의 신원을 확인하거나 권한을 체크할 수 있습니다.
토큰 무결성 보장
JWT는 서명을 포함하여 토큰이 발급된 후 변조되지 않았음을 보장합니다. 토큰을 파싱하고 서명을 검증하는 과정에서 토큰이 변조되지 않았는지 확인할 수 있습니다. 이는 클라이언트가 조작한 토큰을 서버가 검증하지 못하도록 방지하는 중요한 보안 기능입니다.
만료 시간 확인
JWT는 만료 시간을 포함할 수 있습니다. 토큰을 파싱하여 만료 시간을 확인하고, 만료된 토큰을 거부함으로써 보안성을 높일 수 있습니다.
public Claims getUserInfoFromToken(String token) {
// JWT 토큰을 파싱하여 클레임 정보를 추출합니다. 파싱은 'key'로 서명된 토큰을 대상으로 합니다.
return Jwts.parserBuilder()
.setSigningKey(key) // 서명 검증을 위해 키를 설정합니다.
.build()
.parseClaimsJws(token) // 주어진 토큰을 파싱합니다.
.getBody(); // 클레임 정보를 반환합니다.
}
- 파싱은 'key'로 서명된 토큰을 대상으로 합니다:
- Jwts.parserBuilder().setSigningKey(key)는 주어진 키를 사용하여 토큰의 서명을 검증합니다.
- 서명 키는 토큰이 변조되지 않았음을 확인하기 위해 사용됩니다.
- 파싱하고 클레임 정보를 반환합니다:
- parseClaimsJws(token)는 JWT 토큰을 파싱하여 Claims 객체를 생성합니다.
- .getBody()는 토큰의 페이로드에서 클레임 정보를 가져옵니다.
요약
토큰을 파싱하는 이유는 토큰의 유효성을 검증하고, 토큰이 변조되지 않았음을 확인하며, 클레임 정보를 안전하게 추출하여 인증 및 권한 부여를 위한 데이터를 얻기 위함입니다. 이러한 과정은 JWT의 보안과 신뢰성을 보장하는 데 필수적입니다.
서명키란 무엇일까?
서명 키(Signing Key)는
JWT(JSON Web Token)에서 토큰의 무결성을 확인하고 인증하는 데 사용되는 중요한 요소입니다.
서명 키는 토큰이 생성된 후 변조되지 않았는지 검증할 수 있도록 해줍니다.
JWT는 일반적으로 다음 세 가지 부분으로 구성됩니다:
- 헤더(Header): 토큰의 유형(JWT)과 서명 알고리즘(예: HMAC SHA256)을 포함합니다.
- 페이로드(Payload): 토큰의 클레임(Claim)을 포함하는 부분으로, 사용자의 정보나 기타 데이터를 포함할 수 있습니다.
- 서명(Signature): 헤더와 페이로드를 기반으로 생성된 암호화된 문자열로, 토큰이 변조되지 않았음을 보장합니다.
서명 키의 역할
서명 키는 JWT를 생성할 때 사용되며, JWT를 검증할 때도 사용됩니다. 서명 키의 역할은 다음과 같습니다:
- 토큰 생성 시 서명:
- JWT를 생성할 때 헤더와 페이로드를 합친 후 서명 키를 사용하여 서명을 생성합니다.
- 이 서명은 토큰의 끝 부분에 추가되어 토큰의 일부가 됩니다.
- 토큰 검증 시 서명 확인:
- 클라이언트로부터 받은 JWT를 검증할 때, 서명 키를 사용하여 토큰의 서명이 유효한지 확인합니다.
- 서명이 유효하면, 토큰이 생성된 이후로 변조되지 않았음을 보장할 수 있습니다.
서명 키의 종류
서명 키는 대칭 키와 비대칭 키 두 가지로 나눌 수 있습니다:
- 대칭 키(Symmetric Key):
- HMAC 알고리즘을 사용할 때 사용됩니다.
- 같은 키가 토큰 생성과 검증 모두에 사용됩니다.
- 키가 단일한 비밀 값으로 되어 있어 공유할 때 주의가 필요합니다.
- 비대칭 키(Asymmetric Key):
- RSA, ECDSA 알고리즘을 사용할 때 사용됩니다.
- 개인 키(Private Key)와 공개 키(Public Key) 쌍을 사용합니다.
- 개인 키는 토큰을 생성할 때 사용하고, 공개 키는 토큰을 검증할 때 사용합니다.
- 공개 키는 공개할 수 있으므로 공유가 더 안전합니다.
import java.security.Key;
이를 사용하여 key를 설정하였다.
// 추출된 클레임 정보에서 getSubject 메서드를 호출하여 주체(subject) 정보를 얻음
// 주체 정보를 사용하여 인증을 설정
setAuthentication(info.getSubject());
이제 이 부분을 들어가서 SetAuthetication은
private void setAuthentication(String username) {
// 새로운 SecurityContext를 생성
SecurityContext context = SecurityContextHolder.createEmptyContext();
// 주어진 사용자 이름으로 Authentication 객체를 생성
Authentication authentication = createAuthentication(username);
// 생성된 Authentication 객체를 SecurityContext에 설정
context.setAuthentication(authentication);
// SecurityContextHolder에 설정된 SecurityContext를 적용
SecurityContextHolder.setContext(context);
}
setAuthentication 메서드는
스프링 시큐리티(Spring Security)를 사용하여 사용자 인증 정보를 설정하는 데 사용된다.
이 메서드는 주로 JWT를 사용한 인증에서 토큰을 검증한 후,
해당 사용자의 인증 정보를 스프링 시큐리티의 컨텍스트에 설정하는 역할을 합니다.
setAuthentication 메서드의 역할
- JWT 토큰을 검증하고 사용자 정보를 추출:
- JWT 토큰에서 사용자 정보를 추출합니다.
- 추출한 사용자 정보를 사용하여 Authentication 객체 생성:
- 스프링 시큐리티의 Authentication 객체를 생성합니다.
- SecurityContext에 Authentication 객체 설정:
- 스프링 시큐리티의 컨텍스트 홀더(SecurityContextHolder)에 Authentication 객체를 설정하여 현재 사용자 세션을 인증된 상태로 만듭니다.
SecurityContext는 Spring Security를 dependency에 build 하면 사용 할 수 있다.
dependencies {
// security
implementation 'org.springframework.boot:spring-boot-starter-security'
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
제공해주는 SecurityContext에서 추상 클래스로 정의된
SecurityContext는 스프링 시큐리티(Spring Security)에서 인증된 사용자 정보를 저장하는 중요한 인터페이스이다.
이 컨텍스트는 현재 실행 중인 애플리케이션의 모든 스레드에 대한 인증 정보를 보관하고
SecurityContext는 인증된 사용자의 세부 정보를 포함하며,
이 정보를 사용하여 접근 제어와 권한 부여를 관리한다.
// 새로운 SecurityContext를 생성
SecurityContext context = SecurityContextHolder.createEmptyContext();
SecurityContextHolder.createEmptyContext() 메서드는 새로운 빈 SecurityContext 객체를 생성한다. 이 메서드는 현재 스레드에 할당된 SecurityContext를 새로 만들고 초기화할 때 사용된다.
SecurityContext의 createEmptyContext() 클래스로 정의가 되있는데
그 전에 strategy는?
- SecurityContextHolder는 스프링 시큐리티에서 현재 인증된 사용자의 SecurityContext를 관리합니다.
- 기본적으로 ThreadLocal 전략을 사용하여 각 스레드마다 별도의 SecurityContext를 유지합니다.
- SecurityContextHolderStrategy 인터페이스를 통해 다른 전략으로 변경할 수 있습니다.
- 전략을 변경하려면 SecurityContextHolder.setStrategyName 메서드를 사용합니다.
SecurityContextHolderStrategy클래스로 정의가 되어있는데
SecurityContextHolderStrategy란
SecurityContext를 저장하고 검색하는 방법을 정의하는 인터페이스이다.
스프링 시큐리티는 기본적으로 ThreadLocalSecurityContextHolderStrategy를 사용하지만,
필요에 따라 다른 전략으로 변경할 수 있습니다.
인터페이스로 정의가 되어있고
SecurityContext의 인터페이스 클래스를 createEmptyContext로 사용하고 있다.
SecurityContext의 역할
- SecurityContext: 인증된 사용자 정보를 보관합니다. 보통 Authentication 객체를 포함하며, 이 객체는 인증된 사용자 정보 및 권한을 포함한다.
- SecurityContextHolder: 현재 스레드의 SecurityContext를 제공하는 역할을 합니다. SecurityContextHolder는 ThreadLocal을 사용하여 각 스레드마다 별도의 SecurityContext를 유지한다.
createEmptyContext() 메서드는
새로운 빈 SecurityContext 객체를 생성하는 정적 메서드입니다.
이 메서드는 새로운 인증 컨텍스트를 초기화할 때 유용합니다.
ThreadLocal
자바에서 제공하는 클래스로, 각 스레드가 고유한 변수 값을 가질 수 있도록 하고
ThreadLocal을 사용하면 여러 스레드가 동시에 실행되는 멀티스레드 환경에서 스레드 간에 변수를 공유하지 않고, 각 스레드가 독립적인 변수 값을 유지할 수 있다.
다시 로직으로 넘어와서
// 주어진 사용자 이름으로 Authentication 객체를 생성
Authentication authentication = createAuthentication(username);
Authentication은 스프링 시큐리티(Spring Security)에서 사용자의 인증 정보를 나타내는 인터페이스.
이 인터페이스는 인증된 사용자의 정보를 포함하며, 스프링 시큐리티 프레임워크 내에서 사용자의 신원을 증명하고 권한을 부여하는 데 사용됩니다.
// 인증 객체 생성
private Authentication createAuthentication(String username) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
Authentication 인터페이스는 다음과 같은 주요 메서드와 필드를 포함합니다:
- getPrincipal(): 인증된 사용자의 주체를 반환합니다. 일반적으로 UserDetails 객체 또는 사용자 이름이 될 수 있습니다.
- getCredentials(): 사용자의 자격 증명을 반환합니다. 비밀번호와 같은 정보가 포함될 수 있습니다.
- getAuthorities(): 사용자의 권한(roles)을 반환합니다. 이는 사용자가 시스템에서 수행할 수 있는 작업을 결정합니다.
- isAuthenticated(): 사용자가 인증되었는지를 나타내는 부울 값을 반환합니다.
- setAuthenticated(boolean isAuthenticated): 인증 상태를 설정합니다.
- getDetails(): 인증 요청에 대한 추가 세부 정보를 반환합니다. 일반적으로 WebAuthenticationDetails와 같은 객체가 포함됩니다.
UserDetailServiceImpl은 UserDetailsService인터페이스를 구현하고있다.
UserDetailsService는 스프링 시큐리티(Spring Security)에서 제공하는 인터페이스로,
사용자 정보를 로드하는 역할을 한다.
이 인터페이스는 사용자 인증을 처리하기 위해 필수적인 사용자 세부 정보를 제공하는 메서드를 정의하고
주로 사용자 이름을 기반으로 사용자 정보를 로드하는 데 사용됩니다.
UserDetailsService 인터페이스
UserDetailsService 인터페이스는 다음과 같은 메서드를 정의합니다:
- UserDetails loadUserByUsername(String username) throws UsernameNotFoundException:
- 주어진 사용자 이름을 기반으로 사용자 정보를 로드합니다.
- 사용자 정보를 찾을 수 없으면 UsernameNotFoundException을 던집니다.
UserDetailsService의 역할
- 사용자 로드:
- 사용자 이름을 기반으로 데이터베이스나 다른 저장소에서 사용자 정보를 로드합니다.
- UserDetails 객체 반환:
- 로드된 사용자 정보를 기반으로 UserDetails 객체를 생성하여 반환합니다. 이 객체는 사용자 이름, 비밀번호, 권한 등의 정보를 포함합니다.
UserDetailsImpl 또한 UserDetails 인터페이스를 구현하고 있어 서
해당 new UserDetailsImpl이 UserDetails로 return 될 수 있는것이다.
DB에 유저정보가 저장되있다면
// 주어진 사용자 이름으로 Authentication 객체를 생성
Authentication authentication = createAuthentication(username);
// 생성된 Authentication 객체를 SecurityContext에 설정
context.setAuthentication(authentication);
context의 setAuthentication메소드 안의 매개변수로 userRepository에 저장된
User 객체를 기반으로 Authentication 객체를 생성하여
이를 SecurityContext에 설정해야 한다.
인증된 유저라고 생각하면된다.
public interface SecurityContext extends Serializable {
setContext메소드 안의 인증된 객체를 넣고 감싸서 extends를 하고있는
Serializable을 적용시켜서
Serializable?
Java에서 객체 직렬화를 가능하게 하는 인터페이스.
JSON 객체를 직렬화하여 JWT로 변환하는 과정을 의미
JWT란?
JWT는 JSON 객체를 인코딩하여 직렬화된 토큰 형식.
주로 웹 애플리케이션에서 클라이언트와 서버 간에 안전하게 정보를 전송하는 데 사용된다.
직렬화(Serialization)는 객체를 바이트 스트림으로 변환하여 파일 저장, 네트워크 전송 등을 할 수 있게 한다.반대로 역직렬화(Deserialization)는 바이트 스트림을 다시 객체로 변환하는 과정.
// 생성된 Authentication 객체를 SecurityContext에 설정
context.setAuthentication(authentication);
context.setAuthentication(authentication)을 사용하여
직렬화된 인증 객체를 설정을 하고
이를 통해 현재 스레드의 보안 컨텍스트에 인증 정보를 저장하고 관리할 수 있다.
// SecurityContextHolder에 설정된 SecurityContext를 적용
SecurityContextHolder.setContext(context);
SecurityContextHolder의 setContext 메서드를 사용하여
현재 스레드의 보안 컨텍스트(SecurityContext)에 Authentication 객체를 설정함으로써 인증 처리를 한다.
이를 통해 스프링 시큐리티는 인증된 사용자 정보를 유지하고,
애플리케이션의 다른 부분에서 이 정보를 사용할 수 있게 한다.
정리하자면
Authorization의 Bearer을
//getTokenFromHeader의 매개변수를 확인
String accessToken = jwtUtil.getTokenFromHeader(JwtUtil.AUTHORIZATION_HEADER, req);
Authorization을 매개변수로 주고
public String getTokenFromHeader(String headerName, HttpServletRequest request) {
String token = request.getHeader(headerName);
// 토큰이 유효한 텍스트인지 확인하고, "Bearer "로 시작하는지 검사
if (StringUtils.hasText(token) && token.startsWith(BEARER_PREFIX)) {
// "Bearer " 접두사를 제거하고 실제 토큰 부분만 반환
return token.substring(BEARER_PREFIX.length());
}
return null;
}
HeaderName과 HeaderName의 시작이 Bearer이라면 실제 토큰부분만 반환한다.
String accessToken = jwtUtil.getTokenFromHeader(JwtUtil.AUTHORIZATION_HEADER, req);
// 이제 위에서 정의된 헤더와 토큰의 시작 이름을 가지고
if (StringUtils.hasText(accessToken)) {
// 액세스 토큰이 유효한 텍스트인지 확인
try {
// jwtUtil의 getUserInfoFromToken 메서드를 호출하여 액세스 토큰으로부터 클레임 정보를 추출
Claims info = jwtUtil.getUserInfoFromToken(accessToken);
// 추출된 클레임 정보에서 getSubject 메서드를 호출하여 주체(subject) 정보를 얻음
// 주체 정보를 사용하여 인증을 설정
setAuthentication(info.getSubject());
} catch (ExpiredJwtException e) {
// 토큰이 만료된 경우 처리
handleExpiredAccessToken(req, res);
return;
} catch (Exception e) {
// 기타 예외 처리
log.error("Token Error: " + e.getMessage());
SecurityContextHolder.clearContext();
return;
}
}
filterChain.doFilter(req, res);
}
전체 로직
@Slf4j(topic = "JWT 검증 및 인가")
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailServiceImpl userDetailsService;
public JwtAuthorizationFilter(JwtUtil jwtUtil, UserDetailServiceImpl userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
// 헤더에서 액세스 토큰을 가져옵니다.
String accessToken = jwtUtil.getTokenFromHeader(JwtUtil.AUTHORIZATION_HEADER, req);
// 액세스 토큰이 존재하는지 확인합니다.
if (StringUtils.hasText(accessToken)) {
try {
// 토큰에서 사용자 정보를 추출합니다.
Claims info = jwtUtil.getUserInfoFromToken(accessToken);
// 추출한 사용자 정보로 인증을 설정합니다.
setAuthentication(info.getSubject());
} catch (ExpiredJwtException e) {
// 액세스 토큰이 만료된 경우 처리합니다.
handleExpiredAccessToken(req, res);
return;
} catch (Exception e) {
// 기타 예외가 발생한 경우 로그에 에러 메시지를 출력하고, 보안 컨텍스트를 지웁니다.
log.error("Token Error: {}", e.getMessage(), e);
SecurityContextHolder.clearContext();
return;
}
}
// 다음 필터를 호출합니다.
filterChain.doFilter(req, res);
}
private void handleExpiredAccessToken(HttpServletRequest req, HttpServletResponse res) throws IOException {
// 쿠키에서 리프레시 토큰을 가져옵니다.
String refreshToken = getRefreshTokenFromCookies(req);
// 리프레시 토큰이 유효한지 확인합니다.
if (StringUtils.hasText(refreshToken) && jwtUtil.validateToken(refreshToken)) {
// 리프레시 토큰을 사용하여 새로운 액세스 토큰을 생성합니다.
String newAccessToken = jwtUtil.refreshAccessToken(refreshToken);
// 새로운 액세스 토큰을 헤더에 추가합니다.
jwtUtil.addJwtToHeader(JwtUtil.AUTHORIZATION_HEADER, JwtUtil.BEARER_PREFIX + newAccessToken, res);
// 새로운 액세스 토큰에서 사용자 정보를 추출합니다.
Claims info = jwtUtil.getUserInfoFromToken(newAccessToken);
// 추출한 사용자 정보로 인증을 설정합니다.
setAuthentication(info.getSubject());
} else {
// 리프레시 토큰이 유효하지 않으면, 401 Unauthorized 상태를 반환합니다.
res.setStatus(HttpStatus.UNAUTHORIZED.value());
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
res.getWriter().write("{\"message\":\"리프레시 토큰으로 재발급 받으세요\"}");
}
}
private void setAuthentication(String username) {
// 새로운 SecurityContext를 생성합니다.
SecurityContext context = SecurityContextHolder.createEmptyContext();
// 주어진 사용자 이름으로 Authentication 객체를 생성합니다.
Authentication authentication = createAuthentication(username);
// 생성된 Authentication 객체를 SecurityContext에 설정합니다.
context.setAuthentication(authentication);
// SecurityContextHolder에 설정된 SecurityContext를 적용합니다.
SecurityContextHolder.setContext(context);
}
private Authentication createAuthentication(String username) {
// 주어진 사용자 이름으로 사용자 세부 정보를 로드합니다.
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 로드된 사용자 세부 정보를 기반으로 Authentication 객체를 생성하여 반환합니다.
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
private String getRefreshTokenFromCookies(HttpServletRequest req) {
// 요청에서 쿠키 배열을 가져옵니다.
Cookie[] cookies = req.getCookies();
if (cookies != null) {
// 쿠키 배열을 순회하며 "refreshToken" 이름을 가진 쿠키를 찾습니다.
for (Cookie cookie : cookies) {
if (cookie.getName().equals(JwtUtil.REFRESH_HEADER)) {
// "refreshToken" 쿠키를 찾으면 해당 값을 반환합니다.
return cookie.getValue();
}
}
}
// "refreshToken" 쿠키가 없으면 null을 반환합니다.
return null;
}
https://velog.io/@mercurios0603/%ED%8C%8C%EC%8B%B1Parsing%EC%9D%B4%EB%9E%80
https://2damlee.github.io/springboot/SpringSecurity/
'TIL' 카테고리의 다른 글
(스파르타 코딩클럽) 면접예상질문 Spring AOP란? (0) | 2024.08.13 |
---|---|
(스파르타 코딩클럽)Vue.js 와 SpringBoot 네이버 소셜로그인 연동하기 (0) | 2024.08.10 |
(스파르타 코딩클럽) TDD (0) | 2024.08.02 |
(스파르타 코딩클럽) CI/CD란 (0) | 2024.08.02 |
(스파르타 코딩클럽)GET, POST의 개념과 데이터 흐름 (2) | 2024.07.29 |