# [Security] JWT ๊ตฌํ˜„ (3) - ํ† ํฐ ์ƒ์„ฑ๊ธฐ ๊ตฌํ˜„ (JWT TokenProvider)
Study Repository

[Security] JWT ๊ตฌํ˜„ (3) - ํ† ํฐ ์ƒ์„ฑ๊ธฐ ๊ตฌํ˜„ (JWT TokenProvider)

by rlaehddnd0422

JWT ์„ค์ • ์ถ”๊ฐ€

 

[Security] Jwt Authentication ๊ตฌํ˜„ by Overriding Spring Security ์ธ์ฆ ์•„ํ‚คํ…์ณ

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” Spring Security์— Jwt ์„ ์ ์šฉํ•œ ์ธ์ฆ(Authentication)๋ฐฉ์‹์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ณ , ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ธ์ฆ์— ๊ธฐ๋ฐ˜ํ•œ ์ธ๊ฐ€(๊ถŒํ•œ๊ฒ€์‚ฌ,Authorization)๋ฐฉ์‹์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๐ŸšฉSecuri

rlaehddnd0422.tistory.com

์ด ์ „์—๋Š” 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์„ ์ƒ์„ฑํ•˜๋Š” ๋ฉ”์†Œ๋“œ์ž…๋‹ˆ๋‹ค.

 

๋™์ž‘ ๋ฐฉ์‹์€ ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

  1. Authentication.getAuthorities() ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด UserDetails์˜ authorities๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ๊ถŒํ•œ ์ •๋ณด๋ฅผ ๋ฐ›์•„๋ƒ…๋‹ˆ๋‹ค.
  2. ํ˜„์žฌ ์‹œ๊ฐ„์„ ๋ฐ›์•„๋ƒ…๋‹ˆ๋‹ค.
  3. ๊ถŒํ•œ ์ •๋ณด์™€ ํ˜„์žฌ ์‹œ๊ฐ„์„ ํ† ๋Œ€๋กœ 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๋กœ ๋งŒ๋“ค์–ด ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค.

 

UsernamePasswordAuthenticationToken implements Authentication

 

<์ฐธ๊ณ ์ž๋ฃŒ>

 

๋ณธ ํฌ์ŠคํŒ…์€ ์ •์€๊ตฌ๋‹˜์˜ Spring Boot JWT Tutorial ๊ฐ•์˜๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

[๋ฌด๋ฃŒ] Spring Boot JWT Tutorial - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜

Spring Boot, Spring Security, JWT๋ฅผ ์ด์šฉํ•œ ํŠœํ† ๋ฆฌ์–ผ์„ ํ†ตํ•ด ์ธ์ฆ๊ณผ ์ธ๊ฐ€์— ๋Œ€ํ•œ ๊ธฐ์ดˆ ์ง€์‹์„ ์‰ฝ๊ณ  ๋น ๋ฅด๊ฒŒ ํ•™์Šตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค., - ๊ฐ•์˜ ์†Œ๊ฐœ | ์ธํ”„๋Ÿฐ

www.inflearn.com

 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

Study Repository

rlaehddnd0422

ํ™œ๋™ํ•˜๊ธฐ