diff --git a/src/main/java/team/silvertown/masil/config/jwt/JwtTokenProvider.java b/src/main/java/team/silvertown/masil/config/jwt/JwtTokenProvider.java index 04bbab2b..28d49ec3 100644 --- a/src/main/java/team/silvertown/masil/config/jwt/JwtTokenProvider.java +++ b/src/main/java/team/silvertown/masil/config/jwt/JwtTokenProvider.java @@ -1,5 +1,6 @@ package team.silvertown.masil.config.jwt; +import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.JwtParser; @@ -9,17 +10,18 @@ import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.MacAlgorithm; +import io.micrometer.common.util.StringUtils; +import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.StringJoiner; import javax.crypto.SecretKey; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; -import team.silvertown.masil.user.domain.UserAuthority; -import team.silvertown.masil.user.repository.UserAuthorityRepository; -import team.silvertown.masil.user.validator.UserValidator; +import team.silvertown.masil.user.domain.Authority; @Slf4j @Component @@ -29,7 +31,10 @@ public class JwtTokenProvider { private static final String EXPIRED_TOKEN = "Expired JWT token."; private static final String UNSUPPORTED_TOKEN = "Unsupported JWT token."; private static final String UNEXPECTED_TOKEN = "JWT token compact of handler are invalid."; + private static final String NO_AUTHORITY = "No authority included in JWT"; private static final String USER_ID_CLAIM = "user_id"; + private static final String AUTHORITIES_CLAIM = "authorities"; + private static final String AUTHORITIES_DELIM = " "; private static final int MILLS = 1000; private final long tokenValidityInMilliseconds; @@ -37,12 +42,8 @@ public class JwtTokenProvider { private final MacAlgorithm algorithm; private final SecretKey secretKey; private final JwtParser jwtParser; - private final UserAuthorityRepository userAuthorityRepository; - public JwtTokenProvider( - JwtProperties jwtProperties, - UserAuthorityRepository userAuthorityRepository - ) { + public JwtTokenProvider(JwtProperties jwtProperties) { byte[] keyBytes = Decoders.BASE64.decode(jwtProperties.base64Secret()); this.tokenValidityInMilliseconds = jwtProperties.tokenValidityInSeconds() * MILLS; @@ -53,32 +54,37 @@ public JwtTokenProvider( .verifyWith(secretKey) .requireIssuer(issuer) .build(); - this.userAuthorityRepository = userAuthorityRepository; } - public String createToken(long userId) { + public String createToken(long userId, List authorities) { Date now = new Date(); Date validity = new Date(now.getTime() + tokenValidityInMilliseconds); + StringJoiner joiner = new StringJoiner(" "); + + authorities.forEach(authority -> joiner.add(authority.getAuthority())); return Jwts.builder() .issuer(issuer) .issuedAt(now) .expiration(validity) .claim(USER_ID_CLAIM, userId) + .claim(AUTHORITIES_CLAIM, joiner.toString()) .signWith(secretKey, algorithm) .compact(); } public Authentication getAuthentication(String token) { - long userId = jwtParser.parseSignedClaims(token) - .getPayload() - .get(USER_ID_CLAIM, Long.class); + Claims claims = jwtParser.parseSignedClaims(token) + .getPayload(); + Long userId = claims.get(USER_ID_CLAIM, Long.class); + String authorityNames = claims.get(AUTHORITIES_CLAIM, String.class); - List userAuthorities = userAuthorityRepository.findAllByUserId(userId); - UserValidator.validateAuthority(userAuthorities); + if (StringUtils.isBlank(authorityNames)) { + throw new InsufficientAuthenticationException(NO_AUTHORITY); + } - List authorities = userAuthorities.stream() - .map(UserAuthority::getName) + List authorities = Arrays.stream(authorityNames.split(AUTHORITIES_DELIM)) + .map(Authority::get) .toList(); return new UsernamePasswordAuthenticationToken(userId, token, authorities); diff --git a/src/main/java/team/silvertown/masil/config/security/CustomAuthenticationEntryPoint.java b/src/main/java/team/silvertown/masil/config/security/CustomAuthenticationEntryPoint.java index de4888db..bdd47b8c 100644 --- a/src/main/java/team/silvertown/masil/config/security/CustomAuthenticationEntryPoint.java +++ b/src/main/java/team/silvertown/masil/config/security/CustomAuthenticationEntryPoint.java @@ -1,7 +1,6 @@ package team.silvertown.masil.config.security; import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @@ -23,7 +22,7 @@ public void commence( HttpServletRequest request, HttpServletResponse response, AuthenticationException authException - ) throws IOException, ServletException { + ) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); diff --git a/src/main/java/team/silvertown/masil/user/domain/Authority.java b/src/main/java/team/silvertown/masil/user/domain/Authority.java index 68068f34..2bf2c53c 100644 --- a/src/main/java/team/silvertown/masil/user/domain/Authority.java +++ b/src/main/java/team/silvertown/masil/user/domain/Authority.java @@ -1,7 +1,23 @@ package team.silvertown.masil.user.domain; -public enum Authority { +import java.util.Arrays; +import org.springframework.security.core.GrantedAuthority; + +public enum Authority implements GrantedAuthority { RESTRICTED, NORMAL, - ADMIN + ADMIN; + + public static Authority get(String name) { + return Arrays.stream(Authority.values()) + .filter(authority -> name.equals(authority.name())) + .findFirst() + .orElseThrow(); + } + + @Override + public String getAuthority() { + return this.name(); + } + } diff --git a/src/main/java/team/silvertown/masil/user/domain/UserAuthority.java b/src/main/java/team/silvertown/masil/user/domain/UserAuthority.java index 1b1de164..14eab5be 100644 --- a/src/main/java/team/silvertown/masil/user/domain/UserAuthority.java +++ b/src/main/java/team/silvertown/masil/user/domain/UserAuthority.java @@ -14,8 +14,6 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; @Entity @Table(name = "user_authorities") @@ -43,8 +41,8 @@ private UserAuthority(User user, Authority authority) { this.authority = authority; } - public GrantedAuthority getName() { - return new SimpleGrantedAuthority(ROLE_PREFIX + authority.name()); + public String getRole() { + return ROLE_PREFIX + authority.getAuthority(); } } diff --git a/src/main/java/team/silvertown/masil/user/service/UserService.java b/src/main/java/team/silvertown/masil/user/service/UserService.java index aba54673..e8b90540 100644 --- a/src/main/java/team/silvertown/masil/user/service/UserService.java +++ b/src/main/java/team/silvertown/masil/user/service/UserService.java @@ -65,13 +65,15 @@ public LoginResponse login(String kakaoToken) { } User justSavedUser = createAndSave(provider, oAuthResponse.providerId()); - String newUserToken = tokenProvider.createToken(justSavedUser.getId()); + List authorities = getUserAuthorities(justSavedUser); + String newUserToken = tokenProvider.createToken(justSavedUser.getId(), authorities); return new LoginResponse(newUserToken); } private LoginResponse joinedUserResponse(User joinedUser) { - String token = tokenProvider.createToken(joinedUser.getId()); + List authorities = getUserAuthorities(joinedUser); + String token = tokenProvider.createToken(joinedUser.getId(), authorities); return new LoginResponse(token); } @@ -162,7 +164,7 @@ private String getProfileUrl(MultipartFile profileImg) { private void updatingAuthority(List authorities, User user) { boolean hasNormalAuthority = authorities.stream() .map(UserAuthority::getAuthority) - .anyMatch(a -> a.equals(Authority.NORMAL)); + .anyMatch(a -> a == Authority.NORMAL); if (!hasNormalAuthority) { UserAuthority normalAuthority = generateUserAuthority(user, Authority.NORMAL); @@ -195,7 +197,7 @@ private UpdateResponse update(User user, UpdateRequest updateRequest) { private void checkNickname(String nickname, User user) { boolean isDuplicated = userRepository.existsByNickname(nickname); - if (isDuplicated && !Objects.equals(user.getNickname(), nickname)){ + if (isDuplicated && !Objects.equals(user.getNickname(), nickname)) { throw new DuplicateResourceException(UserErrorCode.DUPLICATED_NICKNAME); } } @@ -235,6 +237,14 @@ private void assignDefaultAuthority(User user) { userAuthorityRepository.save(newAuthority); } + private List getUserAuthorities(User user) { + List authorities = userAuthorityRepository.findByUser(user); + + return authorities.stream() + .map(UserAuthority::getAuthority) + .toList(); + } + private UserAuthority generateUserAuthority(User user, Authority authority) { return UserAuthority.builder() .authority(authority) diff --git a/src/test/java/team/silvertown/masil/config/jwt/JwtTokenProviderTest.java b/src/test/java/team/silvertown/masil/config/jwt/JwtTokenProviderTest.java index 088fd8fa..740b30ea 100644 --- a/src/test/java/team/silvertown/masil/config/jwt/JwtTokenProviderTest.java +++ b/src/test/java/team/silvertown/masil/config/jwt/JwtTokenProviderTest.java @@ -2,41 +2,33 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.Collections; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import team.silvertown.masil.common.exception.DataNotFoundException; -import team.silvertown.masil.user.exception.UserErrorCode; -import team.silvertown.masil.user.service.UserService; +import org.springframework.security.authentication.InsufficientAuthenticationException; @SpringBootTest @DisplayNameGeneration(ReplaceUnderscores.class) class JwtTokenProviderTest { - private static final String VALID_PROVIDER = "kakao"; - private static final String VALID_OAUTH_NAME = "valid oauth name"; - private static final String ROLE_PREFIX = "ROLE_"; - @Autowired JwtTokenProvider jwtTokenProvider; - @Autowired - UserService userService; - @Test - public void 권한이_없는_경우_토큰이_생성되지_않는다() throws Exception { + public void 권한이_없는_경우_토큰이_생성되지_않는다() { //given Long userId = 1L; //when - String token = jwtTokenProvider.createToken(userId); + String token = jwtTokenProvider.createToken(userId, Collections.emptyList()); //then assertThatThrownBy(() -> jwtTokenProvider.getAuthentication(token)) - .isInstanceOf(DataNotFoundException.class) - .hasMessage(UserErrorCode.AUTHORITY_NOT_FOUND.getMessage()); + .isInstanceOf(InsufficientAuthenticationException.class) + .hasMessage("No authority included in JWT"); } } diff --git a/src/test/java/team/silvertown/masil/user/domain/UserAuthorityTest.java b/src/test/java/team/silvertown/masil/user/domain/UserAuthorityTest.java index b77409cf..1c101a33 100644 --- a/src/test/java/team/silvertown/masil/user/domain/UserAuthorityTest.java +++ b/src/test/java/team/silvertown/masil/user/domain/UserAuthorityTest.java @@ -7,7 +7,6 @@ import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.Test; -import org.springframework.security.core.GrantedAuthority; import team.silvertown.masil.user.domain.UserAuthority.UserAuthorityBuilder; @DisplayNameGeneration(ReplaceUnderscores.class) @@ -28,17 +27,15 @@ class UserAuthorityTest { @Test void 유저_권한을_조회한다() { // given - UserAuthority authority = UserAuthority.builder() + UserAuthority userAuthority = UserAuthority.builder() .authority(Authority.RESTRICTED) .build(); // when - GrantedAuthority grantedAuthority = authority.getName(); + String role = userAuthority.getRole(); // then - String roleAuthority = grantedAuthority.getAuthority(); - - assertThat(roleAuthority).isEqualTo("ROLE_RESTRICTED"); + assertThat(role).isEqualTo("ROLE_RESTRICTED"); } }