Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
434e647
Merge branch 'main' of https://github.com/MINI-Tech-Post/Tech-Post_BEโ€ฆ
k0081915 Dec 11, 2025
f0721b5
Merge branch 'main' of https://github.com/MINI-Tech-Post/Tech-Post_BEโ€ฆ
k0081915 Dec 11, 2025
5626d28
Merge branch 'main' of https://github.com/MINI-Tech-Post/Tech-Post_BEโ€ฆ
k0081915 Dec 12, 2025
6083e2c
[Refactor] ์—์™ธ ์ฒ˜๋ฆฌ ์ˆ˜์ • #50
bon0512 Dec 12, 2025
24e0d57
[Refactor] ์—์™ธ ์ฒ˜๋ฆฌ ์ˆ˜์ • #50
bon0512 Dec 12, 2025
57fef18
[Refactor] ํšŒ์› ๋„๋ฉ”์ธ ๋ฆฌํŒฉํ† ๋ง
k0081915 Dec 12, 2025
09f5f28
Merge pull request #51 from MINI-Tech-Post/fix/auth-exception
bon0512 Dec 12, 2025
b61a175
Merge branch 'main' of https://github.com/MINI-Tech-Post/Tech-Post_BEโ€ฆ
k0081915 Dec 12, 2025
82da65c
Merge pull request #53 from MINI-Tech-Post/feat/auth-refactoring
bon0512 Dec 12, 2025
9e3d1bf
[Fix] ์ด๋ฏธ ์ข‹์•„์š” ๋ˆ„๋ฅธ ์ƒํƒœ ์ €์žฅ์„ ์œ„ํ•œ Post ๋„๋ฉ”์ธ ์ˆ˜์ •
Jsnooopy Dec 12, 2025
6fa864e
Merge branch 'main' of https://github.com/MINI-Tech-Post/Tech-Post_BEโ€ฆ
Jsnooopy Dec 12, 2025
a7692f1
[Fix] ์ด๋ฏธ ์ข‹์•„์š” ๋ˆ„๋ฅธ ์ƒํƒœ ์ €์žฅ์„ ์œ„ํ•œ Post ๋„๋ฉ”์ธ ์ˆ˜์ •
Jsnooopy Dec 12, 2025
b44ec92
[Feat] ์ฑ„ํŒ…๋ฐฉ ์ฐธ์—ฌ์ž ์ˆ˜ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๋ฐ ์›น์†Œ์ผ“ ์„ธ์…˜ ๊ด€๋ฆฌ ์ˆ˜์ •
Jsnooopy Dec 12, 2025
b38bfb5
[Feat] ์ฑ„ํŒ…๋ฐฉ ์ฐธ์—ฌ์ž ์ˆ˜ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ๋ฐ ์›น์†Œ์ผ“ ์„ธ์…˜ ๊ด€๋ฆฌ ์ˆ˜์ •
Jsnooopy Dec 12, 2025
592d087
Merge branch 'deploy' into main
phonil Dec 12, 2025
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 @@ -2,7 +2,9 @@

