Skip to content

Commit

Permalink
Merge branch 'feature/#40' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
julia-heo authored May 28, 2024
2 parents 6052467 + 362601a commit d5d4461
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 34 deletions.
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
version: '3'
services:

redis:
image: redis
container_name: redis
ports:
- 6379:6379
restart: always

web:
container_name: web
image: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import ewha.lux.once.domain.home.dto.FCMTokenDto;
import ewha.lux.once.domain.home.service.FirebaseCloudMessageService;
import ewha.lux.once.domain.user.dto.*;
import ewha.lux.once.domain.user.entity.Users;
import ewha.lux.once.domain.user.service.UserService;
import ewha.lux.once.global.common.CommonResponse;
import ewha.lux.once.global.common.CustomException;
import ewha.lux.once.global.common.ResponseCode;
import ewha.lux.once.global.common.UserAccount;
import ewha.lux.once.global.security.JwtProvider;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.data.repository.query.Param;
import org.springframework.http.MediaType;
Expand All @@ -35,13 +34,7 @@ public class UserController {
@PostMapping("/signup")
public CommonResponse<?> signup(@RequestBody SignupRequestDto request) throws ParseException {
try {
Users users = userService.signup(request);

String accessToken = jwtProvider.generateAccessToken(users.getLoginId());
String refreshToken = jwtProvider.generateRefreshToken(users.getLoginId());

LoginResponseDto loginResponseDto = new LoginResponseDto(users.getId(), accessToken, refreshToken);
return new CommonResponse<>(ResponseCode.SUCCESS, loginResponseDto);
return new CommonResponse<>(ResponseCode.SUCCESS, userService.signup(request));
} catch (CustomException e) {
return new CommonResponse<>(e.getStatus());
}
Expand All @@ -51,14 +44,18 @@ public CommonResponse<?> signup(@RequestBody SignupRequestDto request) throws Pa
@PostMapping("/login")
public CommonResponse<?> signin(@RequestBody SignInRequestDto request) {
try {
Users user = userService.authenticate(request);

String accessToken = jwtProvider.generateAccessToken(user.getLoginId());
String refreshToken = jwtProvider.generateRefreshToken(user.getLoginId());

LoginResponseDto loginResponseDto = new LoginResponseDto(user.getId(), accessToken, refreshToken);
return new CommonResponse<>(ResponseCode.SUCCESS, userService.authenticate(request));
} catch (CustomException e) {
return new CommonResponse<>(e.getStatus());
}
}

return new CommonResponse<>(ResponseCode.SUCCESS, loginResponseDto);
// [Post] ๋กœ๊ทธ์•„์›ƒ
@PostMapping("/logout")
public CommonResponse<?> logout(HttpServletRequest request, @AuthenticationPrincipal UserAccount userAccount) {
try {
userService.postLogout(userAccount.getUsers(), request);
return new CommonResponse<>(ResponseCode.SUCCESS);
} catch (CustomException e) {
return new CommonResponse<>(e.getStatus());
}
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/ewha/lux/once/domain/user/service/RedisService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ewha.lux.once.domain.user.service;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class RedisService {
private final RedisTemplate redisTemplate;

public void setValueWithTTL(String key, Object value, long timeout, TimeUnit unit) {
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
valueOperations.set(key, value, timeout, unit);
}

public Object getValue(String key) {
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
Object object = valueOperations.get(key);
return object;
}

public void deleteValue(String key) {
redisTemplate.delete(key);
}
}
32 changes: 28 additions & 4 deletions src/main/java/ewha/lux/once/domain/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import ewha.lux.once.global.common.CustomException;
import ewha.lux.once.global.common.ResponseCode;
import ewha.lux.once.global.repository.*;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
Expand Down Expand Up @@ -40,8 +42,10 @@ public class UserService implements UserDetailsService {
private final FavoriteRepository favoriteRepository;
private final FCMTokenRepository fcmTokenRepository;
private final S3Uploader s3Uploader;
private final RedisTemplate redisTemplate;
private final RedisService redisService;

public Users signup(SignupRequestDto request) throws CustomException, ParseException {
public LoginResponseDto signup(SignupRequestDto request) throws CustomException, ParseException {
String loginId = request.getLoginId();
String username = request.getUsername();
String password = request.getPassword();
Expand Down Expand Up @@ -77,10 +81,18 @@ public Users signup(SignupRequestDto request) throws CustomException, ParseExcep
usersBuilder.birthday(birthday);
}

return usersRepository.save(usersBuilder.benefitGoal(100000).build());
Users newUser = usersRepository.save(usersBuilder.benefitGoal(100000).build());

String accessToken = jwtProvider.generateAccessToken(newUser.getLoginId());
String refreshToken = jwtProvider.generateRefreshToken(newUser.getLoginId());

LoginResponseDto loginResponseDto = new LoginResponseDto(newUser.getId(), accessToken, refreshToken);
redisService.setValueWithTTL(refreshToken, newUser.getId().toString(), 14L, TimeUnit.DAYS);

return loginResponseDto;
}

public Users authenticate(SignInRequestDto request) throws CustomException {
public LoginResponseDto authenticate(SignInRequestDto request) throws CustomException {
String loginId = request.getLoginId();
String password = request.getPassword();

Expand All @@ -93,7 +105,19 @@ public Users authenticate(SignInRequestDto request) throws CustomException {

users.setLastLogin();
usersRepository.save(users);
return users;

String accessToken = jwtProvider.generateAccessToken(users.getLoginId());
String refreshToken = jwtProvider.generateRefreshToken(users.getLoginId());
LoginResponseDto loginResponseDto = new LoginResponseDto(users.getId(), accessToken, refreshToken);
redisService.setValueWithTTL(refreshToken, users.getId().toString(), 14L, TimeUnit.DAYS);

return loginResponseDto;
}

public void postLogout(Users nowUser, HttpServletRequest request) throws CustomException {
String token = jwtProvider.resolveAccessToken(request);
Long expiration = jwtProvider.getExpiration(token);
redisTemplate.opsForValue().set(token, "logout", expiration, TimeUnit.MILLISECONDS);
}

public void deleteUsers(Users nowUser) throws CustomException {
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/ewha/lux/once/global/config/JwtSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ewha.lux.once.global.config;

import ewha.lux.once.domain.user.service.RedisService;
import ewha.lux.once.global.security.JwtAuthFilter;
import ewha.lux.once.global.security.JwtProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@RequiredArgsConstructor
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final JwtProvider jwtProvider;
private final RedisService redisService;

@Override
public void configure(HttpSecurity http) throws Exception{
http.addFilterBefore(new JwtAuthFilter(jwtProvider, redisService), UsernamePasswordAuthenticationFilter.class);
}
}
33 changes: 33 additions & 0 deletions src/main/java/ewha/lux/once/global/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ewha.lux.once.global.config;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@RequiredArgsConstructor
@Configuration
@EnableRedisRepositories
public class RedisConfig {
private final RedisProperties redisProperties;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}

@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}

}
5 changes: 3 additions & 2 deletions src/main/java/ewha/lux/once/global/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ewha.lux.once.global.config;

import ewha.lux.once.domain.user.service.RedisService;
import ewha.lux.once.global.security.JwtAuthFilter;
import ewha.lux.once.global.security.JwtProvider;
import lombok.RequiredArgsConstructor;
Expand All @@ -25,7 +26,7 @@
@Configuration
public class SecurityConfig {
private final JwtProvider jwtProvider;

private final RedisService redisService;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand Down Expand Up @@ -60,7 +61,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers("/user/card/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class);
.addFilterBefore(new JwtAuthFilter(jwtProvider, redisService), UsernamePasswordAuthenticationFilter.class);


return http.build();
Expand Down
26 changes: 15 additions & 11 deletions src/main/java/ewha/lux/once/global/security/JwtAuthFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import ewha.lux.once.domain.user.dto.LoginResponseDto;
import ewha.lux.once.domain.user.service.RedisService;
import ewha.lux.once.global.common.CustomException;
import ewha.lux.once.global.common.UserAccount;
import ewha.lux.once.domain.user.entity.Users;
import ewha.lux.once.global.common.ResponseDto;
Expand All @@ -28,6 +30,7 @@
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
private final RedisService redisService;
public static final String HEADER_KEY = "Authorization";
public static final String REFRESH_HEADER_KEY = "Authorization-refresh";
public static final String PREFIX = "Bearer ";
Expand All @@ -37,20 +40,27 @@ public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

String accessToken = resolveToken(request, HEADER_KEY);
String refreshToken = resolveToken(request, REFRESH_HEADER_KEY);

// ์ž๋™ ๋กœ๊ทธ์ธ ์š”์ฒญ์ธ ๊ฒฝ์šฐ
if(request.getRequestURI().equals("/user/auto")) {

String accessToken = resolveToken(request, HEADER_KEY);
String refreshToken = resolveToken(request, REFRESH_HEADER_KEY);

if ("Deprecated".equals(redisService.getValue(refreshToken))) {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write(new ObjectMapper().writeValueAsString(ResponseEntity.ok(ResponseDto.response(1000, true, "refresh token์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”"))));
return;
}
// ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
if (!jwtProvider.validateAccessTokenExpiration(accessToken)) { // accesstoken์ด ์œ ํšจํ•œ ๊ฒฝ์šฐ
Users users = jwtProvider.validateTokenAndGetUsers(accessToken);
LoginResponseDto loginResponseDto = new LoginResponseDto(users.getId(), accessToken, refreshToken);

response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(new ObjectMapper().writeValueAsString(ResponseEntity.ok(ResponseDto.response(1000,true, "access token์ด ๊ฒ€์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", loginResponseDto))));
response.getWriter().write(new ObjectMapper().writeValueAsString(ResponseEntity.ok(ResponseDto.response(1000, true, "access token์ด ๊ฒ€์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", loginResponseDto))));
return;
} else if (!jwtProvider.validateAccessTokenExpiration(refreshToken)) { // accesstoken ๋งŒ๋ฃŒ, refreshtoken์ด ์œ ํšจํ•œ ๊ฒฝ์šฐ
Users users = jwtProvider.validateTokenAndGetUsers(refreshToken);
Expand All @@ -70,19 +80,13 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
return;
}
}

String accessToken = resolveToken(request, HEADER_KEY);

if(StringUtils.hasText(accessToken) && jwtProvider.validateToken(accessToken)){
Users users = jwtProvider.validateTokenAndGetUsers(accessToken);

saveAuthentication(users);
filterChain.doFilter(request, response);
return;
}



filterChain.doFilter(request, response);
}
public void saveAuthentication(Users users) {
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/ewha/lux/once/global/security/JwtProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import ewha.lux.once.global.repository.UsersRepository;
import ewha.lux.once.domain.user.service.UserService;
import io.jsonwebtoken.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.*;

Expand All @@ -17,8 +19,9 @@
public class JwtProvider {
private static final long ACCESS_EXPIRE_TIME = 1000l * 60 * 60 * 2; // 2์‹œ๊ฐ„
private static final long REFRESH_EXPIRE_TIME = 1000l * 60 * 60 * 24 * 14; // 2์ฃผ
private final UserService userService;
private final UsersRepository usersRepository;
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";

@Getter
@Value("${spring.jwt.secret}")
Expand Down Expand Up @@ -95,5 +98,18 @@ public boolean validateAccessTokenExpiration(String token) {
}
}

// accessToken ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
public String resolveAccessToken(HttpServletRequest request) {
String token = request.getHeader(JwtProvider.HEADER_STRING);
if (StringUtils.hasText(token) && token.startsWith(JwtProvider.TOKEN_PREFIX)) {
return token.replace(JwtProvider.TOKEN_PREFIX, "");
}
return null;
}

// accessToken ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๋ฐ˜ํ™˜
public Long getExpiration(String token) {
Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
return claims.getExpiration().getTime();
}
}
4 changes: 4 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
spring:
data:
redis:
host: redis
port: 6379
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: ${SPRING_DATABASE_URL}
Expand Down

0 comments on commit d5d4461

Please sign in to comment.