Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RELEASES] 탈퇴 기능 & 다중 디바이스 대응 배포 #122

Merged
merged 9 commits into from
May 31, 2024
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
package com.nextroom.nextRoomServer.controller;

import static com.nextroom.nextRoomServer.exceptions.StatusCode.*;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.nextroom.nextRoomServer.exceptions.StatusCode.OK;

import com.nextroom.nextRoomServer.dto.AuthDto;
import com.nextroom.nextRoomServer.dto.BaseResponse;
import com.nextroom.nextRoomServer.dto.DataResponse;
import com.nextroom.nextRoomServer.service.AuthService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Auth")
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthController {

private final AuthService authService;

@Operation(
Expand Down Expand Up @@ -61,11 +61,23 @@ public ResponseEntity<BaseResponse> logIn(@RequestBody @Valid AuthDto.LogInReque
public ResponseEntity<BaseResponse> reissue(@RequestBody AuthDto.ReissueRequestDto request) {
return ResponseEntity.ok(new DataResponse<>(OK, authService.reissue(request)));
}


@PostMapping("/rtdntest")
public void reissue(@RequestBody Object message) {
System.out.println(message);

}

@Operation(
summary = "회원탈퇴",
responses = {
@ApiResponse(responseCode = "200", description = "OK"),
}
)
@DeleteMapping("/unregister")
public ResponseEntity<BaseResponse> unregister() {
authService.unregister();
return ResponseEntity.ok(new BaseResponse(OK));
}
}
33 changes: 0 additions & 33 deletions src/main/java/com/nextroom/nextRoomServer/domain/RefreshToken.java

This file was deleted.

19 changes: 9 additions & 10 deletions src/main/java/com/nextroom/nextRoomServer/domain/Shop.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
package com.nextroom.nextRoomServer.domain;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.annotations.Comment;

import com.nextroom.nextRoomServer.util.Timestamped;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
Expand All @@ -17,17 +10,23 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Comment;

@Entity
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Shop extends Timestamped {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "shop_id", nullable = false)
Expand Down Expand Up @@ -59,14 +58,14 @@ public class Shop extends Timestamped {
@Column
private LocalDateTime lastLoginAt;

@OneToMany(mappedBy = "shop", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "shop", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<Theme> themes = new ArrayList<>();

public void updateLastLoginAt() {
this.lastLoginAt = LocalDateTime.now();
}

// @OneToOne(mappedBy = "shop", cascade = CascadeType.DETACH)
// private Subscription subscription;
@OneToOne(mappedBy = "shop", cascade = CascadeType.ALL, orphanRemoval = true)
private Subscription subscription;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class Theme extends Timestamped {
@Column(nullable = false)
private int hintLimit;

@OneToMany(mappedBy = "theme", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "theme", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<Hint> hints = new ArrayList<>();

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.nextroom.nextRoomServer.repository;

import com.nextroom.nextRoomServer.domain.Subscription;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.nextroom.nextRoomServer.domain.Subscription;

public interface SubscriptionRepository extends JpaRepository<Subscription, Long> {

Optional<Subscription> findByShopId(Long id);

Optional<Subscription> findByPurchaseToken(String purchaseToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class SecurityUtil {
private SecurityUtil() {
}

public static Long getCurrentMemberId() {
public static Long getCurrentShopId() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

if (authentication == null || authentication.getName() == null) {
Expand All @@ -24,9 +24,4 @@ public static Long getCurrentMemberId() {

return Long.parseLong(authentication.getName());
}

public static Long getRequestedShopId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return Long.parseLong(authentication.getPrincipal().toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
public class TokenProvider {
private static final String AUTHORITIES_KEY = "auth";
private static final String BEARER_TYPE = "Bearer";
// private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 2; // 2시간
// private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 14; // 14일
private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000L * 60 * 60 * 5; // 5시간
private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000L * 60 * 60 * 24 * 30; // 30일
@Value("${jwt.access-token-expiration-millis}")
private long accessTokenExpirationMillis;
@Value("${jwt.refresh-token-expiration-millis}")
private long refreshTokenExpirationMillis;

private final Key key;

Expand All @@ -53,7 +53,7 @@ public TokenDto generateTokenDto(Authentication authentication) {
long now = (new Date()).getTime();

// Access Token 생성
Date accessTokenExpiresIn = new Date(now + ACCESS_TOKEN_EXPIRE_TIME);
Date accessTokenExpiresIn = new Date(now + accessTokenExpirationMillis);
String accessToken = Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
Expand All @@ -62,7 +62,7 @@ public TokenDto generateTokenDto(Authentication authentication) {
.compact();

String refreshToken = Jwts.builder()
.setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME))
.setExpiration(new Date(now + refreshTokenExpirationMillis))
.signWith(key, SignatureAlgorithm.HS512)
.compact();

Expand All @@ -85,12 +85,9 @@ public Authentication getAuthentication(String accessToken) {
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

.toList();
UserDetails principal = new User(claims.getSubject(), "", authorities);

return new UsernamePasswordAuthenticationToken(principal.getUsername(), principal.getPassword(),
principal.getAuthorities());
return new UsernamePasswordAuthenticationToken(principal, null, authorities);
}

public boolean validateToken(String token) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ public class JwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws
IOException,
ServletException,
ServletException,
IOException {
String jwt = resolveToken(request);
Expand Down
40 changes: 26 additions & 14 deletions src/main/java/com/nextroom/nextRoomServer/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@
import static com.nextroom.nextRoomServer.exceptions.StatusCode.*;
import static com.nextroom.nextRoomServer.util.Timestamped.*;

import java.time.Duration;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;

import com.nextroom.nextRoomServer.domain.RefreshToken;
import com.nextroom.nextRoomServer.domain.Shop;
import com.nextroom.nextRoomServer.domain.Subscription;
import com.nextroom.nextRoomServer.dto.AuthDto;
import com.nextroom.nextRoomServer.dto.TokenDto;
import com.nextroom.nextRoomServer.exceptions.CustomException;
import com.nextroom.nextRoomServer.repository.RefreshTokenRepository;
import com.nextroom.nextRoomServer.repository.RedisRepository;
import com.nextroom.nextRoomServer.repository.ShopRepository;
import com.nextroom.nextRoomServer.repository.SubscriptionRepository;
import com.nextroom.nextRoomServer.security.SecurityUtil;
import com.nextroom.nextRoomServer.security.TokenProvider;
import com.nextroom.nextRoomServer.util.RandomCodeGenerator;
import com.nextroom.nextRoomServer.util.Timestamped;
Expand All @@ -31,14 +34,19 @@
@Service
@RequiredArgsConstructor
public class AuthService {

private final ShopRepository shopRepository;
private final RefreshTokenRepository refreshTokenRepository;
private final SubscriptionRepository subscriptionRepository;
private final RedisRepository redisRepository;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final PasswordEncoder passwordEncoder;
private final TokenProvider tokenProvider;
private final RandomCodeGenerator randomCodeGenerator;

@Value("${jwt.refresh-token-expiration-millis}")
private long refreshTokenExpirationMillis;
private static final String REFRESH_TOKEN_PREFIX = "RefreshToken ";

@Transactional
public AuthDto.SignUpResponseDto signUp(AuthDto.SignUpRequestDto request) {
if (shopRepository.existsByEmail(request.getEmail())) {
Expand Down Expand Up @@ -87,12 +95,9 @@ public AuthDto.LogInResponseDto login(@RequestBody AuthDto.LogInRequestDto reque
AuthDto.LogInResponseDto response = AuthDto.LogInResponseDto.toLogInResponseDto(shop.getName(),
shop.getAdminCode(), token);

RefreshToken refreshToken = RefreshToken.builder()
.key(authentication.getName())
.value(response.getRefreshToken())
.build();

refreshTokenRepository.save(refreshToken);
redisRepository.setValues(REFRESH_TOKEN_PREFIX + authentication.getName() + " " + response.getRefreshToken(),
response.getRefreshToken(),
Duration.ofMillis(refreshTokenExpirationMillis));

shop.updateLastLoginAt();

Expand All @@ -107,18 +112,25 @@ public AuthDto.ReissueResponseDto reissue(AuthDto.ReissueRequestDto request) {

Authentication authentication = tokenProvider.getAuthentication(request.getAccessToken());

RefreshToken refreshToken = refreshTokenRepository.findByKey(authentication.getName())
.orElseThrow(() -> new CustomException(SHOP_IS_LOG_OUT));
String redisKey = REFRESH_TOKEN_PREFIX + authentication.getName() + " " + request.getRefreshToken();
String refreshToken = redisRepository.getValues(redisKey);

if (!refreshToken.getValue().equals(request.getRefreshToken())) {
if (!refreshToken.equals(request.getRefreshToken())) {
throw new CustomException(INVALID_REFRESH_TOKEN);
}

AuthDto.ReissueResponseDto response = tokenProvider.generateTokenDto(authentication).toReissueResponseDto();

RefreshToken newRefreshToken = refreshToken.updateValue(response.getRefreshToken());
refreshTokenRepository.save(newRefreshToken);
redisRepository.deleteValues(redisKey);
redisRepository.setValues(REFRESH_TOKEN_PREFIX + authentication.getName() + " " + response.getRefreshToken(),
response.getRefreshToken(),
Duration.ofMillis(refreshTokenExpirationMillis));

return response;
}

@Transactional
public void unregister() {
shopRepository.deleteById(SecurityUtil.getCurrentShopId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void addHint(HintDto.AddHintRequest request) {
throw new CustomException(HINT_CODE_CONFLICT);
}

if (!Objects.equals(theme.getShop().getId(), SecurityUtil.getRequestedShopId())) {
if (!Objects.equals(theme.getShop().getId(), SecurityUtil.getCurrentShopId())) {
throw new CustomException(NOT_PERMITTED);
}

Expand All @@ -55,7 +55,7 @@ public List<HintDto.HintListResponse> getHintList(Long themeId) {
themeId) // TODO optimize by making method get theme from shop
.orElseThrow(() -> new CustomException(THEME_NOT_FOUND));

if (!Objects.equals(theme.getShop().getId(), SecurityUtil.getRequestedShopId())) {
if (!Objects.equals(theme.getShop().getId(), SecurityUtil.getCurrentShopId())) {
throw new CustomException(NOT_PERMITTED);
}

Expand All @@ -68,7 +68,7 @@ public void editHint(HintDto.EditHintRequest request) {
() -> new CustomException(TARGET_HINT_NOT_FOUND)
);

if (!Objects.equals(hint.getTheme().getShop().getId(), SecurityUtil.getRequestedShopId())) {
if (!Objects.equals(hint.getTheme().getShop().getId(), SecurityUtil.getCurrentShopId())) {
throw new CustomException(NOT_PERMITTED);
}

Expand All @@ -80,7 +80,7 @@ public void removeHint(HintDto.RemoveHintRequest request) {
() -> new CustomException(HINT_NOT_FOUND)
);

if (!Objects.equals(hint.getTheme().getShop().getId(), SecurityUtil.getRequestedShopId())) {
if (!Objects.equals(hint.getTheme().getShop().getId(), SecurityUtil.getCurrentShopId())) {
throw new CustomException(NOT_PERMITTED);
}

Expand Down
Loading
Loading