Spring Boot에서 JWT를 활용한 인증 구현
세션 VS 토큰
[세션]
1. 클라이언트가 로그인 요청을 하고, 로그인 정보가 일치하면 서버는 세션을 생성/유지 (클라이언트마다 하나씩)
(세션에는 사용자 ID, 로그인 시간, IP 등을 저장)
2. 서버는 로그인 응답 (세션을 찾을 수 있는 세션 ID를 클라이언트에 전달 (보통 쿠키로 전달))
(세션은 서버에 저장되는 값, 쿠키는 클라이언트에 저장되는 값)
3. 클라이언트가 세션 ID와 함께 서비스 요청을 하면 서버는 세션 ID로 세션을 찾아 인증된 유저임을 확인
4. 서버는 서비스 응답
서버가 세션을 들고있다.
서버가 1대라면 세션을 사용해도 괜찮지만, 서버는 여러 대가 존재할 것이다. (모두 세션을 가져야 함 & 동기화)
세션 클러스터링 등 작업이 복잡하며 DB에도 부담을 줄 수 있다.
[토큰]
1. 클라이언트가 로그인 요청을 하면 서버는 토큰을 생성 (클라이언트마다 하나씩)
2. 서버는 토큰과 함께 로그인 응답
3. 클라이언트가 토큰과 함께 서비스 요청을 하면 서버는 토큰으로 인증된 유저임을 확인
4. 서버는 토큰과 함께 서비스 응답
클라이언트와 서버가 토큰을 주고 받는다.
build.gradle의 dependencies에 아래의 코드 추가
implementation 'javax.xml.bind:jaxb-api:'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
SecurityController
package com.example.firstproject.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
@RequestMapping("/security")
public class SecurityController {
@Autowired
private SecurityService securityService;
@GetMapping("/create/token")
public Map<String, Object> createToken(@RequestParam(value = "subject") String subject) { // 일반적으로 subject를 ID 값으로, PW를 key 값으로 같이 보내는 POST 방식이 맞지만 여기서는 확인을 위해 GET 방식으로 한다.
String token = securityService.createToken(subject, (2 * 1000 * 60)); // 2분
Map<String, Object> map = new LinkedHashMap<>();
map.put("result", token);
return map;
}
@GetMapping("/get/subject")
public Map<String, Object> getSubject(@RequestParam(value = "token") String token) {
String subject = securityService.getSubject(token);
Map<String, Object> map = new LinkedHashMap<>();
map.put("result", subject);
return map;
}
}
SecurityService
package com.example.firstproject.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Service;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
@Service
public class SecurityService {
private static final String SECRET_KEY = "qweqiwehqhruhqwiejqiwejqiwheuqfjnqweojqwiejqwuequwe";
// 로그인 서비스 보낼 때 같이
public String createToken(String subject, long expTime) {
if (expTime <= 0) {
throw new RuntimeException("만료 시간은 0보다 커야합니다.");
}
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
byte[] secretKeyBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
Key signingKey = new SecretKeySpec(secretKeyBytes, signatureAlgorithm.getJcaName());
return Jwts.builder()
.setSubject(subject)
.signWith(signingKey, signatureAlgorithm)
.setExpiration(new Date(System.currentTimeMillis() + expTime))
.compact();
}
// 실제로 사용할 때는 안에 있는 로직을 이용해 boolean 타입을 리턴하는 토큰 검증 메소드를 만들고 토큰 검증 로직에서 이 메소드를 호출해서 사용
public String getSubject(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(DatatypeConverter.parseBase64Binary(SECRET_KEY))
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
}
POSTMAN 테스트
1. /create/token
2. /get/subject
3. /get/subject 토큰 유지 시간으로 설정한 2분이 지나면
'Framework > Spring Boot' 카테고리의 다른 글
[Framework] Spring Boot [STS 설치 + Git 프로젝트 Import + Tomcat 실행] (1) | 2023.10.05 |
---|---|
[Framework] Spring Boot [@Autowired & private final] (0) | 2022.11.30 |
[Framework] Spring Boot [TDD] 테스트 코드 작성 (0) | 2022.11.30 |
[Framework] Spring Boot [REST API] 기본 구조 (0) | 2022.11.30 |
[Framework] Spring Boot [JPA + H2] 기초 (0) | 2022.11.30 |