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 @@ -7,11 +7,14 @@
import com.swyp8team2.auth.domain.Provider;
import com.swyp8team2.auth.domain.SocialAccount;
import com.swyp8team2.auth.domain.SocialAccountRepository;
import com.swyp8team2.crypto.application.CryptoService;
import com.swyp8team2.user.application.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.security.NoSuchAlgorithmException;

@Service
@RequiredArgsConstructor
public class AuthService {
Expand All @@ -20,6 +23,7 @@ public class AuthService {
private final OAuthService oAuthService;
private final SocialAccountRepository socialAccountRepository;
private final UserService userService;
private final CryptoService cryptoService;

@Transactional
public TokenPair oauthSignIn(String code, String redirectUri) {
Expand All @@ -39,4 +43,9 @@ private SocialAccount createUser(OAuthUserInfo oAuthUserInfo) {
public TokenPair reissue(String refreshToken) {
return jwtService.reissue(refreshToken);
}

public String guestLogin() {
Long guestId = userService.createGuest();
return cryptoService.encrypt(String.valueOf(guestId));
}
}
3 changes: 2 additions & 1 deletion src/main/java/com/swyp8team2/auth/domain/SocialAccount.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.swyp8team2.auth.domain;

import com.swyp8team2.auth.application.oauth.dto.OAuthUserInfo;
import com.swyp8team2.common.domain.BaseEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
Expand All @@ -16,7 +17,7 @@
@Getter
@Entity
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
public class SocialAccount {
public class SocialAccount extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
7 changes: 5 additions & 2 deletions src/main/java/com/swyp8team2/auth/domain/UserInfo.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package com.swyp8team2.auth.domain;

import com.swyp8team2.user.domain.Role;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

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

import static com.swyp8team2.common.util.Validator.validateNull;

public record UserInfo(long userId) implements UserDetails {
public record UserInfo(long userId, Role role) implements UserDetails {

public UserInfo {
validateNull(userId);
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.emptyList();
return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import com.swyp8team2.auth.application.AuthService;
import com.swyp8team2.auth.application.jwt.TokenPair;
import com.swyp8team2.auth.presentation.dto.GuestTokenResponse;
import com.swyp8team2.auth.presentation.dto.OAuthSignInRequest;
import com.swyp8team2.auth.presentation.dto.TokenResponse;
import com.swyp8team2.common.exception.BadRequestException;
Expand Down Expand Up @@ -53,4 +54,10 @@ public ResponseEntity<TokenResponse> reissue(
response.addCookie(cookie);
return ResponseEntity.ok(new TokenResponse(tokenPair.accessToken()));
}

@PostMapping("/guest/token")
public ResponseEntity<GuestTokenResponse> guestLogin() {
String guestToken = authService.guestLogin();
return ResponseEntity.ok(new GuestTokenResponse(guestToken));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.swyp8team2.auth.presentation.dto;

public record GuestTokenResponse(String guestToken) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.swyp8team2.auth.presentation.filter;

import com.swyp8team2.auth.domain.UserInfo;
import com.swyp8team2.common.exception.ApplicationException;
import com.swyp8team2.common.exception.BadRequestException;
import com.swyp8team2.common.exception.ErrorCode;
import com.swyp8team2.common.presentation.CustomHeader;
import com.swyp8team2.crypto.application.CryptoService;
import com.swyp8team2.user.domain.Role;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Objects;

import static com.swyp8team2.auth.presentation.filter.JwtAuthenticationEntryPoint.EXCEPTION_KEY;

@Slf4j
@RequiredArgsConstructor
public class GuestAuthFilter extends OncePerRequestFilter {

private final CryptoService cryptoService;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
AntPathMatcher matcher = new AntPathMatcher();
if (!matcher.match("/posts/{postId}/votes/guest", request.getRequestURI())) {
return;
}
String token = request.getHeader(CustomHeader.GUEST_ID);
if (Objects.isNull(token)) {
throw new BadRequestException(ErrorCode.INVALID_GUEST_HEADER);
}
String guestId = cryptoService.decrypt(token);
Authentication authentication = getAuthentication(Long.parseLong(guestId));
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (ApplicationException e) {
request.setAttribute(EXCEPTION_KEY, e);
} finally {
doFilter(request, response, filterChain);
}
}

private Authentication getAuthentication(long userId) {
UserInfo userInfo = new UserInfo(userId, Role.GUEST);
return new UsernamePasswordAuthenticationToken(userInfo, null, userInfo.getAuthorities());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.swyp8team2.auth.application.jwt.JwtProvider;
import com.swyp8team2.auth.domain.UserInfo;
import com.swyp8team2.common.exception.ApplicationException;
import com.swyp8team2.user.domain.Role;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -44,7 +45,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
}

private Authentication getAuthentication(long userId) {
UserInfo userInfo = new UserInfo(userId);
UserInfo userInfo = new UserInfo(userId, Role.USER);
return new UsernamePasswordAuthenticationToken(userInfo, null, userInfo.getAuthorities());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface CommentRepository extends JpaRepository<Comment, Long> {
SELECT c
FROM Comment c
WHERE c.postId = :postId
AND (:cursor is null or c.id > :cursor)
AND (:cursor is null or c.id < :cursor)
ORDER BY c.createdAt DESC
""")
Slice<Comment> findByPostId(
Expand Down
25 changes: 22 additions & 3 deletions src/main/java/com/swyp8team2/common/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.swyp8team2.common.config;

import com.swyp8team2.auth.application.jwt.JwtProvider;
import com.swyp8team2.auth.presentation.filter.GuestAuthFilter;
import com.swyp8team2.auth.presentation.filter.HeaderTokenExtractor;
import com.swyp8team2.auth.presentation.filter.JwtAuthFilter;
import com.swyp8team2.auth.presentation.filter.JwtAuthenticationEntryPoint;
import com.swyp8team2.crypto.application.CryptoService;
import com.swyp8team2.user.domain.Role;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
Expand Down Expand Up @@ -32,11 +35,14 @@
public class SecurityConfig {

private final HandlerExceptionResolver handlerExceptionResolver;
private final CryptoService cryptoService;

public SecurityConfig(
@Qualifier("handlerExceptionResolver") HandlerExceptionResolver handlerExceptionResolver
@Qualifier("handlerExceptionResolver") HandlerExceptionResolver handlerExceptionResolver,
CryptoService cryptoService
) {
this.handlerExceptionResolver = handlerExceptionResolver;
this.cryptoService = cryptoService;
}

@Bean
Expand Down Expand Up @@ -84,11 +90,17 @@ public SecurityFilterChain securityFilterChain(
.authorizeHttpRequests(authorize ->
authorize
.requestMatchers(getWhiteList(introspect)).permitAll()
.requestMatchers(getGuestTokenRequestList(introspect))
.hasAnyRole(Role.USER.name(), Role.GUEST.name())
.anyRequest().authenticated())

.addFilterBefore(
new JwtAuthFilter(jwtProvider, new HeaderTokenExtractor()),
UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(
new GuestAuthFilter(cryptoService),
JwtAuthFilter.class
)
.exceptionHandling(exception ->
exception.authenticationEntryPoint(
new JwtAuthenticationEntryPoint(handlerExceptionResolver)));
Expand All @@ -99,11 +111,18 @@ public static MvcRequestMatcher[] getWhiteList(HandlerMappingIntrospector intros
MvcRequestMatcher.Builder mvc = new MvcRequestMatcher.Builder(introspect);
return new MvcRequestMatcher[]{
mvc.pattern("/auth/reissue"),
mvc.pattern("/guest"),
mvc.pattern("/auth/guest/token"),
mvc.pattern(HttpMethod.GET, "/posts/{sharedUrl}"),
mvc.pattern(HttpMethod.GET, "/posts/{postId}/comments"),
mvc.pattern("/posts/{postId}/votes/guest/**"),
// mvc.pattern("/posts/{postId}/votes/guest/**"),
mvc.pattern("/auth/oauth2/**")
};
}

public static MvcRequestMatcher[] getGuestTokenRequestList(HandlerMappingIntrospector introspect) {
MvcRequestMatcher.Builder mvc = new MvcRequestMatcher.Builder(introspect);
return new MvcRequestMatcher[]{
mvc.pattern("/posts/{postId}/votes/guest"),
};
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/swyp8team2/common/dev/DataInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.swyp8team2.auth.application.jwt.JwtService;
import com.swyp8team2.auth.application.jwt.TokenPair;
import com.swyp8team2.comment.domain.Comment;
import com.swyp8team2.comment.domain.CommentRepository;
import com.swyp8team2.image.domain.ImageFile;
import com.swyp8team2.image.domain.ImageFileRepository;
import com.swyp8team2.image.presentation.dto.ImageFileDto;
Expand Down Expand Up @@ -33,6 +35,7 @@ public class DataInitializer {
private final PostRepository postRepository;
private final JwtService jwtService;
private final VoteService voteService;
private final CommentRepository commentRepository;

@Transactional
public void init() {
Expand All @@ -52,12 +55,14 @@ public void init() {
ImageFile imageFile2 = imageFileRepository.save(ImageFile.create(new ImageFileDto("202502240006030.png", "https://image.photopic.site/images-dev/202502240006030.png", "https://image.photopic.site/images-dev/resized_202502240006030.png")));
posts.add(postRepository.save(Post.create(user.getId(), "description" + j, List.of(PostImage.create("๋ฝ€๋˜A", imageFile1.getId()), PostImage.create("๋ฝ€๋˜B", imageFile2.getId())), "https://photopic.site/shareurl")));
}

}
for (User user : users) {
for (Post post : posts) {
Random random = new Random();
int num = random.nextInt(2);
voteService.vote(user.getId(), post.getId(), post.getImages().get(num).getId());
commentRepository.save(new Comment(post.getId(), user.getId(), "๋Œ“๊ธ€ ๋‚ด์šฉ" + random.nextInt(100)));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingRequestHeaderException;
Expand Down Expand Up @@ -33,16 +34,21 @@ public ResponseEntity<ErrorResponse> handle(UnauthorizedException e) {
.body(response);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handle(MethodArgumentNotValidException e) {
log.debug("MethodArgumentNotValidException {}", e.getMessage());
@ExceptionHandler({
MethodArgumentNotValidException.class,
HttpMessageNotReadableException.class,
MissingRequestHeaderException.class,
HandlerMethodValidationException.class
})
public ResponseEntity<ErrorResponse> invalidArgument(Exception e) {
log.debug("invalidArgument: {}", e.getMessage());
return ResponseEntity.badRequest()
.body(new ErrorResponse(ErrorCode.INVALID_ARGUMENT));
}

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<Void> handle(HttpRequestMethodNotSupportedException e) {
log.debug("HttpRequestMethodNotSupportedException {}", e.getMessage());
@ExceptionHandler({HttpRequestMethodNotSupportedException.class, MethodArgumentTypeMismatchException.class})
public ResponseEntity<Void> notFound(HttpRequestMethodNotSupportedException e) {
log.debug("notFound: {}", e.getMessage());
return ResponseEntity.notFound().build();
}

Expand All @@ -52,11 +58,6 @@ public ResponseEntity<ErrorResponse> handle(NoResourceFoundException e) {
return ResponseEntity.notFound().build();
}

@ExceptionHandler(HandlerMethodValidationException.class)
public ResponseEntity<ErrorResponse> handle(HandlerMethodValidationException e) {
return ResponseEntity.badRequest()
.body(new ErrorResponse(ErrorCode.INVALID_ARGUMENT));
}

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorResponse> handle(AuthenticationException e) {
Expand All @@ -70,20 +71,6 @@ public ResponseEntity<ErrorResponse> handle(AccessDeniedException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ErrorResponse(ErrorCode.INVALID_TOKEN));
}

@ExceptionHandler(MissingRequestHeaderException.class)
public ResponseEntity<ErrorResponse> handle(MissingRequestHeaderException e) {
log.debug("MissingRequestHeaderException {}", e.getMessage());
return ResponseEntity.badRequest()
.body(new ErrorResponse(ErrorCode.INVALID_ARGUMENT));
}

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponse> handle(MethodArgumentTypeMismatchException e) {
log.debug("MethodArgumentTypeMismatchException {}", e.getMessage());
return ResponseEntity.badRequest()
.body(new ErrorResponse(ErrorCode.INVALID_ARGUMENT));
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handle(Exception e) {
log.error("Exception", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum ErrorCode {
INVALID_POST_IMAGE_COUNT("๊ฒŒ์‹œ๊ธ€ ์ด๋ฏธ์ง€ ๊ฐœ์ˆ˜ ์˜ค๋ฅ˜"),
NOT_POST_AUTHOR("๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ์ž๊ฐ€ ์•„๋‹˜"),
POST_ALREADY_CLOSED("์ด๋ฏธ ๋งˆ๊ฐ๋œ ๊ฒŒ์‹œ๊ธ€"),
INVALID_GUEST_HEADER("์ž˜๋ชป๋œ ๊ฒŒ์ŠคํŠธ ํ† ํฐ ํ—ค๋”"),

//401
EXPIRED_TOKEN("ํ† ํฐ ๋งŒ๋ฃŒ"),
Expand Down
Loading