Skip to content

Commit 8ea594a

Browse files
authored
Merge pull request #27 from MT-TEAM-Org/feat/PH-97
Feat/ph 97
2 parents ecfe047 + bdea8c2 commit 8ea594a

File tree

13 files changed

+107
-97
lines changed

13 files changed

+107
-97
lines changed

src/main/java/org/myteam/server/board/controller/CategoryController.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@ public class CategoryController {
2323

2424
// CREATE: 카테고리 생성
2525
@PostMapping
26-
@PreAuthorize("hasAuthority(T(org.myteam.server.member.domain.MemberRole).ADMIN.name())")
2726
public ResponseEntity<ResponseDto<CategoryResponse>> createCategory(@Valid @RequestBody CategorySaveRequest categorySaveRequest) {
2827
CategoryResponse response = categoryService.create(categorySaveRequest);
2928
return ResponseEntity.ok(new ResponseDto<>(SUCCESS.name(), "카테고리 생성 성공", response));
3029
}
3130

3231
// UPDATE: 카테고리 수정
3332
@PutMapping("/{id}")
34-
@PreAuthorize("hasAuthority(T(org.myteam.server.member.domain.MemberRole).ADMIN.name())")
3533
public ResponseEntity<ResponseDto<CategoryResponse>> updateCategory(@PathVariable Long id,
3634
@Valid @RequestBody CategoryUpdateRequest categoryUpdateRequest) {
3735
CategoryResponse response = categoryService.update(id, categoryUpdateRequest);
@@ -40,7 +38,6 @@ public ResponseEntity<ResponseDto<CategoryResponse>> updateCategory(@PathVariabl
4038

4139
// DELETE: 카테고리 삭제
4240
@DeleteMapping("/{id}")
43-
@PreAuthorize("hasAuthority(T(org.myteam.server.member.domain.MemberRole).ADMIN.name())")
4441
public ResponseEntity<ResponseDto<Void>> deleteCategory(@PathVariable Long id) {
4542
categoryService.delete(id);
4643
return ResponseEntity.ok(new ResponseDto<>(SUCCESS.name(), "카테고리 삭제 성공", null));

src/main/java/org/myteam/server/global/security/config/SecurityConfig.java

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.springframework.beans.factory.annotation.Value;
1818
import org.springframework.context.annotation.Bean;
1919
import org.springframework.context.annotation.Configuration;
20+
import org.springframework.http.HttpMethod;
2021
import org.springframework.security.authentication.AuthenticationManager;
2122
import org.springframework.security.authentication.ProviderManager;
2223
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
@@ -97,14 +98,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
9798
);
9899

99100
http
100-
.addFilterBefore(
101+
.addFilterAt(
102+
new JwtAuthenticationFilter(authenticationManager(), jwtProvider, refreshJpaRepository),
103+
UsernamePasswordAuthenticationFilter.class
104+
) // 로그인 인증 필터
105+
.addFilterAfter(
101106
new TokenAuthenticationFilter(jwtProvider),
102107
JwtAuthenticationFilter.class
103-
)
104-
.addFilterAfter(
105-
new JwtAuthenticationFilter(authenticationManager(), jwtProvider, refreshJpaRepository),
106-
TokenAuthenticationFilter.class
107-
); // 회원 로그인 필터
108+
); // JWT 토큰 검증 필터
108109

109110
// cors 설정
110111
http
@@ -113,13 +114,24 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
113114
// 경로별 인가 작업
114115
http
115116
.authorizeHttpRequests(authorizeRequests ->
116-
authorizeRequests
117-
.requestMatchers("/h2-console").permitAll() // H2 콘솔 접근 허용
118-
.requestMatchers(TOKEN_REISSUE_PATH).permitAll() // 토큰 재발급
119-
.requestMatchers("/api/admin/**").hasAnyRole(MemberRole.ADMIN.name())
120-
.requestMatchers("/api/members/role").permitAll() // 유저 권한 변경 허용
121-
.requestMatchers("/api/members/get-token/{email}").permitAll() // 테스트용 토큰 발급용
122-
.anyRequest().permitAll() // 나머지 요청은 모두 허용
117+
authorizeRequests
118+
.requestMatchers("/upload/**").permitAll() // 정적 자원 접근 허용
119+
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**").permitAll()
120+
121+
.requestMatchers("/h2-console").permitAll() // H2 콘솔 접근 허용
122+
.requestMatchers("/api/members/get-token/**").permitAll() // 테스트용 토큰 발급용
123+
124+
.requestMatchers("/api/admin/**").hasAnyAuthority(MemberRole.ADMIN.name())
125+
.requestMatchers(HttpMethod.POST, "/api/me/create").permitAll()
126+
.requestMatchers(HttpMethod.GET, "/api/categories/**").permitAll()
127+
.requestMatchers(HttpMethod.PUT, "/api/categories/**").hasAnyAuthority(MemberRole.ADMIN.name())
128+
.requestMatchers(HttpMethod.DELETE, "/api/categories/**").hasAnyAuthority(MemberRole.ADMIN.name())
129+
.requestMatchers(HttpMethod.POST, "/api/categories").hasAnyAuthority(MemberRole.ADMIN.name())
130+
131+
.requestMatchers(TOKEN_REISSUE_PATH).permitAll() // 토큰 재발급
132+
.requestMatchers("/api/members/role").permitAll() // 유저 권한 변경 허용
133+
134+
.anyRequest().authenticated() // 나머지 요청은 모두 허용
123135
);
124136

125137
http

src/main/java/org/myteam/server/global/security/filter/JwtAuthenticationFilter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR
8585

8686
if (status.equals(PENDING.name())) {
8787
log.warn("PENDING 상태인 경우 로그인이 불가능합니다");
88-
sendErrorResponse(response, HttpStatus.FORBIDDEN, "PENDING 상태인 경우 로그인이 불가능합니다");
88+
sendErrorResponse(response, HttpStatus.LOCKED, "PENDING 상태인 경우 로그인이 불가능합니다");
8989
return;
9090
} else if (status.equals(INACTIVE.name())) {
9191
log.warn("INACTIVE 상태인 경우 로그인이 불가능합니다");
@@ -113,6 +113,7 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR
113113

114114
log.debug("print accessToken: {}", accessToken);
115115
log.debug("print refreshToken: {}", refreshToken);
116+
log.debug("print role: {}", role);
116117

117118
//Refresh 토큰 저장
118119
addRefreshEntity(publicId, refreshToken, Duration.ofHours(24));
@@ -122,7 +123,6 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR
122123
response.addCookie(createCookie(REFRESH_TOKEN_KEY, cookieValue, LOGOUT_PATH, 24 * 60 * 60, true));
123124
response.setStatus(HttpStatus.OK.value());
124125

125-
126126
// frontUrl += "?" + ACCESS_TOKEN_KEY + "=" + ("Bearer%20" + accessToken);
127127
// frontUrl += "&" + REFRESH_TOKEN_KEY + "=" + ("Bearer%20" + refreshToken);
128128
// response.sendRedirect(frontUrl);

src/main/java/org/myteam/server/global/security/filter/TokenAuthenticationFilter.java

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,26 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
3434
@Override
3535
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
3636
throws ServletException, IOException {
37-
log.info("TokenAuthenticationFilter 토큰을 검사중");
37+
log.info("Token Authenticate Filter 토큰을 검사중");
3838
String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION);
3939
String accessToken = jwtProvider.getAccessToken(authorizationHeader);
4040

4141
log.info("accessToken : " + accessToken);
4242
if (StringUtils.isNotBlank(accessToken)) {
43-
if (jwtProvider.validToken(accessToken)) {
43+
try {
44+
if (!jwtProvider.validToken(accessToken)) {
45+
log.warn("인증되지 않은 토큰입니다");
46+
filterChain.doFilter(request, response);
47+
return;
48+
}
4449

4550
String accessCategory = jwtProvider.getCategory(accessToken);
46-
47-
if (!accessCategory.equals(TOKEN_CATEGORY_ACCESS)) {
48-
// RestControllerAdvice 로 에러가 전달 되지 않아 여기서 에러 처리함
49-
log.warn("잘못된 토큰 유형입니다.");
50-
sendErrorResponse(response, INVALID_TOKEN_TYPE.getStatus(), "잘못된 토큰 유형");
51+
if (!TOKEN_CATEGORY_ACCESS.equals(accessCategory)) {
52+
log.warn("잘못된 토큰 유형입니다");
53+
filterChain.doFilter(request, response);
5154
return;
5255
}
5356

54-
// 토큰에서 username과 role 획득
5557
UUID publicId = jwtProvider.getPublicId(accessToken);
5658
String role = jwtProvider.getRole(accessToken);
5759
String status = jwtProvider.getStatus(accessToken);
@@ -60,53 +62,23 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
6062
log.info("role : " + role);
6163
log.info("status : " + status);
6264

63-
// Member 를 생성하여 값 set
6465
Member member = Member.builder()
6566
.publicId(publicId)
6667
.role(MemberRole.valueOf(role))
6768
.status(MemberStatus.valueOf(status))
6869
.build();
6970

7071
CustomUserDetails customUserDetails = new CustomUserDetails(member);
71-
7272
Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
73+
7374
SecurityContextHolder.getContext().setAuthentication(authToken);
74-
log.info("security Context 에 정보 저장이 완료되었습니다.");
75-
} else {
76-
try {
77-
if (jwtProvider.isExpired(accessToken)) {
78-
// RestControllerAdvice 로 에러가 전달 되지 않아 여기서 에러 처리함
79-
log.warn("토큰이 만료되었습니다.");
80-
sendErrorResponse(response, ACCESS_TOKEN_EXPIRED.getStatus(), "만료된 토큰");
81-
return;
82-
}
83-
} catch (JwtException | IllegalArgumentException e) {
84-
// RestControllerAdvice 로 에러가 전달 되지 않아 여기서 에러 처리함
85-
log.error("잘못된 JWT 토큰 형식 또는 그 외 에러 : {}", e.getMessage());
86-
sendErrorResponse(response, INVALID_ACCESS_TOKEN.getStatus(), "잘못된 JWT 토큰 형식 또는 그 외 에러");
87-
return;
88-
}
89-
// RestControllerAdvice 로 에러가 전달 되지 않아 여기서 에러 처리함
90-
log.warn("인증되지 않은 토큰입니다.");
91-
sendErrorResponse(response, INVALID_ACCESS_TOKEN.getStatus(), "인증되지 않은 토큰");
75+
log.info("SecurityContext 에 인증 정보 저장 완료");
76+
} catch (JwtException e) {
77+
log.error("JWT 처리 중 오류 발생: {}", e.getMessage());
78+
filterChain.doFilter(request, response);
9279
return;
9380
}
9481
}
9582
filterChain.doFilter(request, response);
9683
}
97-
98-
/**
99-
* 공통 에러 응답 처리 메서드
100-
*
101-
* @param response HttpServletResponse
102-
* @param httpStatus HTTP 상태 오브젝트
103-
* @param message 메시지
104-
* @throws IOException
105-
*/
106-
private void sendErrorResponse(HttpServletResponse response, HttpStatus httpStatus, String message) throws IOException {
107-
response.setStatus(httpStatus.value());
108-
response.setContentType("application/json");
109-
response.setCharacterEncoding("UTF-8");
110-
response.getWriter().write(String.format("{\"message\":\"%s\",\"status\":\"%s\"}", message, httpStatus.name()));
111-
}
11284
}

src/main/java/org/myteam/server/member/controller/MemberController.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,9 @@ public ResponseEntity<?> updateStatus(@RequestBody @Valid MemberStatusUpdateRequ
6666
log.info("email : {}" , response.getEmail());
6767

6868
// 서비스 호출
69-
MemberStatus memberStatus = memberStatusUpdateRequest.getStatus(); // 변경 상태
70-
String targetEmail = response.getEmail(); // 상태를 변경할 대상 이메일
71-
String extractedEmail = memberStatusUpdateRequest.getEmail(); // 토큰에서 추출된 이메일
69+
String targetEmail = response.getEmail(); // 변경을 시도하는 유저의 이메일 (본인 또는 관리자)
7270

73-
memberService.updateStatus(extractedEmail, targetEmail, memberStatus);
71+
memberService.updateStatus(targetEmail, memberStatusUpdateRequest);
7472

7573
return ResponseEntity.ok(new ResponseDto<>(SUCCESS.name(), "회원 상태가 성공적으로 변경되었습니다.", null));
7674
}
@@ -82,11 +80,19 @@ public ResponseEntity<?> updateRole(@RequestBody @Valid MemberRoleUpdateRequest
8280
return new ResponseEntity<>(new ResponseDto<>(SUCCESS.name(), "권한 변경 성공", response), HttpStatus.OK);
8381
}
8482

85-
@GetMapping("/get-token/{email}")
83+
@GetMapping("/get-token/admin/{email}")
84+
public ResponseEntity<?> getAdminToken(@PathVariable String email) {
85+
log.info("getToken 메서드가 실행되었습니다.");
86+
MemberResponse response = memberService.getByEmail(email);
87+
String encode = TOKEN_PREFIX + jwtProvider.generateToken(TOKEN_CATEGORY_ACCESS, Duration.ofHours(10), response.getPublicId(), MemberRole.ADMIN.name(), response.getStatus().name());
88+
return new ResponseEntity<>(new ResponseDto<>(SUCCESS.name(), "토큰 조회 성공", encode), HttpStatus.OK);
89+
}
90+
91+
@GetMapping("/get-token/user/{email}")
8692
public ResponseEntity<?> getToken(@PathVariable String email) {
8793
log.info("getToken 메서드가 실행되었습니다.");
8894
MemberResponse response = memberService.getByEmail(email);
89-
String encode = TOKEN_PREFIX + jwtProvider.generateToken(TOKEN_CATEGORY_ACCESS, Duration.ofHours(6), response.getPublicId(), MemberRole.USER.name(), response.getStatus().name());
95+
String encode = TOKEN_PREFIX + jwtProvider.generateToken(TOKEN_CATEGORY_ACCESS, Duration.ofHours(10), response.getPublicId(), MemberRole.USER.name(), response.getStatus().name());
9096
return new ResponseEntity<>(new ResponseDto<>(SUCCESS.name(), "토큰 조회 성공", encode), HttpStatus.OK);
9197
}
9298
}

src/main/java/org/myteam/server/member/domain/GenderType.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
import lombok.AllArgsConstructor;
44
import lombok.Getter;
5-
import org.myteam.server.global.exception.PlayHiveException;
6-
7-
import static org.myteam.server.global.exception.ErrorCode.INVALID_PARAMETER;
85

96
@AllArgsConstructor
107
@Getter
@@ -18,6 +15,6 @@ public static GenderType fromValue(String value) {
1815
return gender;
1916
}
2017
}
21-
throw new PlayHiveException(INVALID_PARAMETER, "Invalid gender value: " + value);
18+
return null;
2219
}
2320
}

