JWT란
JWT는 "JSON Web Token"의 약자로, 웹 통신에서 사용되는 인증과 관련된 개념입니다. 즉, JWT는 클라이언트와 서버 간의 정보를 안전하게 전송하기 위해 사용됩니다.
- JWT 구성
헤더(Header)
페이로드(Payload)
서명(Signature)
각 부분은 Base64로 인코딩되어 하나의 문자열로 표현됩니다.
- JWT의 장점
Stateless - 세션 상태 관리 없이도 사용자 상태를 유지할 수 있으므로 확장성이 용이합니다.데이터 자체 포함 - 필요한 모든 정보가 자체적으로 포함되어 있으므로 별도의 데이터베이스 조회 없이도 필요한 정보에 접근할 수 있습니다. 범용화 - 다양한 프로그래밍 언어와 프레임워크에서 지원하며, 쉽게 구현할 수 있습니다.
하지만 주의해야 할 점도 있습니다:
보안 설정 - 비밀키(secret key)를 안전하게 보관하고 관리해야 합니다. 크기 제약 - JWT에 담길 수 있는 데이터 크기에 제약이 있는 경우도 있으므로 적절히 활용해야 합니다.
JWT.IO
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
jwt.io
dependencies {
... 생략
// JWT 라이브러리
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2'
}
- io.jsonwebtoken 패키지의 라이브러리들은 JWT 생성, 검증 및 처리를 위해 필요합니다.
- jjwt-api는 JWT의 API 인터페이스를 제공하고, jjwt-impl은 실제 구현체를 포함합니다.
- jjwt-jackson은 JSON 직렬화 및 역직렬화를 위한 Jackson 라이브러리와의 통합을 제공합니다.
JWT 구축하기 (필터로 구현)
작업 순서
- JwtUtil 클래스 생성하기
- 필터 구현하기 - Request
- 필터를 특정 URI 패턴에만 동작 처리 하기
- 로그인 시 JWT 토근 생성 및 응답 처리 하기
- JWT 테스트
package com.tencoding.todo.utils;
import java.util.Date;
import javax.crypto.SecretKey;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
@Component // Ioc 대상 된다.
public class JwtUtil {
// 시크릿 키를 생성 - xxkdk --> 동적 생성 ()
private final SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS512);
private final long EXP_TIME = 86400000L; // 유효 시간 1일
// 토큰 생성하는 메서드를 만들어야 한다.
public String generateToken(String userEmail, Integer userId) {
return Jwts.builder()
.claim("userEmail", userEmail)
.claim("userId", userId)
.setExpiration(new Date(System.currentTimeMillis() + EXP_TIME))
.signWith(key)
.compact();
}
// 토큰이 유효한지 검증 메서드를 만들어야 한다.
public Boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
// 유저에 정보를 뽑고 싶은 메서드를 만들어야 한다. - email
public String getUserEmailFromToken(String token) {
Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws("userEmail");
return claimsJws.getBody().get("userEmail", String.class);
}
// 유저 정보에서 userId를 뽑는 메서드를 만들어 볼 예정
public String getUserIdFromToken(String token) {
Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws("userId");
return claimsJws.getBody().get("userId", String.class);
}
}
필터 만들어 보기 - JwtUtil 클래스를 활용
package com.tencoding.todo.filter;
import java.io.IOException;
import java.net.http.HttpResponse;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.tencoding.todo.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class JwtRequestFilter implements Filter {
@Autowired
private JwtUtil jwtUtil;
// doFilter -> 약속 : 요청이 오면 반드시 doFilter가 호출 된다.
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("JWT Filter 동작 확인 - 1");
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
String authHader = httpServletRequest.getHeader("Authorization");
if(authHader != null) {
// 토큰 존재 여부 확인
if(!authHader.startsWith("Bearer ")) {
sendError(response);
return;
}
// 토큰 존재 여부 확인
if(!authHader.startsWith("Bearer ")) {
sendError(response);
return;
}
String jwtToken = authHader.substring(7);
// 토큰 유효성 확인
if(!jwtUtil.validateToken(jwtToken)) {
sendError(response);
return;
}
}
log.info("JWT Filter 동작 확인 - 2");
chain.doFilter(request, response);
}
private void sendError(ServletResponse response) throws IOException {
HttpServletResponse httpServletResponse = (HttpServletResponse)response;
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid Token");
}
}
3. 필터를 특정 URI 패턴에만 동작 처리 하기
package com.tencoding.todo.filter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
// 어노테이션은 해당 클래스가 스프링 프레임워크의 설정 정보를 담는 클래스를 나타낸다.
// 이 Configuration @Component을 상속 받고 있다.
// 이 클래스에 내부에서 Bean 객체를 더 생성해야 할 때 사용
@Slf4j
@Configuration
public class FilterConfig {
// 1. 우리가 정의한 JWT 관련된 필터 동작 객체를 생성자 의존 주의 받는다.
@Autowired
private JwtRequestFilter jwtRequestFilter;
// 특정 메서들 만들어서 URI 패턴을 등록 처리
@Bean
public FilterRegistrationBean<JwtRequestFilter> loggingFilter() {
log.error("스프링 부트 구동시 초기화 확인 - 1");
FilterRegistrationBean<JwtRequestFilter> registrationBean
= new FilterRegistrationBean<>();
registrationBean.setFilter(jwtRequestFilter);
registrationBean.addUrlPatterns("/todos/*");
return registrationBean;
}
}
UserController
package com.tencoding.todo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.function.ServerRequest.Headers;
import com.tencoding.todo.dto.UserDTO;
import com.tencoding.todo.repository.entity.UserEntity;
import com.tencoding.todo.service.UserService;
import com.tencoding.todo.utils.JwtUtil;
@RestController
@RequestMapping("/user")
public class UserController {
private final UserService userService;
// 코드 추가
private final JwtUtil jwtUtil;
// 코드 수정
@Autowired
public UserController(UserService userService, JwtUtil jwtUtil) {
this.userService = userService;
this.jwtUtil = jwtUtil;
}
// 주소 설계
// 회원 가입 요청 -- form, HTTP Message body
@PostMapping("/sign-up")
public ResponseEntity<?> signUp(@RequestBody UserDTO userDTO) {
// 데이터 유효성 검사- 생략
int result = userService.signUp(userDTO); // 201 , 200, 404
return ResponseEntity.status(HttpStatus.CREATED).body(result);
}
// 로그인 요청 --> 보안상 이유
@PostMapping("/sign-in")
public ResponseEntity<?> signin(@RequestBody UserDTO userDTO) {
// 아이디만 확인
// 비번까지 확인
UserEntity user = userService.signin(userDTO);
// 세션 처리 ---> JWT
if(user != null) {
String token = jwtUtil.generateToken(user.getEmail(), user.getUserId());
// 헤더 셋팅
HttpHeaders headers = new HttpHeaders();
// JWT 헤더는 약속 Bearer , 으로 반드시 시작 해야 한다.
headers.add("Authorization", "Bearer " + token);
return ResponseEntity.ok().headers(headers).body(user);
} else {
return new ResponseEntity<>("로그인 실패", HttpStatus.UNAUTHORIZED);
}
}
// TEST
// <http://localhost:80/user/token-test>
@GetMapping("/token-test")
public String testToken() {
// eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyRW1haWwiOiJ0ZXN0QG5hdmVyLmNvbSIsInVzZXJJZCI6MTAsImV4cCI6MTY5ODQ3NDAxNn0.3SGGdFMME3FCVDNrFaQLOlqzN-i28khCAnG65PJe49BLdm2NdHK5AckXj-3AqQAV-Y10idjhdRiIOz2PR-OrMg
return jwtUtil.generateToken("test@naver.com", 10);
}
}
'Flutter' 카테고리의 다른 글
Flutter - RiverPod 이란 (0) | 2023.11.01 |
---|---|
theme 적용하기 (0) | 2023.11.01 |
Flutter MVVM 패턴 - 2(TodoList) (0) | 2023.10.24 |
Flutter MVVM 패턴 - 1 (0) | 2023.10.24 |
Flutter - HTTP 통신 하기 (0) | 2023.10.23 |