Skip to content
Closed
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
4 changes: 3 additions & 1 deletion src/main/java/Gotcha/common/config/SecurityFilterConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import Gotcha.common.jwt.TokenProvider;
import Gotcha.common.jwt.filter.JwtAuthenticationFilter;
import Gotcha.common.jwt.filter.JwtExceptionFilter;
import Gotcha.domain.guestUser.service.GuestUserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
Expand All @@ -17,10 +18,11 @@ public class SecurityFilterConfig {
private final TokenProvider tokenProvider;
private final ObjectMapper objectMapper;
private final BlackListTokenService blackListTokenService;
private final GuestUserService guestUserService;

@Bean
public JwtAuthenticationFilter authenticationFilter() {
return new JwtAuthenticationFilter(userDetailsService, tokenProvider, blackListTokenService);
return new JwtAuthenticationFilter(userDetailsService, tokenProvider, blackListTokenService, guestUserService);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public class SecurityConstants {
public static final String[] PUBLIC_ENDPOINTS = {
"/swagger-resources/**", "/swagger-ui/**", "/v3/api-docs/**",
"/webjars/**", "/error", "/api/v1/auth/**", "/api/v1/users/nickname-check"
"/webjars/**", "/error", "/api/v1/auth/**", "/api/v1/users/nickname-check", "/api/v1/guest/sign-in"
};

public static final String[] ADMIN_ENDPOINTS = {"/api/v1/admin/**"};
Expand Down
32 changes: 23 additions & 9 deletions src/main/java/Gotcha/common/jwt/JwtHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import Gotcha.common.jwt.exception.JwtExceptionCode;
import Gotcha.common.util.CookieUtil;
import Gotcha.domain.auth.dto.TokenDto;
import Gotcha.domain.guestUser.entity.GuestUser;
import Gotcha.domain.user.entity.Role;
import Gotcha.domain.user.entity.User;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
Expand All @@ -25,30 +27,42 @@ public class JwtHelper {

public TokenDto createToken(User user) {
Long userId = user.getId();
String email = user.getEmail();
String username = user.getEmail();
String role = String.valueOf(user.getRole());

String accessToken = TOKEN_PREFIX + tokenProvider.createAccessToken(role, userId, email);
String refreshToken = tokenProvider.createRefreshToken(role, userId, email);
String accessToken = TOKEN_PREFIX + tokenProvider.createAccessToken(role, userId, username);
String refreshToken = tokenProvider.createRefreshToken(role, userId, username);

refreshTokenService.saveRefreshToken(email, refreshToken);
refreshTokenService.saveRefreshToken(username, refreshToken);
return new TokenDto(accessToken, refreshToken);
}

public TokenDto createGuestToken(GuestUser guestUser) {
Long userId = guestUser.getGuestId();
String nickname = guestUser.getGuestNickname();
String role = String.valueOf(Role.GUEST);

String accessToken = TOKEN_PREFIX + tokenProvider.createAccessToken(role, userId, nickname);
String refreshToken = tokenProvider.createRefreshToken(role, userId, nickname);

refreshTokenService.saveRefreshToken(nickname, refreshToken);
return new TokenDto(accessToken, refreshToken);
}

public TokenDto reissueToken(String refreshToken) {
String email = tokenProvider.getEmail(refreshToken);
String username = tokenProvider.getUsername(refreshToken);

if (!refreshTokenService.existedRefreshToken(email, refreshToken))
if (!refreshTokenService.existedRefreshToken(username, refreshToken))
throw new CustomException(JwtExceptionCode.REFRESH_TOKEN_NOT_FOUND);

Long userId = tokenProvider.getUserId(refreshToken);
String role = tokenProvider.getRole(refreshToken);

String newAccessToken = TOKEN_PREFIX + tokenProvider.createAccessToken(role, userId, email);
String newRefreshToken = tokenProvider.createRefreshToken(role, userId, email);
String newAccessToken = TOKEN_PREFIX + tokenProvider.createAccessToken(role, userId, username);
String newRefreshToken = tokenProvider.createRefreshToken(role, userId, username);

refreshTokenService.deleteRefreshToken(refreshToken);
refreshTokenService.saveRefreshToken(email, newRefreshToken);
refreshTokenService.saveRefreshToken(username, newRefreshToken);

return new TokenDto(newAccessToken, newRefreshToken);
}
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/Gotcha/common/jwt/RefreshTokenService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ public class RefreshTokenService {
@Value("${token.refresh.in-redis}")
private long REFRESH_EXPIRATION;

public void saveRefreshToken(String email, String refreshToken) {
String key = REFRESH_TOKEN_KEY_PREFIX + email;
public void saveRefreshToken(String username, String refreshToken) {
String key = REFRESH_TOKEN_KEY_PREFIX + username;
redisUtil.setData(key, refreshToken);
redisUtil.setDataExpire(key, REFRESH_EXPIRATION);
}

public void deleteRefreshToken(String refreshToken) {
String email = tokenProvider.getEmail(refreshToken);
String key = REFRESH_TOKEN_KEY_PREFIX + email;
String username = tokenProvider.getUsername(refreshToken);
String key = REFRESH_TOKEN_KEY_PREFIX + username;
redisUtil.deleteData(key);
}

public boolean existedRefreshToken(String email, String requestRefreshToken) {
String key = REFRESH_TOKEN_KEY_PREFIX + email;
public boolean existedRefreshToken(String username, String requestRefreshToken) {
String key = REFRESH_TOKEN_KEY_PREFIX + username;

String storedRefreshToken = (String) redisUtil.getData(key);

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/Gotcha/common/jwt/TokenProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private Claims getClaims(String token) {
.getPayload();
}

public String getEmail(String token) {
public String getUsername(String token) {
return getClaims(token).getSubject();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package Gotcha.common.jwt;

import Gotcha.common.jwt.userDetails.SecurityUserDetails;
import Gotcha.domain.user.entity.User;
import Gotcha.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import Gotcha.common.jwt.BlackListTokenService;
import Gotcha.common.jwt.exception.JwtExceptionCode;
import Gotcha.common.jwt.TokenProvider;
import Gotcha.common.jwt.userDetails.GuestUserDetails;
import Gotcha.domain.guestUser.entity.GuestUser;
import Gotcha.domain.guestUser.service.GuestUserService;
import Gotcha.domain.user.entity.Role;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand Down Expand Up @@ -32,6 +36,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final TokenProvider tokenProvider;
private final BlackListTokenService blackListTokenService;
private final GuestUserService guestUserService;

private static final String SPECIAL_CHARACTERS_PATTERN = "[`':;|~!@#$%()^&*+=?/{}\\[\\]\\\"\\\\\"]+$";
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
Expand All @@ -52,8 +57,17 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

String accessToken = resolveAccessToken(response, accessTokenHeader);

String username = tokenProvider.getEmail(accessToken);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
String role = tokenProvider.getRole(accessToken);
UserDetails userDetails;

if (String.valueOf(Role.GUEST).equals(role)) {
Long guestId = tokenProvider.getUserId(accessToken);
GuestUser guestUser = guestUserService.getGuestUser(guestId);
userDetails = new GuestUserDetails(guestUser);
} else {
String username = tokenProvider.getUsername(accessToken);
userDetails = userDetailsService.loadUserByUsername(username);
}

Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/Gotcha/common/jwt/userDetails/GuestUserDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package Gotcha.common.jwt.userDetails;

import Gotcha.common.security.CustomGrantedAuthority;
import Gotcha.domain.guestUser.entity.GuestUser;
import Gotcha.domain.user.entity.Role;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

@NoArgsConstructor
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public class GuestUserDetails implements UserDetails {
private GuestUser guestUser;

public GuestUserDetails(GuestUser guestUser) {
this.guestUser = guestUser;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new CustomGrantedAuthority(Role.GUEST.getValue()));
}

@Override
public String getPassword() {
return null;
}

public Long getGuestId() {
return guestUser.getGuestId();
}

@Override
public String getUsername() {
return guestUser.getGuestNickname();
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package Gotcha.common.jwt;
package Gotcha.common.jwt.userDetails;

import Gotcha.common.security.CustomGrantedAuthority;
import Gotcha.domain.user.entity.User;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/Gotcha/common/util/RedisUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public Object getData(String key) {
return redisTemplate.opsForValue().get(key);
}

public void setData(String key, String value) {
public void setData(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}

Expand Down
25 changes: 25 additions & 0 deletions src/main/java/Gotcha/domain/guestUser/api/GuestUserApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package Gotcha.domain.guestUser.api;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;

@Tag(name = "[게스트 API]", description = "게스트 관련 API")
public interface GuestUserApi {
@Operation(summary = "게스트 로그인", description = "게스트 로그인 API")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "게스트 로그인 성공",
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(value = """
{
"accessToken": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiLsi5zrgYTrn6zsmrTrgpntg4AiLCJyb2xlIjoiR1VFU1QiLCJ1c2VySWQiOjY3OTkyNTU1MjE4ODE1NzMyMDEsImlzcyI6ImdvdGNoYSEiLCJpYXQiOjE3NDI5OTUyMjksImV4cCI6MTc0Mjk5NzAyOX0.jwE4E2ZS0jNaKFifOQjeDUFGhlfaqXOyN_kxgfC6rLw"
}
""")
}))
})
ResponseEntity<?> guestSignIn();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package Gotcha.domain.guestUser.controller;

import Gotcha.common.util.CookieUtil;
import Gotcha.domain.auth.dto.TokenDto;
import Gotcha.domain.guestUser.api.GuestUserApi;
import Gotcha.domain.guestUser.service.GuestUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

import static Gotcha.common.jwt.JwtProperties.REFRESH_COOKIE_VALUE;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/guest")
public class GuestUserController implements GuestUserApi {
private final GuestUserService guestUserService;
private final CookieUtil cookieUtil;

@PostMapping("/sign-in")
public ResponseEntity<?> guestSignIn(){
TokenDto tokenDto = guestUserService.createGuestAccessToken();

return createTokenRes(tokenDto);
}

private ResponseEntity<?> createTokenRes(TokenDto tokenDto) {
Map<String, Object> responseData = new HashMap<>();
responseData.put("accessToken", tokenDto.accessToken());

return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE,
cookieUtil.createCookie(REFRESH_COOKIE_VALUE,
tokenDto.refreshToken()).toString())
.body(responseData);
}
}
16 changes: 16 additions & 0 deletions src/main/java/Gotcha/domain/guestUser/entity/GuestUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package Gotcha.domain.guestUser.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;


@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class GuestUser {
Long guestId;
String guestNickname;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package Gotcha.domain.guestUser.service;

import Gotcha.common.jwt.JwtHelper;
import Gotcha.common.util.RedisUtil;
import Gotcha.domain.auth.dto.TokenDto;
import Gotcha.domain.guestUser.entity.GuestUser;
import Gotcha.domain.guestUser.util.RandomNicknameGenerator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.UUID;

@RequiredArgsConstructor
@Service
public class GuestUserService {
private final JwtHelper jwtHelper;
private final RedisUtil redisUtil;

private static final long GUEST_TTL_SECONDS = 30 * 60;

public TokenDto createGuestAccessToken() {
//무작위 아이디 값 생성
Long guestId;
do{
guestId = UUID.randomUUID().getMostSignificantBits();
}while(redisUtil.existed("guest:" + guestId));
//무작위 닉네임 생성
String nickname = RandomNicknameGenerator.generateNickname();

//게스트 유저 생성
GuestUser guestUser = GuestUser.builder()
.guestId(guestId)
.guestNickname(nickname)
.build();

//게스트 유저 정보를 Redis에 저장
redisUtil.setData("guest:" + guestId, guestUser);
redisUtil.setDataExpire("guest:" + guestId, GUEST_TTL_SECONDS);

//게스트 유저 토큰 생성
return jwtHelper.createGuestToken(guestUser);
}

public GuestUser getGuestUser(Long guestId){
//매 요청 시 게스트가 활동 중이라는 의미이므로 데이터가 삭제되지 않게 하기 위해 TTL을 갱신
redisUtil.setDataExpire("guest:" + guestId, GUEST_TTL_SECONDS);
return (GuestUser) redisUtil.getData("guest:" + guestId);
}
}
Loading