From 56477f6744cc1674a0d0134a78343170676ba8aa Mon Sep 17 00:00:00 2001 From: leejongwook2 <34103253+leejongwook2@users.noreply.github.com> Date: Thu, 26 Dec 2024 07:25:40 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EB=8B=A4=EB=A5=B8=EC=82=AC=EB=9E=8C=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/config/SecurityConfig.java | 6 +++--- .../member/controller/MemberController.java | 4 +--- .../server/member/service/MemberService.java | 19 ++++++++++--------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/myteam/server/global/security/config/SecurityConfig.java b/src/main/java/org/myteam/server/global/security/config/SecurityConfig.java index 3fe19066..04e0ed96 100644 --- a/src/main/java/org/myteam/server/global/security/config/SecurityConfig.java +++ b/src/main/java/org/myteam/server/global/security/config/SecurityConfig.java @@ -114,12 +114,12 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http .authorizeHttpRequests(authorizeRequests -> authorizeRequests - .requestMatchers("/h2-console").permitAll() // H2 콘솔 접근 허용 .requestMatchers(TOKEN_REISSUE_PATH).permitAll() // 토큰 재발급 - .requestMatchers("/api/admin/**").hasAnyRole(MemberRole.ADMIN.name()) + .requestMatchers("/h2-console").permitAll() // H2 콘솔 접근 허용 .requestMatchers("/api/members/role").permitAll() // 유저 권한 변경 허용 .requestMatchers("/api/members/get-token/{email}").permitAll() // 테스트용 토큰 발급용 - .anyRequest().permitAll() // 나머지 요청은 모두 허용 + .requestMatchers("/api/admin/**").hasAnyRole(MemberRole.ADMIN.name()) + .anyRequest().permitAll() // 나머지 요청은 모두 허용 ); http diff --git a/src/main/java/org/myteam/server/member/controller/MemberController.java b/src/main/java/org/myteam/server/member/controller/MemberController.java index 442ea2a9..75d9a934 100644 --- a/src/main/java/org/myteam/server/member/controller/MemberController.java +++ b/src/main/java/org/myteam/server/member/controller/MemberController.java @@ -66,11 +66,9 @@ public ResponseEntity updateStatus(@RequestBody @Valid MemberStatusUpdateRequ log.info("email : {}" , response.getEmail()); // 서비스 호출 - MemberStatus memberStatus = memberStatusUpdateRequest.getStatus(); // 변경 상태 String targetEmail = response.getEmail(); // 상태를 변경할 대상 이메일 - String extractedEmail = memberStatusUpdateRequest.getEmail(); // 토큰에서 추출된 이메일 - memberService.updateStatus(extractedEmail, targetEmail, memberStatus); + memberService.updateStatus(targetEmail, memberStatusUpdateRequest); return ResponseEntity.ok(new ResponseDto<>(SUCCESS.name(), "회원 상태가 성공적으로 변경되었습니다.", null)); } diff --git a/src/main/java/org/myteam/server/member/service/MemberService.java b/src/main/java/org/myteam/server/member/service/MemberService.java index 46fd5abf..3d5c1d53 100644 --- a/src/main/java/org/myteam/server/member/service/MemberService.java +++ b/src/main/java/org/myteam/server/member/service/MemberService.java @@ -5,12 +5,10 @@ import org.myteam.server.global.exception.ErrorCode; import org.myteam.server.global.exception.PlayHiveException; import org.myteam.server.global.security.jwt.JwtProvider; +import org.myteam.server.member.domain.MemberRole; import org.myteam.server.member.domain.MemberStatus; -import org.myteam.server.member.dto.MemberSaveRequest; +import org.myteam.server.member.dto.*; import org.myteam.server.member.controller.response.MemberResponse; -import org.myteam.server.member.dto.MemberRoleUpdateRequest; -import org.myteam.server.member.dto.MemberUpdateRequest; -import org.myteam.server.member.dto.PasswordChangeRequest; import org.myteam.server.member.entity.Member; import org.myteam.server.member.repository.MemberJpaRepository; import org.myteam.server.member.repository.MemberRepository; @@ -169,18 +167,21 @@ public void changePassword(String email, PasswordChangeRequest passwordChangeReq } @Transactional - public void updateStatus(String extractedEmail, String targetEmail, MemberStatus memberStatus) { - log.info("토큰에서 추출된 이메일: {}, 상태를 변경할 대상 이메일: {}, 새로운 상태: {}", extractedEmail, targetEmail, memberStatus); + public void updateStatus(String targetEmail, MemberStatusUpdateRequest memberStatusUpdateRequest) { + log.info("토큰에서 추출된 이메일: {}, 상태를 변경할 대상 이메일: {}, 새로운 상태: {}", targetEmail, memberStatusUpdateRequest.getStatus().name()); Member member = memberRepository.getByEmail(targetEmail); // 자신의 계정이 아닌 다른 계정을 수정하려고 함 - if (!member.verifyOwnEmail(extractedEmail)) { - throw new PlayHiveException(NO_PERMISSION); + if (!member.verifyOwnEmail(memberStatusUpdateRequest.getEmail())) { + if (!member.getRole().equals(MemberRole.ADMIN)) { // 관리자가 상태를 업데이트 하려고 하는 것일 수도 있는 상황 + // 빈 Response 객체 반환 + throw new PlayHiveException(NO_PERMISSION, "상태 수정 권한이 없습니다."); + } } // 상태 업데이트 - member.updateStatus(memberStatus); + member.updateStatus(memberStatusUpdateRequest.getStatus()); } public boolean existsByEmail(String email) { From 68920c99379fb0faf9c2ca5138b8a80f4bb03cd0 Mon Sep 17 00:00:00 2001 From: leejongwook2 <34103253+leejongwook2@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:36:40 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=97=90=EB=9F=AC=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EC=BB=A4=EB=B0=8B=201.=20=EC=A0=95?= =?UTF-8?q?=EC=A0=81=20=EC=9E=90=EC=9B=90=20=EA=B2=BD=EB=A1=9C=20=ED=97=88?= =?UTF-8?q?=EC=9A=A9=202.=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=ED=8E=B8?= =?UTF-8?q?=EC=9D=98=EC=9A=A9=20=ED=86=A0=ED=81=B0=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=83=9D=EC=84=B1=203.=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=ED=8C=A8=EC=8A=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20validation=20=EC=88=98=EC=A0=95=204.=20?= =?UTF-8?q?=EC=84=B1=EB=B3=84=20validation=20=EC=88=98=EC=A0=95=205.=20PEN?= =?UTF-8?q?DING=20=EA=B4=80=EB=A0=A8=20=EC=83=81=ED=83=9C=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20423=20=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/controller/CategoryController.java | 3 - .../security/config/SecurityConfig.java | 38 +++++++----- .../filter/JwtAuthenticationFilter.java | 4 +- .../filter/TokenAuthenticationFilter.java | 58 +++++-------------- .../member/controller/MemberController.java | 12 +++- .../server/member/domain/GenderType.java | 5 +- .../member/dto/MemberDeleteRequest.java | 2 +- .../server/member/dto/MemberSaveRequest.java | 2 +- .../member/dto/MemberUpdateRequest.java | 2 +- .../member/dto/PasswordChangeRequest.java | 6 +- 10 files changed, 59 insertions(+), 73 deletions(-) diff --git a/src/main/java/org/myteam/server/board/controller/CategoryController.java b/src/main/java/org/myteam/server/board/controller/CategoryController.java index c82d8025..2787666e 100644 --- a/src/main/java/org/myteam/server/board/controller/CategoryController.java +++ b/src/main/java/org/myteam/server/board/controller/CategoryController.java @@ -23,7 +23,6 @@ public class CategoryController { // CREATE: 카테고리 생성 @PostMapping - @PreAuthorize("hasAuthority(T(org.myteam.server.member.domain.MemberRole).ADMIN.name())") public ResponseEntity> createCategory(@Valid @RequestBody CategorySaveRequest categorySaveRequest) { CategoryResponse response = categoryService.create(categorySaveRequest); return ResponseEntity.ok(new ResponseDto<>(SUCCESS.name(), "카테고리 생성 성공", response)); @@ -31,7 +30,6 @@ public ResponseEntity> createCategory(@Valid @Requ // UPDATE: 카테고리 수정 @PutMapping("/{id}") - @PreAuthorize("hasAuthority(T(org.myteam.server.member.domain.MemberRole).ADMIN.name())") public ResponseEntity> updateCategory(@PathVariable Long id, @Valid @RequestBody CategoryUpdateRequest categoryUpdateRequest) { CategoryResponse response = categoryService.update(id, categoryUpdateRequest); @@ -40,7 +38,6 @@ public ResponseEntity> updateCategory(@PathVariabl // DELETE: 카테고리 삭제 @DeleteMapping("/{id}") - @PreAuthorize("hasAuthority(T(org.myteam.server.member.domain.MemberRole).ADMIN.name())") public ResponseEntity> deleteCategory(@PathVariable Long id) { categoryService.delete(id); return ResponseEntity.ok(new ResponseDto<>(SUCCESS.name(), "카테고리 삭제 성공", null)); diff --git a/src/main/java/org/myteam/server/global/security/config/SecurityConfig.java b/src/main/java/org/myteam/server/global/security/config/SecurityConfig.java index 04e0ed96..71843dc3 100644 --- a/src/main/java/org/myteam/server/global/security/config/SecurityConfig.java +++ b/src/main/java/org/myteam/server/global/security/config/SecurityConfig.java @@ -17,6 +17,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; @@ -97,14 +98,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ); http - .addFilterBefore( + .addFilterAt( + new JwtAuthenticationFilter(authenticationManager(), jwtProvider, refreshJpaRepository), + UsernamePasswordAuthenticationFilter.class + ) // 로그인 인증 필터 + .addFilterAfter( new TokenAuthenticationFilter(jwtProvider), JwtAuthenticationFilter.class - ) - .addFilterAfter( - new JwtAuthenticationFilter(authenticationManager(), jwtProvider, refreshJpaRepository), - TokenAuthenticationFilter.class - ); // 회원 로그인 필터 + ); // JWT 토큰 검증 필터 // cors 설정 http @@ -113,13 +114,24 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // 경로별 인가 작업 http .authorizeHttpRequests(authorizeRequests -> - authorizeRequests - .requestMatchers(TOKEN_REISSUE_PATH).permitAll() // 토큰 재발급 - .requestMatchers("/h2-console").permitAll() // H2 콘솔 접근 허용 - .requestMatchers("/api/members/role").permitAll() // 유저 권한 변경 허용 - .requestMatchers("/api/members/get-token/{email}").permitAll() // 테스트용 토큰 발급용 - .requestMatchers("/api/admin/**").hasAnyRole(MemberRole.ADMIN.name()) - .anyRequest().permitAll() // 나머지 요청은 모두 허용 + authorizeRequests + .requestMatchers("/upload/**").permitAll() // 정적 자원 접근 허용 + .requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**").permitAll() + + .requestMatchers("/h2-console").permitAll() // H2 콘솔 접근 허용 + .requestMatchers("/api/members/get-token/**").permitAll() // 테스트용 토큰 발급용 + + .requestMatchers("/api/admin/**").hasAnyAuthority(MemberRole.ADMIN.name()) + .requestMatchers(HttpMethod.POST, "/api/me/create").permitAll() + .requestMatchers(HttpMethod.GET, "/api/categories/**").permitAll() + .requestMatchers(HttpMethod.PUT, "/api/categories/**").hasAnyAuthority(MemberRole.ADMIN.name()) + .requestMatchers(HttpMethod.DELETE, "/api/categories/**").hasAnyAuthority(MemberRole.ADMIN.name()) + .requestMatchers(HttpMethod.POST, "/api/categories").hasAnyAuthority(MemberRole.ADMIN.name()) + + .requestMatchers(TOKEN_REISSUE_PATH).permitAll() // 토큰 재발급 + .requestMatchers("/api/members/role").permitAll() // 유저 권한 변경 허용 + + .anyRequest().authenticated() // 나머지 요청은 모두 허용 ); http diff --git a/src/main/java/org/myteam/server/global/security/filter/JwtAuthenticationFilter.java b/src/main/java/org/myteam/server/global/security/filter/JwtAuthenticationFilter.java index 30c96691..ff66608b 100644 --- a/src/main/java/org/myteam/server/global/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/org/myteam/server/global/security/filter/JwtAuthenticationFilter.java @@ -85,7 +85,7 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR if (status.equals(PENDING.name())) { log.warn("PENDING 상태인 경우 로그인이 불가능합니다"); - sendErrorResponse(response, HttpStatus.FORBIDDEN, "PENDING 상태인 경우 로그인이 불가능합니다"); + sendErrorResponse(response, HttpStatus.LOCKED, "PENDING 상태인 경우 로그인이 불가능합니다"); return; } else if (status.equals(INACTIVE.name())) { log.warn("INACTIVE 상태인 경우 로그인이 불가능합니다"); @@ -113,6 +113,7 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR log.debug("print accessToken: {}", accessToken); log.debug("print refreshToken: {}", refreshToken); + log.debug("print role: {}", role); //Refresh 토큰 저장 addRefreshEntity(publicId, refreshToken, Duration.ofHours(24)); @@ -122,7 +123,6 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR response.addCookie(createCookie(REFRESH_TOKEN_KEY, cookieValue, LOGOUT_PATH, 24 * 60 * 60, true)); response.setStatus(HttpStatus.OK.value()); - // frontUrl += "?" + ACCESS_TOKEN_KEY + "=" + ("Bearer%20" + accessToken); // frontUrl += "&" + REFRESH_TOKEN_KEY + "=" + ("Bearer%20" + refreshToken); // response.sendRedirect(frontUrl); diff --git a/src/main/java/org/myteam/server/global/security/filter/TokenAuthenticationFilter.java b/src/main/java/org/myteam/server/global/security/filter/TokenAuthenticationFilter.java index a1a6317c..786ab874 100644 --- a/src/main/java/org/myteam/server/global/security/filter/TokenAuthenticationFilter.java +++ b/src/main/java/org/myteam/server/global/security/filter/TokenAuthenticationFilter.java @@ -34,24 +34,26 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - log.info("TokenAuthenticationFilter 토큰을 검사중"); + log.info("Token Authenticate Filter 토큰을 검사중"); String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION); String accessToken = jwtProvider.getAccessToken(authorizationHeader); log.info("accessToken : " + accessToken); if (StringUtils.isNotBlank(accessToken)) { - if (jwtProvider.validToken(accessToken)) { + try { + if (!jwtProvider.validToken(accessToken)) { + log.warn("인증되지 않은 토큰입니다"); + filterChain.doFilter(request, response); + return; + } String accessCategory = jwtProvider.getCategory(accessToken); - - if (!accessCategory.equals(TOKEN_CATEGORY_ACCESS)) { - // RestControllerAdvice 로 에러가 전달 되지 않아 여기서 에러 처리함 - log.warn("잘못된 토큰 유형입니다."); - sendErrorResponse(response, INVALID_TOKEN_TYPE.getStatus(), "잘못된 토큰 유형"); + if (!TOKEN_CATEGORY_ACCESS.equals(accessCategory)) { + log.warn("잘못된 토큰 유형입니다"); + filterChain.doFilter(request, response); return; } - // 토큰에서 username과 role 획득 UUID publicId = jwtProvider.getPublicId(accessToken); String role = jwtProvider.getRole(accessToken); String status = jwtProvider.getStatus(accessToken); @@ -60,7 +62,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse log.info("role : " + role); log.info("status : " + status); - // Member 를 생성하여 값 set Member member = Member.builder() .publicId(publicId) .role(MemberRole.valueOf(role)) @@ -68,45 +69,16 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse .build(); CustomUserDetails customUserDetails = new CustomUserDetails(member); - Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authToken); - log.info("security Context 에 정보 저장이 완료되었습니다."); - } else { - try { - if (jwtProvider.isExpired(accessToken)) { - // RestControllerAdvice 로 에러가 전달 되지 않아 여기서 에러 처리함 - log.warn("토큰이 만료되었습니다."); - sendErrorResponse(response, ACCESS_TOKEN_EXPIRED.getStatus(), "만료된 토큰"); - return; - } - } catch (JwtException | IllegalArgumentException e) { - // RestControllerAdvice 로 에러가 전달 되지 않아 여기서 에러 처리함 - log.error("잘못된 JWT 토큰 형식 또는 그 외 에러 : {}", e.getMessage()); - sendErrorResponse(response, INVALID_ACCESS_TOKEN.getStatus(), "잘못된 JWT 토큰 형식 또는 그 외 에러"); - return; - } - // RestControllerAdvice 로 에러가 전달 되지 않아 여기서 에러 처리함 - log.warn("인증되지 않은 토큰입니다."); - sendErrorResponse(response, INVALID_ACCESS_TOKEN.getStatus(), "인증되지 않은 토큰"); + log.info("SecurityContext 에 인증 정보 저장 완료"); + } catch (JwtException e) { + log.error("JWT 처리 중 오류 발생: {}", e.getMessage()); + filterChain.doFilter(request, response); return; } } filterChain.doFilter(request, response); } - - /** - * 공통 에러 응답 처리 메서드 - * - * @param response HttpServletResponse - * @param httpStatus HTTP 상태 오브젝트 - * @param message 메시지 - * @throws IOException - */ - private void sendErrorResponse(HttpServletResponse response, HttpStatus httpStatus, String message) throws IOException { - response.setStatus(httpStatus.value()); - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); - response.getWriter().write(String.format("{\"message\":\"%s\",\"status\":\"%s\"}", message, httpStatus.name())); - } } diff --git a/src/main/java/org/myteam/server/member/controller/MemberController.java b/src/main/java/org/myteam/server/member/controller/MemberController.java index 75d9a934..483f5dcf 100644 --- a/src/main/java/org/myteam/server/member/controller/MemberController.java +++ b/src/main/java/org/myteam/server/member/controller/MemberController.java @@ -80,11 +80,19 @@ public ResponseEntity updateRole(@RequestBody @Valid MemberRoleUpdateRequest return new ResponseEntity<>(new ResponseDto<>(SUCCESS.name(), "권한 변경 성공", response), HttpStatus.OK); } - @GetMapping("/get-token/{email}") + @GetMapping("/get-token/admin/{email}") + public ResponseEntity getAdminToken(@PathVariable String email) { + log.info("getToken 메서드가 실행되었습니다."); + MemberResponse response = memberService.getByEmail(email); + String encode = TOKEN_PREFIX + jwtProvider.generateToken(TOKEN_CATEGORY_ACCESS, Duration.ofHours(10), response.getPublicId(), MemberRole.ADMIN.name(), response.getStatus().name()); + return new ResponseEntity<>(new ResponseDto<>(SUCCESS.name(), "토큰 조회 성공", encode), HttpStatus.OK); + } + + @GetMapping("/get-token/user/{email}") public ResponseEntity getToken(@PathVariable String email) { log.info("getToken 메서드가 실행되었습니다."); MemberResponse response = memberService.getByEmail(email); - String encode = TOKEN_PREFIX + jwtProvider.generateToken(TOKEN_CATEGORY_ACCESS, Duration.ofHours(6), response.getPublicId(), MemberRole.USER.name(), response.getStatus().name()); + String encode = TOKEN_PREFIX + jwtProvider.generateToken(TOKEN_CATEGORY_ACCESS, Duration.ofHours(10), response.getPublicId(), MemberRole.USER.name(), response.getStatus().name()); return new ResponseEntity<>(new ResponseDto<>(SUCCESS.name(), "토큰 조회 성공", encode), HttpStatus.OK); } } diff --git a/src/main/java/org/myteam/server/member/domain/GenderType.java b/src/main/java/org/myteam/server/member/domain/GenderType.java index afe02e52..f52d8eb4 100644 --- a/src/main/java/org/myteam/server/member/domain/GenderType.java +++ b/src/main/java/org/myteam/server/member/domain/GenderType.java @@ -2,9 +2,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import org.myteam.server.global.exception.PlayHiveException; - -import static org.myteam.server.global.exception.ErrorCode.INVALID_PARAMETER; @AllArgsConstructor @Getter @@ -18,6 +15,6 @@ public static GenderType fromValue(String value) { return gender; } } - throw new PlayHiveException(INVALID_PARAMETER, "Invalid gender value: " + value); + return null; } } diff --git a/src/main/java/org/myteam/server/member/dto/MemberDeleteRequest.java b/src/main/java/org/myteam/server/member/dto/MemberDeleteRequest.java index 1cade623..dc783bb6 100644 --- a/src/main/java/org/myteam/server/member/dto/MemberDeleteRequest.java +++ b/src/main/java/org/myteam/server/member/dto/MemberDeleteRequest.java @@ -14,6 +14,6 @@ public class MemberDeleteRequest { @NotBlank // @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문+숫자 조합 4 ~ 10자 이내로 입력해주세요") - @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 10자 이내로 입력해주세요") + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{4,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 15자 이내로 입력해주세요") private String password; // 비밀번호 } diff --git a/src/main/java/org/myteam/server/member/dto/MemberSaveRequest.java b/src/main/java/org/myteam/server/member/dto/MemberSaveRequest.java index 267a87db..ce86271d 100644 --- a/src/main/java/org/myteam/server/member/dto/MemberSaveRequest.java +++ b/src/main/java/org/myteam/server/member/dto/MemberSaveRequest.java @@ -17,7 +17,7 @@ public class MemberSaveRequest { @NotBlank // @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문+숫자 조합 4 ~ 10자 이내로 입력해주세요") - @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 10자 이내로 입력해주세요") + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{4,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 15자 이내로 입력해주세요") private String password; // 비밀번호 @NotBlank diff --git a/src/main/java/org/myteam/server/member/dto/MemberUpdateRequest.java b/src/main/java/org/myteam/server/member/dto/MemberUpdateRequest.java index b008ccbf..bd870372 100644 --- a/src/main/java/org/myteam/server/member/dto/MemberUpdateRequest.java +++ b/src/main/java/org/myteam/server/member/dto/MemberUpdateRequest.java @@ -30,7 +30,7 @@ public class MemberUpdateRequest { @Column(name = "birth_date") private LocalDate birthdate; - @Pattern(regexp = "^(MALE|FEMALE)$", message = "성별은 MALE, FEMALE 중 하나여야 합니다.") + @Pattern(regexp = "^(?i)(MALE|FEMALE)$", message = "성별은 MALE, FEMALE 중 하나여야 합니다.") private String gender; private MemberStatus status; diff --git a/src/main/java/org/myteam/server/member/dto/PasswordChangeRequest.java b/src/main/java/org/myteam/server/member/dto/PasswordChangeRequest.java index 24b8f77e..40d84dba 100644 --- a/src/main/java/org/myteam/server/member/dto/PasswordChangeRequest.java +++ b/src/main/java/org/myteam/server/member/dto/PasswordChangeRequest.java @@ -8,15 +8,15 @@ @NoArgsConstructor public class PasswordChangeRequest { @NotNull - @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 10자 이내로 입력해주세요") + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{4,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 15자 이내로 입력해주세요") private String password; @NotNull - @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 10자 이내로 입력해주세요") + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{4,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 15자 이내로 입력해주세요") private String newPassword; @NotNull - @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{8,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 10자 이내로 입력해주세요") + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[0-9]).{4,15}$", message = "비밀번호는 영문, 숫자 조합 4 ~ 15자 이내로 입력해주세요") private String confirmPassword; @Builder From 8a67dd6ec1381542ba466616b3810d94903f4e88 Mon Sep 17 00:00:00 2001 From: leejongwook2 <34103253+leejongwook2@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:17:16 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95=20=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B=201.=20=EC=9C=A0=EC=A0=80=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=20-=20=EC=9E=90=EC=8B=A0=EC=9D=98=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EB=A5=BC=20=EB=B3=80=EA=B2=BD=20=20-=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=EC=9E=90=EA=B0=80=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?=EA=B3=84=EC=A0=95=EC=9D=98=20=EC=83=81=ED=83=9C=EB=A5=BC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=20-=20=EC=9D=BC=EB=B0=98=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EA=B0=80=20=EB=8B=A4=EB=A5=B8=20=EC=82=AC=EB=9E=8C?= =?UTF-8?q?=EC=9D=98=20=EC=9C=A0=EC=A0=80=20=EB=B3=80=EA=B2=BD=EC=8B=9C=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 2 +- .../myteam/server/member/entity/Member.java | 5 +++ .../server/member/service/MemberService.java | 32 ++++++++++++------- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/myteam/server/member/controller/MemberController.java b/src/main/java/org/myteam/server/member/controller/MemberController.java index 483f5dcf..f7716543 100644 --- a/src/main/java/org/myteam/server/member/controller/MemberController.java +++ b/src/main/java/org/myteam/server/member/controller/MemberController.java @@ -66,7 +66,7 @@ public ResponseEntity updateStatus(@RequestBody @Valid MemberStatusUpdateRequ log.info("email : {}" , response.getEmail()); // 서비스 호출 - String targetEmail = response.getEmail(); // 상태를 변경할 대상 이메일 + String targetEmail = response.getEmail(); // 변경을 시도하는 유저의 이메일 (본인 또는 관리자) memberService.updateStatus(targetEmail, memberStatusUpdateRequest); diff --git a/src/main/java/org/myteam/server/member/entity/Member.java b/src/main/java/org/myteam/server/member/entity/Member.java index 671175f3..84fdc638 100644 --- a/src/main/java/org/myteam/server/member/entity/Member.java +++ b/src/main/java/org/myteam/server/member/entity/Member.java @@ -16,6 +16,7 @@ import org.myteam.server.member.dto.PasswordChangeRequest; import org.springframework.security.crypto.password.PasswordEncoder; +import static org.myteam.server.member.domain.MemberRole.ADMIN; import static org.myteam.server.member.domain.MemberRole.USER; import static org.myteam.server.member.domain.MemberStatus.PENDING; import static org.myteam.server.member.domain.MemberType.LOCAL; @@ -126,6 +127,10 @@ public boolean verifyOwnEmail(String email) { return email.equals(this.email); } + public boolean isAdmin() { + return this.role.equals(ADMIN); + } + public boolean validatePassword(String inputPassword, PasswordEncoder bCryptPasswordEncoder) { // 입력된 평문 패스워드와 이미 암호화된 패스워드를 비교 boolean isValid = bCryptPasswordEncoder.matches(inputPassword, this.password); diff --git a/src/main/java/org/myteam/server/member/service/MemberService.java b/src/main/java/org/myteam/server/member/service/MemberService.java index 3d5c1d53..cdeb0daa 100644 --- a/src/main/java/org/myteam/server/member/service/MemberService.java +++ b/src/main/java/org/myteam/server/member/service/MemberService.java @@ -168,20 +168,30 @@ public void changePassword(String email, PasswordChangeRequest passwordChangeReq @Transactional public void updateStatus(String targetEmail, MemberStatusUpdateRequest memberStatusUpdateRequest) { - log.info("토큰에서 추출된 이메일: {}, 상태를 변경할 대상 이메일: {}, 새로운 상태: {}", targetEmail, memberStatusUpdateRequest.getStatus().name()); - - Member member = memberRepository.getByEmail(targetEmail); + log.info("토큰에서 추출된 이메일: {}, 상태를 변경할 대상 이메일: {}, 새로운 상태: {}", + targetEmail, memberStatusUpdateRequest.getEmail(), memberStatusUpdateRequest.getStatus().name()); + + // 요청자와 대상 사용자 정보 조회 + Member requester = memberRepository.getByEmail(targetEmail); // 요청자 + Member targetMember = memberRepository.getByEmail(memberStatusUpdateRequest.getEmail()); // 상태 변경 대상자 + + // 1. 요청자가 본인의 상태를 변경하려는 경우 + if (requester.verifyOwnEmail(memberStatusUpdateRequest.getEmail())) { + log.info("사용자가 자신의 상태를 변경 중: {}", targetEmail); + requester.updateStatus(memberStatusUpdateRequest.getStatus()); + return; + } - // 자신의 계정이 아닌 다른 계정을 수정하려고 함 - if (!member.verifyOwnEmail(memberStatusUpdateRequest.getEmail())) { - if (!member.getRole().equals(MemberRole.ADMIN)) { // 관리자가 상태를 업데이트 하려고 하는 것일 수도 있는 상황 - // 빈 Response 객체 반환 - throw new PlayHiveException(NO_PERMISSION, "상태 수정 권한이 없습니다."); - } + // 2. 관리자가 다른 사용자의 상태를 변경하려는 경우 + if (requester.isAdmin()) { + log.info("관리자가 상태를 변경 중: {}, 대상자: {}", targetEmail, memberStatusUpdateRequest.getEmail()); + targetMember.updateStatus(memberStatusUpdateRequest.getStatus()); + return; } - // 상태 업데이트 - member.updateStatus(memberStatusUpdateRequest.getStatus()); + // 3. 권한 없는 사용자가 다른 사용자의 상태를 변경하려고 시도한 경우 + log.warn("권한 없는 요청: 요청자 {}, 대상자 {}", targetEmail, memberStatusUpdateRequest.getEmail()); + throw new PlayHiveException(NO_PERMISSION, "상태 수정 권한이 없습니다."); } public boolean existsByEmail(String email) { From bdea8c26c0bb64bc605b237879c7bffb46c3ee47 Mon Sep 17 00:00:00 2001 From: leejongwook2 <34103253+leejongwook2@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:32:41 +0900 Subject: [PATCH 4/4] =?UTF-8?q?Oauth2=20PENDING=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=EB=A6=AC=ED=94=84=EB=A0=88?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=EC=9D=80=20=EB=B0=9C=EA=B8=89?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EC=9D=B4=EB=8F=99=EC=8B=9C=ED=82=A4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/CustomOauth2SuccessHandler.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/myteam/server/oauth2/handler/CustomOauth2SuccessHandler.java b/src/main/java/org/myteam/server/oauth2/handler/CustomOauth2SuccessHandler.java index 3fe234b2..cac3e927 100644 --- a/src/main/java/org/myteam/server/oauth2/handler/CustomOauth2SuccessHandler.java +++ b/src/main/java/org/myteam/server/oauth2/handler/CustomOauth2SuccessHandler.java @@ -54,9 +54,26 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String role = auth.getAuthority(); String status = customUserDetails.getStatus().name(); + log.info("onAuthenticationSuccess email: {}", email); + log.info("onAuthenticationSuccess role: {}", role); + //유저확인 + Member member = memberJpaRepository.findByEmail(email) + .orElseThrow(() -> new RuntimeException("Member not found")); + log.info("onAuthenticationSuccess publicId: {}", member.getPublicId()); + log.info("onAuthenticationSuccess role: {}", member.getRole()); + + if (status.equals(PENDING.name())) { log.warn("PENDING 상태인 경우 로그인이 불가능합니다"); // sendErrorResponse(response, HttpStatus.FORBIDDEN, "PENDING 상태인 경우 로그인이 불가능합니다"); + // X-Refresh-Token + String refreshToken = jwtProvider.generateToken(TOKEN_CATEGORY_REFRESH, Duration.ofDays(7), member.getPublicId(), member.getRole().name(), member.getStatus().name()); + String cookieValue = URLEncoder.encode("Bearer " + refreshToken, StandardCharsets.UTF_8); + + // redirect 순간 Header 값 날아감 + // response.addHeader(ACCESS_TOKEN_KEY, "Bearer " + accessToken); + response.addCookie(createCookie(REFRESH_TOKEN_KEY, cookieValue, TOKEN_REISSUE_PATH, 24 * 60 * 60, true)); + response.addCookie(createCookie(REFRESH_TOKEN_KEY, cookieValue, LOGOUT_PATH, 24 * 60 * 60, true)); response.sendRedirect(frontUrl + "?status=" + status); return; } else if (status.equals(INACTIVE.name())) { @@ -71,13 +88,6 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo return; } - log.info("onAuthenticationSuccess email: {}", email); - log.info("onAuthenticationSuccess role: {}", role); - //유저확인 - Member member = memberJpaRepository.findByEmail(email) - .orElseThrow(() -> new RuntimeException("Member not found")); - log.info("onAuthenticationSuccess publicId: {}", member.getPublicId()); - log.info("onAuthenticationSuccess role: {}", member.getRole()); // Authorization String accessToken = jwtProvider.generateToken(TOKEN_CATEGORY_ACCESS, Duration.ofHours(1), member.getPublicId(), member.getRole().name(), member.getStatus().name()); // X-Refresh-Token