[Security] JWT ๊ตฌํ (3) - ํ ํฐ ์์ฑ๊ธฐ ๊ตฌํ (JWT TokenProvider)
by rlaehddnd0422JWT ์ค์ ์ถ๊ฐ
์ด ์ ์๋ UsernamePasswordAuthenticationFilter์ successfulAuthentication() ๋ฉ์๋์์ Token ์ ๋ณด๋ฅผ ์ง์ ํ๊ณ JWT๋ฅผ ๋ง๋ค์ด ์ฃผ์์์ต๋๋ค.
ํ์ง๋ง application.yml ํ์ผ์ JWT ๊ด๋ จ ์ค์ ์ ์ถ๊ฐํ์ฌ JWT ์์ฑ ์ ๋ณด๋ฅผ ์ง์ ํด์ค ์ ์์ต๋๋ค.
apllication.yml์ JWT ๊ด๋ จ ์ค์ ์ถ๊ฐ
jwt:
header: Authorization
secret: ZGhybmZtYQo=
token-validity-in-seconds: 86400
- Response header ์ด๋ฆ : Authorization
- secret key(Base64Encode) : ์๋ฒ์ ์ํฌ๋ฆฟ ํค (dhrnfma|base64)
- token-validity-in-seconds : ๋ง๋ฃ ์๊ฐ ์ค์ 86400์ด
Dependency ์ถ๊ฐ (build.gradle)
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
JWT ํ ํฐ ํธ๋ค๋ฌ ๊ตฌํ
๋ณธ๊ฒฉ์ ์ผ๋ก JWT ๊ด๋ จ ๊ธฐ๋ฅ๋ค์ ๊ฐ๋ฐํด๋ด ์๋ค.
JWT ๊ตฌํ์ ์์ด, ํ ํฐ์ ์์ฑ๊ณผ ๊ฒ์ฆ์ ๋ด๋นํ๋ ํด๋์ค๋ฅผ ๋ณ๋๋ก ๋ง๋ค์ด ์ฌ์ฉํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์, TokenProvider๋ผ๋ ํด๋์ค๋ฅผ ๋ง๋ค์ด ์ค์๋ค.
TokenProvider
@Component
public class TokenProvider implements InitializingBean {
private final Logger logger = LoggerFactory.getLogger(TokenProvider.class);
private static final String AUTHORITIES_KEY = "auth";
private final String secret;
private final long tokenValidityInMilliseconds;
private Key key;
public TokenProvider(@Value("${jwt.secret}") String secret,
@Value("${jwt.token-validity-in-seconds}") long tokenValidityInseconds) {
this.secret = secret;
this.tokenValidityInMilliseconds = tokenValidityInseconds * 1000;
}
@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(secret);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
// ํ ํฐ ์์ฑ
public String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
Date validity = new Date(now + this.tokenValidityInMilliseconds);
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
// ํ ํฐ ํด๋
(ํ ํฐ์ผ๋ก๋ถํฐ Authentication ๊ฐ์ฒด ์ถ์ถ)
public Authentication getAuthentication(String token) {
Claims claims = Jwts
.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
// ํ ํฐ ๊ฒ์ฆ
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
logger.info("์๋ชป๋ JWT ์๋ช
์
๋๋ค.");
} catch (ExpiredJwtException e) {
logger.info("๋ง๋ฃ๋ JWT ํ ํฐ์
๋๋ค.");
} catch (UnsupportedJwtException e) {
logger.info("์ง์๋์ง ์๋ JWT ํ ํฐ์
๋๋ค.");
} catch (IllegalArgumentException e) {
logger.info("JWT ํ ํฐ์ด ์๋ชป๋์์ต๋๋ค.");
}
return false;
}
}
TokenProvider - ์์ฑ์ ๋ฐ ์ด๊ธฐํ
public TokenProvider(@Value("${jwt.secret}") String secret,
@Value("${jwt.token-validity-in-seconds}") long tokenValidityInseconds) {
this.secret = secret;
this.tokenValidityInMilliseconds = tokenValidityInseconds * 1000;
}
- yml์ ์ค์ ํด๋ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ(์ํฌ๋ฆฟ ํค, ๋ง๋ฃ ์๊ฐ)์ผ๋ก TokenProvider๋ฅผ ์์ฑํฉ๋๋ค.
@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(secret);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
afterPropertiesSet() ์ InintializingBean ์ธํฐํ์ด์ค์ ๋ฉ์๋๋ก BeanFactory์ ์ํด ๋ชจ๋ property ๊ฐ ์ค์ ๋๊ณ ๋ ๋ค ์คํ๋๋ ๋ฉ์๋์ ๋๋ค. ์์ฑ์๊ฐ ์คํ๋ ์ดํ ์ํฌ๋ฆฟ ํค๋ฅผ Base64๋ก ์ธ์ฝ๋ฉ ํ HMAC-SHA ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ํธํํฉ์๋ค.
๋น LifeCycle : ์์ฑ) @PostConstruct โถ๏ธ InitializingBean Interface์ afterPropertiesSet() โถ๏ธ @Bean ์ initMethod
๋น LifeCycle : ์๋ฉธ) @PreDestroy โถ๏ธ DisposableBean Interface์ destroy() โถ๏ธ @Bean์ destroyMethod
ํ ํฐ ์์ฑ ๋ฉ์๋๋ถํฐ ํํค์ณ ๋ด ์๋ค.
TokenProvider - createToken(Authentication)
public String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
Date validity = new Date(now + this.tokenValidityInMilliseconds);
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
Authentication ๊ฐ์ฒด์ property๋ฅผ ๊ธฐ๋ฐ์ผ๋ก Token์ ์์ฑํ๋ ๋ฉ์๋์ ๋๋ค.
๋์ ๋ฐฉ์์ ์ด๋ ์ต๋๋ค.
- Authentication.getAuthorities() ๋ฉ์๋๋ฅผ ํตํด UserDetails์ authorities๋ฅผ ๋ฐํํ์ฌ ๊ถํ ์ ๋ณด๋ฅผ ๋ฐ์๋ ๋๋ค.
- ํ์ฌ ์๊ฐ์ ๋ฐ์๋ ๋๋ค.
- ๊ถํ ์ ๋ณด์ ํ์ฌ ์๊ฐ์ ํ ๋๋ก Jwt Builder๋ฅผ ํตํด ํ ํฐ์ ์์ฑํฉ๋๋ค.
ํ ํฐ ์์ฑ ์ ๋ณด : AUTHORITIES_KEY("auth")์ ๊ถํ ์ ๋ณด๋ฅผ ์ถ๊ฐ
ํ ํฐํ ํด์ ์๊ณ ๋ฆฌ์ฆ : HS512 ์ฌ์ฉ.
ํ ํฐ ๋ง๋ฃ๊ธฐ๊ฐ : ํ์ฌ์๊ฐ + yml์ ์ง์ ํด๋ ์๊ฐ(86400s)
๋ค์์ ํ ํฐ ๊ฒ์ฆ ๋ฉ์๋์ ๋๋ค.
TokenProvider - validateToken(token)
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("์๋ชป๋ JWT ์๋ช
์
๋๋ค.");
} catch (ExpiredJwtException e) {
log.info("๋ง๋ฃ๋ JWT ํ ํฐ์
๋๋ค.");
} catch (UnsupportedJwtException e) {
log.info("์ง์๋์ง ์๋ JWT ํ ํฐ์
๋๋ค.");
} catch (IllegalArgumentException e) {
log.info("JWT ํ ํฐ์ด ์๋ชป๋์์ต๋๋ค.");
}
return false;
}
- Jwt-Parser๋ฅผ ์ด์ฉํด Claims๊ฐ์ฒด๋ก ํ์ฑํ๋ ๊ณผ์ ์์ ๋ฐ์ํ ์ ์๋ ์์ธ๋ฅผ try-catch๋ก ์ฒ๋ฆฌํ์์ต๋๋ค.
- validateToken์ ๊ฒ์ฆ์ด ์๋ฃ๋๋ฉด true, ๊ทธ๋ ์ง ์์ผ๋ฉด false๋ฅผ ๋ฆฌํดํ์ฌ ๊ฒ์ฆ ํต๊ณผ ์ฌ๋ถ๋ฅผ ๋ฆฌํดํด์ค๋๋ค.
๋ค์์ ํ ํฐ์ ํตํด ์ธ์ฆ์ ๋ณด๋ฅผ ๋ง๋๋ ๋ฉ์๋์ ๋๋ค.
TokenProvider - getAuthentication(token)
public Authentication getAuthentication(String token) {
Claims claims = Jwts
.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
Jwt-Parser๋ฅผ ์ด์ฉํ์ฌ ์ ๋ ฅ๋ฐ์ ํ ํฐ์ ํ์ฑํ๋ฉฐ Claims๋ผ๋ ๊ฐ์ฒด๋ฅผ ์ป์ ์ ์์ต๋๋ค.
์ด Claims ๊ฐ์ฒด๋ฅผ ํตํด ๊ถํ์ ๋ณด๋ฅผ ์ถ์ถํ ํ, ๊ถํ์ ๋ณด์ claims์ subject๋ฅผ ํ ๋๋ก User๋ฅผ ํ๋ ์์ฑํ์ฌ UsernamePasswordAuthenticationToken๋ก ๋ง๋ค์ด ๋ฆฌํดํฉ๋๋ค.
<์ฐธ๊ณ ์๋ฃ>
๋ณธ ํฌ์คํ ์ ์ ์๊ตฌ๋์ Spring Boot JWT Tutorial ๊ฐ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ์์ฑํ์์ต๋๋ค.
'๐ Backend > Spring Security' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋ธ๋ก๊ทธ์ ์ ๋ณด
Study Repository
rlaehddnd0422