From 1b43ebd628c35ce5cefbbf57ca6febd98c0e9309 Mon Sep 17 00:00:00 2001 From: dongHyo Date: Tue, 31 May 2022 20:00:22 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20JwtProvider=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/global/jwt/JwtProvider.java | 114 ++++++++++++++++++ .../server/global/jwt/TokenProviderTest.java | 67 ++++++++++ 2 files changed, 181 insertions(+) create mode 100644 server/src/main/java/com/ticketing/server/global/jwt/JwtProvider.java create mode 100644 server/src/test/java/com/ticketing/server/global/jwt/TokenProviderTest.java diff --git a/server/src/main/java/com/ticketing/server/global/jwt/JwtProvider.java b/server/src/main/java/com/ticketing/server/global/jwt/JwtProvider.java new file mode 100644 index 0000000..ac5fe7c --- /dev/null +++ b/server/src/main/java/com/ticketing/server/global/jwt/JwtProvider.java @@ -0,0 +1,114 @@ +package com.ticketing.server.global.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SecurityException; +import java.security.Key; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.StringJoiner; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class JwtProvider { + + private static final String AUTHORITIES_KEY = "auth"; + private static final String AUTHORITIES_DELIMITER = ","; + + private final long tokenValidityInMilliseconds; + private final Key key; + + public JwtProvider(JwtProperties jwtProperties) { + this.tokenValidityInMilliseconds = jwtProperties.getTokenValidityInSeconds(); + + byte[] keyBytes = Decoders.BASE64.decode(jwtProperties.getSecretKey()); + this.key = Keys.hmacShaKeyFor(keyBytes); + } + + public String createToken(Authentication authentication) { + // 권한 정보 가져오기 + String authorities = generateStringToAuthorities(authentication); + + // 만료시간 계산 + long now = new Date().getTime(); + Date accessTokenExpiresIn = new Date(now + this.tokenValidityInMilliseconds); + + // JWT 생성 + return Jwts.builder() + .setSubject(authentication.getName()) // email + .claim(AUTHORITIES_KEY, authorities) // payload + .setExpiration(accessTokenExpiresIn) // 만료일 + .signWith(key, SignatureAlgorithm.HS512) // 서명 키 값 + .compact(); + } + + private String generateStringToAuthorities(Authentication authentication) { + StringJoiner authorities = new StringJoiner(AUTHORITIES_DELIMITER); + for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) { + String roleName = makeRoleName(grantedAuthority.getAuthority()); + authorities.add(roleName); + } + return authorities.toString(); + } + + private String makeRoleName(String role) { + return "ROLE_" + role.toUpperCase(); + } + + public Authentication getAuthentication(String token) { + // 토큰 복호화 + Claims claims = parseClaims(token); + + List authorities = + Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(AUTHORITIES_DELIMITER)) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + UserDetails principal = new User(claims.getSubject(), "", authorities); + return new UsernamePasswordAuthenticationToken(principal, token, authorities); + } + + public boolean validateToken(String token) { + try { + parseClaims(token); + return true; + } catch (SecurityException | MalformedJwtException exception) { + log.info("잘못된 JWT 서명입니다."); + } catch (ExpiredJwtException e) { + log.info("잘못된 JWT 토큰입니다."); + } catch (UnsupportedJwtException e) { + log.info("지원되지 않는 JWT 토큰입니다."); + } catch (IllegalArgumentException e) { + log.info("JWT 토큰이 잘못되었습니다."); + } + + return false; + } + + private Claims parseClaims(String token) { + return Jwts + .parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } + +} + diff --git a/server/src/test/java/com/ticketing/server/global/jwt/TokenProviderTest.java b/server/src/test/java/com/ticketing/server/global/jwt/TokenProviderTest.java new file mode 100644 index 0000000..869bb7e --- /dev/null +++ b/server/src/test/java/com/ticketing/server/global/jwt/TokenProviderTest.java @@ -0,0 +1,67 @@ +package com.ticketing.server.global.jwt; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ticketing.server.global.factory.YamlPropertySourceFactory; +import com.ticketing.server.user.domain.UserGrade; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +@ExtendWith(SpringExtension.class) +@EnableConfigurationProperties(value = JwtProperties.class) +@PropertySource(value = "classpath:application.yml", factory = YamlPropertySourceFactory.class) +class TokenProviderTest { + + @Autowired + private JwtProperties jwtProperties; + + JwtProvider tokenProvider; + + + @BeforeEach + void init() { + tokenProvider = new JwtProvider(jwtProperties); + } + + @Test + @DisplayName("토큰 생성 성공") + void createTokenSuccess() { + // given + SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority(UserGrade.GUEST.name()); + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken("ticketing@gmail.com", "123456", Collections.singleton(grantedAuthority)); + + // when + String token = tokenProvider.createToken(authenticationToken); + + // then + assertThat(token).isNotEmpty(); + } + + @Test + @DisplayName("토큰 복호화 성공") + void getAuthentication() { + // given + SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority(UserGrade.GUEST.name()); + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken("ticketing@gmail.com", "123456", Collections.singleton(grantedAuthority)); + + // when + String token = tokenProvider.createToken(authenticationToken); + Authentication authentication = tokenProvider.getAuthentication(token); + + // then + assertThat(authentication.getName()).isEqualTo("ticketing@gmail.com"); + } + +} \ No newline at end of file