: 해당 유저가 실제 유저인지 인증하는 개념
: 비연결성, 무상태
: 해당 유저가 특정 리소스에 접근이 가능한지 허가를 확인하는 개념
: 스프링 시큐리티가 관리해줌
- 사용자가 로그인 요청을 보냅니다.
- 서버는 DB의 유저 테이블을 뒤져서 아이디 비밀번호를 대조해봐야겠죠?
- 실제 유저테이블의 정보와 일치한다면 인증을 통과한 것으로 보고 “세션 저장소”에 해당 유저가 로그인 되었다는 정보를 넣습니다.
- 세션 저장소에서는 유저의 정보와는 관련 없는 난수인 session-id를 발급합니다.
- 서버는 로그인 요청의 응답으로 session-id를 내어줍니다.
- 클라이언트는 그 session-id를 쿠키라는 저장소에 보관하고 앞으로의 요청마다 세션아이디를 같이 보냅니다. (주로 HTTP header에 담아서 보냅니다!)
- 클라이언트의 요청에서 쿠키를 발견했다면 서버는 세션 저장소에서 쿠키를 검증합니다.
- 만약 유저정보를 받아왔다면 이 사용자는 로그인이 되어있는 사용자겠죠?
- 이후에는 로그인 된 유저에 따른 응답을 내어줍니다.
저장된 쿠키
라고 생각하면 됨
- 사용자가 로그인 요청 보냄
- 서버는 DB의 유저 테이블에서 ID/PW 대조
- 실제 유저테이블의 정보와 일치한다면 인증을 통과한 것으로 보고 유저의 정보를 JWT로 암호화해서 내보냄
- 서버는 로그인 요청의 응답으로 JWT 토큰을 내어줌
- 클라이언트는 그 토큰을 저장소에 보관하고 앞으로의 요청마다 토큰을 같이 보냄
- 클라이언트의 요청에서 토큰을 발견했다면 서버는 토큰을 검증
- 이후에는 로그인 된 유저에 따른 응답을 내어줌
JWT를 사용하는 이유
단점
JWT는 어떻게 생겼나 - jwt.io
.
을 사용하여 세 부분으로 나누어짐- Header, Payload, Verify Signature
- Header, Verify Signature : 암호화 관련된 정보 양식
- Payload : 실제 유저의 정보가 들어있는 부분- 위 암호화 문자열을 해독하면 아래와 같은 모습
1. JWT dependency 추가
compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
# application.properties
jwt.secret.key=7ZWt7ZW0OTntmZTsnbTtjIXtlZzqta3snYTrhIjrqLjshLjqs4TroZzrgpjslYTqsIDsnpDtm4zrpa3tlZzqsJzrsJzsnpDrpbzrp4zrk6TslrTqsIDsnpA=
2. 토큰생성에 필요한 값
3. Header에서 토큰 가져오기
4. JWT 생성
5. JWT 검증
6. JWT에서 사용자 정보 가져오기
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtil {
// 2. 토큰생성에 필요한 값
// Header Key 값, 사용자 권한 값의 Key, Token 식별자, 토큰 만료시간
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String AUTHORIZATION_KEY = "auth";
private static final String BEARER_PREFIX = "Bearer ";
private static final long TOKEN_TIME = 60 * 60 * 1000L; // ms 단위, 총 60분
@Value("${jwt.secret.key}") // application.properties 에서 가져온 값을
private String secretKey; // secretKey에 담음
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
@PostConstruct // 객체가 생성될때 초기화 해줌
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
// application.properties에 넣은 jwt key -> 64base로 문자열을 변환한 것
// 3. header에서 토큰을 가져오기
public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
return bearerToken.substring(7);
}
return null;
}
// 4. JWT 토큰 생성
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username)
.claim(AUTHORIZATION_KEY, role)
.setExpiration(new Date(date.getTime() + TOKEN_TIME))
.setIssuedAt(date)
.signWith(key, signatureAlgorithm)
.compact();
}
// 5. 토큰 검증
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException e) {
log.info("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
} catch (ExpiredJwtException e) {
log.info("Expired JWT token, 만료된 JWT token 입니다.");
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
} catch (IllegalArgumentException e) {
log.info("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
}
return false;
}
// 6. 토큰에서 사용자 정보 가져오기
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
}