import com.ureka.techpost.domain.auth.dto.LoginDto;
import com.ureka.techpost.domain.auth.dto.SignupDto;
import com.ureka.techpost.domain.auth.entity.TokenDto;
import com.ureka.techpost.domain.auth.service.AuthService;
import com.ureka.techpost.domain.auth.utils.CookieUtil;
import com.ureka.techpost.global.apiPayload.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand Down Expand Up @@ -43,7 +45,20 @@ public ApiResponse<String> signup(
public ApiResponse<?> reissue(
@Parameter(hidden = true) HttpServletRequest request,
@Parameter(hidden = true) HttpServletResponse response) {
return ApiResponse.onSuccess(authService.reissue(request, response));

// Request์—์„œ ํ† ํฐ ์ถ”์ถœ
String authorization = request.getHeader("Authorization");
String accessToken = (authorization != null && authorization.startsWith("Bearer ")) ? authorization.split(" ")[1] : null;
String refreshToken = CookieUtil.getCookieValue(request, "refresh");

// ์„œ๋น„์Šค ํ˜ธ์ถœ
TokenDto tokenDto = authService.reissue(accessToken, refreshToken);

// Response ์„ค์ • (ํ—ค๋” + ์ฟ ํ‚ค)
response.setHeader("Authorization", "Bearer " + tokenDto.getAccessToken());
response.addCookie(CookieUtil.createCookie("refresh", tokenDto.getRefreshToken(), 1209600));

return ApiResponse.onSuccess("์žฌ๋ฐœ๊ธ‰ ์„ฑ๊ณต");
}

@Operation(summary = "๋กœ๊ทธ์ธ", description = "์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธํ•˜์—ฌ Access Token ๋ฐ Refresh Token์„ ๋ฐœ๊ธ‰๋ฐ›์Šต๋‹ˆ๋‹ค.")
Expand All @@ -52,7 +67,13 @@ public ApiResponse<String> login(
@Parameter(description = "๋กœ๊ทธ์ธ ์š”์ฒญ ๋ฐ์ดํ„ฐ (์•„์ด๋””, ๋น„๋ฐ€๋ฒˆํ˜ธ)", required = true)
@Valid @RequestBody LoginDto loginDto,
@Parameter(hidden = true) HttpServletResponse response) {
authService.login(loginDto, response);
// ์„œ๋น„์Šค ํ˜ธ์ถœ
TokenDto tokenDto = authService.login(loginDto);

// Response ์„ค์ •
response.setHeader("Authorization", "Bearer " + tokenDto.getAccessToken());
response.addCookie(CookieUtil.createCookie("refresh", tokenDto.getRefreshToken(), 1209600));

return ApiResponse.onSuccess("๋กœ๊ทธ์ธ ์„ฑ๊ณต");
}

Expand All @@ -61,7 +82,15 @@ public ApiResponse<String> login(
public ResponseEntity<String> logout(
@Parameter(hidden = true) HttpServletRequest request,
@Parameter(hidden = true) HttpServletResponse response) {
authService.logout(request, response);
// ์ฟ ํ‚ค์—์„œ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ์ถ”์ถœ
String refreshToken = CookieUtil.getCookieValue(request, "refresh");

// ์„œ๋น„์Šค ํ˜ธ์ถœ (DB ์‚ญ์ œ)
authService.logout(refreshToken);

// ํด๋ผ์ด์–ธํŠธ ์ฟ ํ‚ค ์‚ญ์ œ (ํ•ญ์ƒ ์ˆ˜ํ–‰)
CookieUtil.deleteCookie(response, "refresh");

return ResponseEntity.ok("๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,6 @@ public Map<String, Object> getAttributes() {

@Override
public String getName() {
return user.getProviderId();
return user.getName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@
@RedisHash(value = "refreshToken", timeToLive = 1209600)
public class RefreshToken {

@Id
private String id; // Redis Key (์ผ๋ฐ˜์ ์œผ๋กœ username์ด๋‚˜ userId ์‚ฌ์šฉ)
@Id
private String tokenValue; // ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๊ฐ’ (Key)

@Indexed
private String tokenValue; // ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๊ฐ’ (์กฐํšŒ์šฉ ์ธ๋ฑ์Šค)

private String username; // ์‚ฌ์šฉ์ž ์‹๋ณ„์ž
@Indexed
private String username; // ์‚ฌ์šฉ์ž ์‹๋ณ„์ž (ํ•„์š” ์‹œ ์ „์ฒด ๋กœ๊ทธ์•„์›ƒ์„ ์œ„ํ•ด ์ธ๋ฑ์Šค ์„ค์ •)

public void updateToken(String tokenValue) {
this.tokenValue = tokenValue;
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/ureka/techpost/domain/auth/entity/TokenDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ureka.techpost.domain.auth.entity;

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

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TokenDto {
private String accessToken;
private String refreshToken;
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*/

@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
public class CustomOAuth2FailureHandler implements AuthenticationFailureHandler {

private final ObjectMapper objectMapper = new ObjectMapper();

Expand All @@ -31,14 +31,20 @@ public void onAuthenticationFailure(
AuthenticationException exception
) throws IOException {

// ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€ ์ถ”์ถœ (OAuth2AuthenticationException ๋˜๋Š” ๊ธฐํƒ€ AuthenticationException)
String errorMessage = exception.getMessage() != null
? exception.getMessage()
: "์†Œ์…œ ๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.";

ErrorResponse errorResponse = ErrorResponse.builder()
.status(HttpStatus.UNAUTHORIZED)
.code("LOGIN_FAILED")
.message("๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.")
.code("OAUTH2_LOGIN_FAILED")
.message(errorMessage)
.build();

response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json;charset=UTF-8");

objectMapper.writeValue(response.getWriter(), errorResponse);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.ureka.techpost.domain.auth.dto.CustomUserDetails;
import com.ureka.techpost.domain.auth.jwt.JwtUtil;
import com.ureka.techpost.domain.auth.service.TokenService;
import com.ureka.techpost.domain.auth.utils.CookieUtil;
import com.ureka.techpost.domain.user.entity.User;
import com.ureka.techpost.domain.user.repository.UserRepository;
import jakarta.servlet.ServletException;
Expand Down Expand Up @@ -45,7 +46,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
String refresh = jwtUtil.generateRefreshToken("refresh");

tokenService.addRefreshToken(oAuth2User.getUser(), refresh);
response.addCookie(tokenService.createCookie("refresh", refresh));
response.addCookie(CookieUtil.createCookie("refresh", refresh, 1209600));

// ์•ก์„ธ์Šค ํ† ํฐ์„ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋‹ด์•„ ํ”„๋ก ํŠธ์—”๋“œ URL๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
// vue.js ์—์„œ ์ง€์›ํ•˜๋Š” ํฌํŠธ ๋ฒˆํ˜ธ๋กœ ๋ณ€๊ฒฝํ•ด์•ผ ํ•จ
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.ureka.techpost.domain.user.entity.User;
import com.ureka.techpost.domain.user.repository.UserRepository;
import com.ureka.techpost.domain.auth.service.TokenService;
import com.ureka.techpost.global.exception.CustomException;
import com.ureka.techpost.global.exception.ErrorCode;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -31,87 +33,78 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtUtil jwtUtil;
private final UserRepository userRepository;
private final TokenService tokenService;

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String uri = request.getRequestURI();

// Swagger UI & ๋ฌธ์„œ ๊ฒฝ๋กœ
if (uri.startsWith("/swagger-ui") ||
uri.startsWith("/v3/api-docs") ||
uri.startsWith("/swagger-resources") ||
uri.startsWith("/webjars")) {
return true;
}

// Auth ๊ด€๋ จ public ๊ฒฝ๋กœ
if (uri.equals("/") ||
uri.startsWith("/health") ||
uri.startsWith("/actuator") ||
uri.equals("/api/auth/reissue") ||
uri.equals("/api/auth/signup") ||
uri.equals("/api/auth/login")) {
return true;
}

return false;
String requestURI = request.getRequestURI();
// reissue ์š”์ฒญ์€ ํ—ค๋”์— access ํ† ํฐ์ด ์•„๋‹Œ refresh ํ† ํฐ์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์—,
// JwtAuthenticationFilter์˜ ๊ฒ€์ฆ ๋กœ์ง์„ ๊ฑด๋„ˆ๋›ฐ์–ด์•ผ ํ•จ
return requestURI.equals("/api/auth/reissue");
}



@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("[JwtAuthFilter] doFilterInternal");

// ์š”์ฒญ ํ—ค๋”์—์„œ Authorization ํ‚ค์˜ ๊ฐ’(ํ† ํฐ) ์ถ”์ถœ

// ํ† ํฐ ์ถ”์ถœ
String accessToken = resolveToken(request);

// ํ† ํฐ์ด ์—†์œผ๋ฉด ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์ง„ํ–‰
if (accessToken == null) {
filterChain.doFilter(request, response);
return;
}

// ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ (์‹คํŒจ ์‹œ ์ฆ‰์‹œ ์ข…๋ฃŒ)
validateToken(response, accessToken);

// ์ธ์ฆ ์ฒ˜๋ฆฌ
authenticateUser(accessToken);

// ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์ง„ํ–‰
filterChain.doFilter(request, response);
}

private String resolveToken(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.startsWith("Bearer ")) {
return authorization.split(" ")[1];
}
return null;
}

private void validateToken(HttpServletResponse response, String accessToken) throws IOException {
// ํ† ํฐ ๋งŒ๋ฃŒ ์—ฌ๋ถ€ ํ™•์ธ
if (jwtUtil.isExpired(accessToken)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
throw new CustomException(ErrorCode.ACCESS_TOKEN_MISSING);
}

// ํ† ํฐ ์นดํ…Œ๊ณ ๋ฆฌ ํ™•์ธ
String category = jwtUtil.getCategory(accessToken);
if (!"access".equals(category)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
throw new CustomException(ErrorCode.INVALID_TOKEN_CATEGORY);
}
}

private void authenticateUser(String accessToken) {
// ํ† ํฐ์—์„œ username ์ถ”์ถœ
String username = jwtUtil.getUsernameFromToken(accessToken);

// DB์—์„œ ์‚ฌ์šฉ์ž ์กฐํšŒ
User foundUser = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("ํšŒ์›์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."));

// UserDetails ๊ฐ์ฒด ์ƒ์„ฑ
CustomUserDetails customUserDetails = new CustomUserDetails(foundUser);

// ์ธ์ฆ ํ† ํฐ ์ƒ์„ฑ
Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());

// SecurityContext์— ์„ค์ •
SecurityContextHolder.getContext().setAuthentication(authToken);

// ํ† ํฐ์ด ์—†๊ฑฐ๋‚˜, Bearer ํƒ€์ž…์ด ์•„๋‹ˆ๋ฉด ํ•„ํ„ฐ ํ†ต๊ณผ (์ธ์ฆ ์‹คํŒจ ์ฒ˜๋ฆฌ๋จ)
if (authorization == null || !authorization.startsWith("Bearer ")) {
log.warn("JWT ํ† ํฐ ์—†์Œ");
filterChain.doFilter(request, response);
return;
}

// "Bearer " ์ ‘๋‘์‚ฌ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ์ˆœ์ˆ˜ ํ† ํฐ ๊ฐ’๋งŒ ์ถ”์ถœ
String accessToken = authorization.split(" ")[1];

// ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ (๋งŒ๋ฃŒ ์—ฌ๋ถ€, ์œ„์กฐ ์—ฌ๋ถ€ ๋“ฑ ํ™•์ธ)
// ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ GlobalExceptionHandler๊ฐ€ ์ฒ˜๋ฆฌ
tokenService.validateAccessToken(accessToken);

// ํ† ํฐ์—์„œ ์‚ฌ์šฉ์ž ์ด๋ฆ„(username) ์ถ”์ถœ
String username = jwtUtil.getUsernameFromToken(accessToken);

// ์ถ”์ถœํ•œ username์œผ๋กœ DB์—์„œ ์‹ค์ œ ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ
// (ํ† ํฐ์—๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ์—†์œผ๋ฏ€๋กœ DB ์กฐํšŒ๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ)
User foundUser = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("ํšŒ์›์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."));

// ์ธ์ฆ ๊ฐ์ฒด(Authentication) ์ƒ์„ฑ์„ ์œ„ํ•œ ์ž„์‹œ User ๊ฐ์ฒด ์ƒ์„ฑ
// ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ด๋ฏธ ํ† ํฐ ๊ฒ€์ฆ์„ ํ†ต๊ณผํ–ˆ์œผ๋ฏ€๋กœ ์ž„์˜์˜ ๊ฐ’์œผ๋กœ ์„ค์ •
User user = User.builder()
.userId(foundUser.getUserId())
.username(username)
.password("temppassword")
.name(foundUser.getName())
.role(foundUser.getRole())
.provider("NONE")
.providerId(null)
.build();

// UserDetails ๊ฐ์ฒด ์ƒ์„ฑ (Spring Security๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ์ฒด)
CustomUserDetails customUserDetails = new CustomUserDetails(user);

// ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์ธ์ฆ ํ† ํฐ ์ƒ์„ฑ
Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());

// ์„ธ์…˜(Security Context)์— ์ธ์ฆ ์ •๋ณด ๋“ฑ๋ก
// ์ด ์š”์ฒญ์ด ๋๋‚  ๋•Œ๊นŒ์ง€๋งŒ ์ธ์ฆ๋œ ์ƒํƒœ๋กœ ์œ ์ง€๋จ (Stateless)
SecurityContextHolder.getContext().setAuthentication(authToken);

// ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์ง„ํ–‰
filterChain.doFilter(request, response);
}
}
Loading