Skip to content

Commit

Permalink
Merge pull request #113 from Team-SilverTown/refactor-authorization
Browse files Browse the repository at this point in the history
⚙️ 토큰 권한 리팩토링
  • Loading branch information
IjjS authored Mar 14, 2024
2 parents 202ccc2 + 6bf63f8 commit 8060d5b
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand All @@ -29,20 +31,19 @@ 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;
private final String issuer;
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;
Expand All @@ -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<Authority> 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<UserAuthority> userAuthorities = userAuthorityRepository.findAllByUserId(userId);
UserValidator.validateAuthority(userAuthorities);
if (StringUtils.isBlank(authorityNames)) {
throw new InsufficientAuthenticationException(NO_AUTHORITY);
}

List<GrantedAuthority> authorities = userAuthorities.stream()
.map(UserAuthority::getName)
List<Authority> authorities = Arrays.stream(authorityNames.split(AUTHORITIES_DELIM))
.map(Authority::get)
.toList();

return new UsernamePasswordAuthenticationToken(userId, token, authorities);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);

Expand Down
20 changes: 18 additions & 2 deletions src/main/java/team/silvertown/masil/user/domain/Authority.java
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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();
}

}
18 changes: 14 additions & 4 deletions src/main/java/team/silvertown/masil/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ public LoginResponse login(String kakaoToken) {
}

User justSavedUser = createAndSave(provider, oAuthResponse.providerId());
String newUserToken = tokenProvider.createToken(justSavedUser.getId());
List<Authority> 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<Authority> authorities = getUserAuthorities(joinedUser);
String token = tokenProvider.createToken(joinedUser.getId(), authorities);

return new LoginResponse(token);
}
Expand Down Expand Up @@ -162,7 +164,7 @@ private String getProfileUrl(MultipartFile profileImg) {
private void updatingAuthority(List<UserAuthority> 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);
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -235,6 +237,14 @@ private void assignDefaultAuthority(User user) {
userAuthorityRepository.save(newAuthority);
}

private List<Authority> getUserAuthorities(User user) {
List<UserAuthority> authorities = userAuthorityRepository.findByUser(user);

return authorities.stream()
.map(UserAuthority::getAuthority)
.toList();
}

private UserAuthority generateUserAuthority(User user, Authority authority) {
return UserAuthority.builder()
.authority(authority)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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");
}

}

0 comments on commit 8060d5b

Please sign in to comment.