Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.runimo.runimo.auth.jwt.JwtResolver;
import org.runimo.runimo.auth.jwt.UserDetail;
import org.runimo.runimo.common.response.ErrorResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
Expand All @@ -27,6 +29,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {

private static final String AUTH_HEADER = "Authorization";
private static final String AUTH_PREFIX = "Bearer ";
private static final String ROLE_PREFIX = "ROLE_";
private static final int TOKEN_PREFIX_LENGTH = 7;
private final JwtResolver jwtResolver;
private final ObjectMapper objectMapper = new ObjectMapper();
Expand Down Expand Up @@ -64,11 +67,11 @@ private String extractToken(HttpServletRequest request) {

private boolean processToken(String jwtToken, HttpServletResponse response) throws IOException {
try {
String userId = jwtResolver.getUserIdFromJwtToken(jwtToken);
UserDetail userDetail = jwtResolver.getUserDetailFromJwtToken(jwtToken);
Authentication authentication = new UsernamePasswordAuthenticationToken(
userId,
userDetail.userId(),
null,
Collections.emptyList()
List.of(new SimpleGrantedAuthority(ROLE_PREFIX + userDetail.role()))
);
SecurityContextHolder.getContext().setAuthentication(authentication);
return true;
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/runimo/runimo/auth/jwt/JwtResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ public DecodedJWT verifyJwtToken(String token) throws JWTVerificationException {
return JWT.require(Algorithm.HMAC256(jwtSecret)).withIssuer(ISSUER).build().verify(token);
}

public UserDetail getUserDetailFromJwtToken(String token) throws JWTVerificationException {
DecodedJWT jwt = verifyJwtToken(token);
String userId = jwt.getSubject();
String role = jwt.getClaim("role").asString();
return new UserDetail(userId, role);
}

public String getUserIdFromJwtToken(String token) throws JWTVerificationException {
DecodedJWT jwt = verifyJwtToken(token);
return jwt.getSubject();
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/org/runimo/runimo/auth/jwt/JwtTokenFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ public JwtTokenFactory(String jwtSecret, long jwtExpiration, long jwtRefreshExpi
this.tempJwtExpiration = tempJwtExpiration;
}

public String generateAccessToken(String userPublicId) {
public String generateAccessToken(User user) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpiration);

return JWT.create()
.withSubject(userPublicId)
.withSubject(user.getPublicId())
.withIssuedAt(now)
.withExpiresAt(expiryDate)
.withIssuer(ISSUER)
.withClaim("role", user.getRole().name())
.sign(Algorithm.HMAC256(jwtSecret));
}

Expand Down Expand Up @@ -65,7 +66,7 @@ public String generateSignupTemporalToken(String providerId, SocialProvider soci
}

public TokenPair generateTokenPair(User user) {
String accessToken = generateAccessToken(user.getPublicId());
String accessToken = generateAccessToken(user);
String refreshToken = generateRefreshToken(user.getPublicId());
return new TokenPair(accessToken, refreshToken);
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/runimo/runimo/auth/jwt/UserDetail.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.runimo.runimo.auth.jwt;

public record UserDetail(
String userId,
String role
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public TokenPair refreshAccessToken(String refreshToken) {
throw UserJwtException.of(UserHttpResponseCode.TOKEN_INVALID);
}

String newAccessToken = jwtTokenFactory.generateAccessToken(userPublicId);
String newAccessToken = jwtTokenFactory.generateAccessToken(user);
return new TokenPair(newAccessToken, refreshToken);
}
}
1 change: 1 addition & 0 deletions src/main/java/org/runimo/runimo/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public SecurityFilterChain devSecurityFilterChain(HttpSecurity http) throws Exce
.requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**")
.permitAll()
.requestMatchers(("/error")).permitAll()
.requestMatchers("/api/v1/users/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/runimo/runimo/user/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public class User extends BaseEntity {
@Column(name = "gender")
@Enumerated(EnumType.STRING)
private Gender gender;
@Column(name = "role", nullable = false)
@Enumerated(EnumType.STRING)
private UserRole role = UserRole.USER;


@Builder
public User(String nickname, String imgUrl, Long totalDistanceInMeters,
Expand All @@ -46,6 +50,7 @@ public User(String nickname, String imgUrl, Long totalDistanceInMeters,
this.totalDistanceInMeters = totalDistanceInMeters != null ? totalDistanceInMeters : 0L;
this.totalTimeInSeconds = totalTimeInSeconds != null ? totalTimeInSeconds : 0L;
this.gender = gender;
this.role = role != null ? role : UserRole.USER;
}

public boolean checkUserFirstRun() {
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/runimo/runimo/user/domain/UserRole.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.runimo.runimo.user.domain;

public enum UserRole {
USER,
ADMIN
}
35 changes: 18 additions & 17 deletions src/main/resources/sql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ CREATE TABLE `users`
`total_time_in_seconds` BIGINT NOT NULL DEFAULT 0,
`main_runimo_id` BIGINT,
`gender` VARCHAR(24),
`role` VARCHAR(24) NOT NULL DEFAULT 'USER',
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`deleted_at` TIMESTAMP NULL
Expand Down Expand Up @@ -91,16 +92,16 @@ CREATE TABLE `signup_token`

CREATE TABLE `running_record`
(
`id` INTEGER PRIMARY KEY AUTO_INCREMENT,
`user_id` INTEGER NOT NULL,
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT NOT NULL,
`record_public_id` VARCHAR(255) NOT NULL,
`title` VARCHAR(255),
`description` VARCHAR(255),
`img_url` VARCHAR(255),
`started_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`end_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`total_distance` INTEGER,
`pace_in_milli_seconds` INTEGER,
`total_distance` BIGINT,
`pace_in_milli_seconds` BIGINT,
`is_rewarded` BOOLEAN,
`pace_per_km` VARCHAR(10000),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
Expand All @@ -110,7 +111,7 @@ CREATE TABLE `running_record`

CREATE TABLE `item`
(
`id` INTEGER PRIMARY KEY AUTO_INCREMENT,
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`item_code` VARCHAR(255) NOT NULL,
`description` VARCHAR(255),
Expand All @@ -126,7 +127,7 @@ CREATE TABLE `item`

CREATE TABLE `egg_type`
(
`id` INTEGER PRIMARY KEY AUTO_INCREMENT,
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(64) NOT NULL,
`code` VARCHAR(64) NOT NULL,
`required_distance_in_meters` BIGINT,
Expand All @@ -139,11 +140,11 @@ CREATE TABLE `egg_type`

CREATE TABLE `item_activity`
(
`id` INTEGER PRIMARY KEY AUTO_INCREMENT,
`activity_user_id` INTEGER NOT NULL,
`activity_item_id` INTEGER NOT NULL,
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`activity_user_id` BIGINT NOT NULL,
`activity_item_id` BIGINT NOT NULL,
`activity_event_type` VARCHAR(255) NOT NULL,
`quantity` INTEGER,
`quantity` BIGINT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` TIMESTAMP NULL
Expand All @@ -152,9 +153,9 @@ CREATE TABLE `item_activity`
CREATE TABLE `user_item`
(
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` INTEGER NOT NULL,
`item_id` INTEGER NOT NULL,
`quantity` INTEGER NOT NULL,
`user_id` BIGINT NOT NULL,
`item_id` BIGINT NOT NULL,
`quantity` BIGINT NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` TIMESTAMP NULL
Expand All @@ -163,10 +164,10 @@ CREATE TABLE `user_item`
CREATE TABLE `incubating_egg`
(
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` INTEGER NOT NULL,
`egg_id` INTEGER NOT NULL,
`current_love_point_amount` INTEGER,
`hatch_require_amount` INTEGER,
`user_id` BIGINT NOT NULL,
`egg_id` BIGINT NOT NULL,
`current_love_point_amount` BIGINT,
`hatch_require_amount` BIGINT,
`egg_status` VARCHAR(255),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
Expand Down
6 changes: 6 additions & 0 deletions src/test/java/org/runimo/runimo/TestConsts.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.runimo.runimo;

public class TestConsts {
public static final String TEST_USER_UUID = "test-user-uuid-1";

}
55 changes: 55 additions & 0 deletions src/test/java/org/runimo/runimo/TokenUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.runimo.runimo;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import org.runimo.runimo.auth.jwt.JwtTokenFactory;
import org.runimo.runimo.user.domain.User;
import org.runimo.runimo.user.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TokenUtils {

private final static String AUTH_HEADER_PREFIX = "Bearer ";
private final static Long TEST_EXPIRATION_TIME = 1000L * 60 * 60 * 24 * 30; // 30 days
private final static String ISSUER = "RUNIMO_SERVICE";
private final static String TEST_SECRET = "testSecret";
@Autowired
private JwtTokenFactory jwtTokenFactory;
@Autowired
private UserRepository userRepository;


public String createTokenByUserPublicId(String publicId) {
User user = userRepository.findByPublicId(publicId)
.orElseThrow(() -> new IllegalStateException("테스트 유저가 존재하지 않습니다."));
return AUTH_HEADER_PREFIX + jwtTokenFactory.generateAccessToken(user);
}

public String createTestOidcToken(String testPublicId) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + TEST_EXPIRATION_TIME);

return JWT.create()
.withSubject(testPublicId)
.withIssuedAt(now)
.withExpiresAt(expiryDate)
.withIssuer(ISSUER)
.sign(Algorithm.HMAC256(TEST_SECRET));
}

public static String createTestOnlyToken(String testPublicId) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + TEST_EXPIRATION_TIME);

return AUTH_HEADER_PREFIX + JWT.create()
.withSubject(testPublicId)
.withIssuedAt(now)
.withExpiresAt(expiryDate)
.withIssuer(ISSUER)
.withClaim("role", "USER")
.sign(Algorithm.HMAC256(TEST_SECRET));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package org.runimo.runimo.auth.controller;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -15,7 +16,6 @@
import org.runimo.runimo.auth.domain.SignupToken;
import org.runimo.runimo.auth.jwt.JwtTokenFactory;
import org.runimo.runimo.auth.repository.SignupTokenRepository;

import org.runimo.runimo.auth.service.login.apple.AppleTokenVerifier;
import org.runimo.runimo.auth.service.login.kakao.KakaoLoginHandler;
import org.runimo.runimo.auth.service.login.kakao.KakaoTokenVerifier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

@ControllerTest(controllers = {AuthController.class})

class AuthControllerTest {

@Autowired
Expand All @@ -56,6 +56,7 @@ class AuthControllerTest {
@Autowired
private ObjectMapper objectMapper;


@Test
void 카카오_로그인_INVALID_401응답() throws Exception {
// given
Expand Down
Loading
Loading