src/main/java/org/myteam/server/member/dto/MemberDeleteRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ public class MemberDeleteRequest {
1414

1515
@NotBlank
1616
// @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문+숫자 조합 4 ~ 10자 이내로 입력해주세요")
17-
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 10자 이내로 입력해주세요")
17+
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{4,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 15자 이내로 입력해주세요")
1818
private String password; // 비밀번호
1919
}

src/main/java/org/myteam/server/member/dto/MemberSaveRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class MemberSaveRequest {
1717

1818
@NotBlank
1919
// @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문+숫자 조합 4 ~ 10자 이내로 입력해주세요")
20-
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 10자 이내로 입력해주세요")
20+
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{4,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 15자 이내로 입력해주세요")
2121
private String password; // 비밀번호
2222

2323
@NotBlank

src/main/java/org/myteam/server/member/dto/MemberUpdateRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class MemberUpdateRequest {
3030
@Column(name = "birth_date")
3131
private LocalDate birthdate;
3232

33-
@Pattern(regexp = "^(MALE|FEMALE)$", message = "성별은 MALE, FEMALE 중 하나여야 합니다.")
33+
@Pattern(regexp = "^(?i)(MALE|FEMALE)$", message = "성별은 MALE, FEMALE 중 하나여야 합니다.")
3434
private String gender;
3535

3636
private MemberStatus status;

src/main/java/org/myteam/server/member/dto/PasswordChangeRequest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
@NoArgsConstructor
99
public class PasswordChangeRequest {
1010
@NotNull
11-
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 10자 이내로 입력해주세요")
11+
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{4,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 15자 이내로 입력해주세요")
1212
private String password;
1313

1414
@NotNull
15-
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 10자 이내로 입력해주세요")
15+
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{4,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 15자 이내로 입력해주세요")
1616
private String newPassword;
1717

1818
@NotNull
19-
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 10자 이내로 입력해주세요")
19+
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{4,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 15자 이내로 입력해주세요")
2020
private String confirmPassword;
2121

2222
@Builder

0 commit comments

Comments
 (0)