Bibi's DevLog ๐ค๐
[Spring] JWT๋ก ํ ํฐ ๊ธฐ๋ฐ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ๋ง๋ค๊ธฐ ๋ณธ๋ฌธ
[Spring] JWT๋ก ํ ํฐ ๊ธฐ๋ฐ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ๋ง๋ค๊ธฐ
๋น๋น bibi 2021. 6. 4. 23:37[airbnbํ๋ก์ ํธ] JWT ์ฝ๋ ์ค๋ช
์ถ์ฒ : ์ ์ฒด ์ฝ๋ ์ถ์ฒ๋ https://ocblog.tistory.com/56, ์ค๋ช ์ yeon์ด ํด ์ฃผ์ จ์ต๋๋ค๐โโ๏ธ
JWT ํ ํฐ ์ ์ฐ๋๊ฐ?
๋ก๊ทธ์ธ ์ ์๋ฒ๊ฐ ํ ํฐ์ ๋ง๋ค์ด ํด๋ผ์ด์ธํธ์ ๋ฐ๊ธํด ์ค๋ค.
์ดํ ๋ก๊ทธ์ธ์ด ํ์ํ URL์์, ํค๋์ ํ ํฐ์ด ๋ค์ด์๋์ง ํ์ธํ๋ค.
ํ ํฐ์ด ์์ผ๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํ ๊ธฐ๋ฅ๋ค์ ์ด์ฉํ ์ ์๋ค.
ํ ํฐ์ด ์์ผ๋ฉด ๋ก๊ทธ์ธ๋์ง ์์ ์ํ๋ก ๊ฐ์ฃผํ๊ณ ๋ก๊ทธ์ธ์ด ํ์ํ ๊ธฐ๋ฅ๋ค์ ์ด์ฉํ ์ ์๋ค.
์ธํฐ์ ํฐ interceptor
์ธํฐ์ ํฐ : ์ปจํธ๋กค๋ฌ์ ์ค๊ธฐ ์ ์ ์กด์ฌํ๋ ๋จ๊ณ.
ํ๋ก ํธ๊ฐ HTTP Header์ ์ฐ๋ฆฌ๊ฐ ๋ฐ๊ธํ ํ ํฐ์ ๋ด์์ ๋ณด๋ด์ฃผ๋ฉด ("Authorization" : Bearer ํ ํฐ
) ์ฐ๋ฆฌ๊ฐ ๋ฐ๊ธํ ํ ํฐ์ด ๋ง๋์ง ํ์ธํ๋ค.
ํ ํฐ ํ์ธ ์์ ์ด ๋ฐ๋ณต์ ์ด๊ธฐ ๋๋ฌธ์, ์ปจํธ๋กค๋ฌ๊ฐ ์๋ ์ธํฐ์ ํฐ๋ฅผ ๋ฐ๋ก ๋ง๋ค์ด ๊ฑฐ๊ธฐ์์ ์ฒ๋ฆฌํ๊ฒ ํ๋ ๊ฒ์ด๋ค.
(์๋์ BearerInterceptor
์ฝ๋ ์ค๋ช
์ฐธ์กฐ)
AppConfig
@Configuration
public class AppConfig implements WebMvcConfigurer {
private static final Logger logger = LoggerFactory.getLogger(AppConfig.class);
private final BearerAuthInterceptor bearerAuthInterceptor;
public AppConfig(BearerAuthInterceptor bearerAuthInterceptor) { // ์ธํฐ์
ํฐ ๊ตฌํ์ฒด๋ฅผ ๋ง๋ค์ด ๋ฑ๋กํจ
this.bearerAuthInterceptor = bearerAuthInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
logger.info(">>> ์ธํฐ์
ํฐ ๋ฑ๋ก");
registry.addInterceptor(bearerAuthInterceptor).addPathPatterns("/api/booking")
.addPathPatterns("/api/booking/{bookingId}")
.addPathPatterns("/api/rooms/{userId}/wish/{roomId}");
}
}
AppConfig
๋ WebMvcConfigurer
๋ฅผ ๊ตฌํํด์ผ ํ๋ค.
addInterceptors()
- ์ธํฐ์ ํฐ๋ฅผ ๋ฑ๋กํ ๋, ์ธํฐ์ ํฐ๋ฅผ ๊ฑฐ์น url์ ๋ฑ๋กํ๋ค.
- = ์ฆ ๋ก๊ทธ์ธ์ํ์ธ์ง ํ์ธ์ด ๋์ด์ผ๋ง ์ ๊ณต๋๋ ๊ธฐ๋ฅ๊ณผ ์ฐ๊ฒฐ๋ url๋ค์ ๋ฑ๋กํ๋ฉด ๋๋ค.
BearerAuthInterceptor
package com.codesquad.airbnb.jwt;
import com.codesquad.airbnb.exception.TokenEmptyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class BearerAuthInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(BearerAuthInterceptor.class);
private AuthorizationExtractor authorizationExtractor;
private JwtTokenProvider jwtTokenProvider;
public BearerAuthInterceptor(AuthorizationExtractor authorizationExtractor, JwtTokenProvider jwtTokenProvider) {
this.authorizationExtractor = authorizationExtractor;
this.jwtTokenProvider = jwtTokenProvider;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
logger.info(">>> interceptor.preHandle ํธ์ถ");
String token = authorizationExtractor.extract(request, "Bearer");
if (token.isEmpty()) {
throw new TokenEmptyException();
}
if (!jwtTokenProvider.validateToken(token)) {
throw new IllegalArgumentException("์ ํจํ์ง ์์ ํ ํฐ");
}
Long id = jwtTokenProvider.getSubject(token);
request.setAttribute("id", id);
return true;
}
}
HandlerInterceptor
๋ฅผ ๊ตฌํํ BearerAuthInterceptor
์ธํฐ์
ํฐ๋ฅผ, @Configuration
์ด ๋ถ์ AppConfig
์์ ์ธํฐ์
ํฐ๋ก ๋ฑ๋กํ๋ค. (addInterceptors()
)
preHandle()
:HandlerInterceptor
์ ์ถ์๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํ ๊ฒ.- ์ด ๊ฒฐ๊ณผ๊ฐ์ด true์ฌ์ผ ๋ค์์ผ๋ก ๋์ด๊ฐ๋ค.
- true๊ฐ ๋ฐํ์ด ๋์ด์ผ๋ง controller์ ๋๋ฌํ๋ค.
- request-์ธํฐ์
ํฐ - ํค๋์ ์๋ ํ ํฐ์ ์ถ์ถํ๋ค. (
authorizationExtractor.extract()
) - ํ ํฐ์ด ๋น์ด์๋ค = ๋ก๊ทธ์ธ์ด ์ ๋์ด์๋ค -> ์์ธ ๋ฐ์
jwtTokenProvider.validateToken()
: ์ ํจํ ํ ํฐ์ธ์ง ๊ฒ์ฆ. ์ ํจํ์ง ์์ผ๋ฉด ์์ธ ๋ฐ์- ํ์ธ์ด ๋๋๋ฉด (์ฐ๋ฆฌ๋ userId๋ฅผ ์ด์ฉํด ํ ํฐ์ ๋ง๋๋๋ฐ) ํ ํฐ์ ๋์ฝ๋ฉํด, ํ ํฐ์ ๋ค์ด์๋ userId๋ฅผ ํ์ธํ๋ค(
jwtTokenProvider.getSubject()
).
AuthorizationExtractor
package com.codesquad.airbnb.jwt;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Component
public class AuthorizationExtractor {
public static final String AUTHORIZATION = "Authorization";
public static final String ACCESS_TOKEN_TYPE = AuthorizationExtractor.class.getSimpleName();
public String extract(HttpServletRequest request, String type) {
Enumeration<String> headers = request.getHeaders(AUTHORIZATION);
while (headers.hasMoreElements()) {
String value = headers.nextElement();
if (value.toLowerCase().startsWith(type.toLowerCase())) {
return value.substring(type.length()).trim();
}
}
return Strings.EMPTY;
}
}
- HTTP header์ ๋ค์ด ์๋ ํ ํฐ์ ์ถ์ธจํ๋ ์ญํ ์ ๋ฉ์๋์ด๋ค.
JwtTokenProvider
package com.codesquad.airbnb.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Base64;
import java.util.Date;
@Component
public class JwtTokenProvider {
private final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);
private String secretKey;
private long validityInMilliseconds;
public JwtTokenProvider(@Value("&{security.jwt.token.secret-key}") String secretKey, @Value("${security.jwt.token.expire-length}") long validityInMilliseconds) {
this.secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
this.validityInMilliseconds = validityInMilliseconds;
}
// ํ ํฐ ์์ฑ
public String createToken(Long id) {
Claims claims = Jwts.claims().setSubject(String.valueOf(id));
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
logger.info("now: {}", now);
logger.info("validity: {}", validity);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
// ํ ํฐ์์ ๊ฐ ์ถ์ถ
public Long getSubject(String token) {
return Long.valueOf(Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody()
.getSubject());
}
// ์ ํจํ ํ ํฐ์ธ์ง ํ์ธ
public boolean validateToken(String token) {
Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return !claims.getBody().getExpiration().before(new Date());
}
}
JwtTokenProvider
: ํ ํฐ์ ์ํธํ, ๋ณตํธํ, ๊ฒ์ฆ ๋ฑ์ ์์ ์ ํ๋ ํ๋ ํด๋์คgetSubject()
: ํ ํฐ์ ๊ฐ์ ์ถ์ถํด ๋ฐ์์ค๋ ๋ฉ์๋- ํ ํฐ ์์ฑ, ์ถ์ถ, ์ ํจ์ฑ ํ์ธ ๋ฉ์๋๋ค์ด ๋ค์ด์๋ค.
- ํ ํฐ ์์ฑ์ ํ์ํ ์ํฌ๋ฆฟ ํค์ ์ ํจ๊ธฐ๊ฐ ๋ฑ์
application.properties
์ ์ค์ ์์ ๊ฐ์ ธ์ ํ ํฐ์ ๋ง๋ ๋ค. - ์์ฑ :
Jwts
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํด ์ํฌ๋ฆฟ ํค๋ก ํ ํฐ ์ํธํ์ ์ ํจ๊ธฐ๊ฐ์ ์ค์ ํ๋ค.- ํ ํฐ์ sub ๋ฑ์ claim์ผ๋ก ์ค์ ํ๋ค.
TokenRespnose
package com.codesquad.airbnb.jwt;
public class TokenResponse {
private String accessToken;
private String tokenType;
public TokenResponse(String accessToken, String tokenType) {
this.accessToken = accessToken;
this.tokenType = tokenType;
}
public String getAccessToken() {
return accessToken;
}
public String getTokenType() {
return tokenType;
}
}
- TokenResponse : JWTํ ํฐ ๊ฐ์ ๋ด์ ๋ณด๋ด๊ธฐ ์ํ Response DTO ํด๋์ค์ด๋ค.