-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Description
📝 개요
- 마이페이지 화면: 회원 정보 조회/수정/회원탈퇴 기능 유효성 검사 및백엔드 연동
- 전체 메뉴 화면: 회원 정보 조회/로그아웃 기능 백엔드 연동
- 관련 issue (FinalProjectBack): ✨ [Feat] 마이페이지 - 회원정보조회/수정/회원탈퇴 기능 추가 FinalProjectBack#51 (comment)
✅ To-Do
마이페이지
- 1 회원 정보 조회
- 1.1 회원 정보 조회 API 호출
- 1.2 회원 정보(이름, 이메일) 표시 (회원 정보 수정 성공 시 변경된 회원 정보 반영되도록 설정)
- 2 회원 정보 수정
- 2.1 이름 변경
- 2.1.1 이름 입력값 유효성 검사
- 2.1.1.1 기존 이름과 동일 X
- 2.1.1.2 올바른 형식: 한글, 영문, 2~20자
- 2.1.1 이름 입력값 유효성 검사
- 2.2 비밀번호 변경
- 2.2.1 비밀번호 변경 항목 표시 대상: 이메일 계정만 (카카오 및 구글 계정은 미표시)
- 2.2.2 기존 비밀번호 검증
- 2.2.3 비밀번호 입력값 유효성 검사
- 2.2.3.1 기존 비밀번호와 동일 X
- 2.2.3.2 올바른 형식: 영문 대·소문자, 숫자, 특수문자를 각각 포함한 8자 이상
- 2.2.3.3 새 비밀번호, 비밀번호 일치 여부
- 2.3 프로필사진 변경
- 2.3.1 새 파일로 변경
- 2.3.2 기본 이미지로 변경
- 2.4 회원 정보 수정 성공/실패 시 안내 메세지 표시 (Toast)
- 2.1 이름 변경
- 3 회원 탈퇴
- 3.1 이메일 계정
- 3.1.1 기존 비밀번호 검증
- 3.1.2 회원 탈퇴 API 호출
- 3.2 카카오 계정
- 3.3 구글 계정
- 3.1 이메일 계정
전체 메뉴
- 1 회원 정보 조회
- 1.1 로그인 했을 경우
- 1.1.1 이메일 계정: 프로필사진, 이름 및 이메일 표시
- 1.1.2 카카오 계정: 프로필사진, 이름 표시
- 1.1.3 구글 계정: 프로필사진, 이름 및 이메일 표시
- 1.2 비회원
- 1.2.1 로고, "안전한 거래의 시작, Light House" 문구 표시
- 1.1 로그인 했을 경우
- 2 로그아웃 버튼 로그인한 회원에게만 표시
📸 스크린샷
마이페이지 (/mypage)
- 회원 정보 조회/표시, 프로필사진 수정, 회원 정보 (이름, 비밀번호)수정, 회원 탈퇴
전체 메뉴 (/mainMenu)
- 상단 프로필 영역
Trouble Shooting
CORS 설정
- 문제: 회원정보 변경 요청 시, 이름/비밀번호는 CORS 통과 되는데 프로필사진 변경은 CORS에 걸림
-
원인:
- 파일 업로드 요청에서만 CORS가 막히는 이유는
multipart/form-data요청과 Spring Security 필터 체인의 처리 순서에 있음
- 파일 업로드 요청에서만 CORS가 막히는 이유는
-
설명:
- 파일 업로드 요청의 특성:
multipart/form-data는 일반 JSON 요청과 다른Content-Type을 가짐 - Spring Security 필터 순서: CORS 필터가 Security 필터 체인에서 부적절한 위치에 있을 수 있음
- OPTIONS 요청 처리: 브라우저가 파일 업로드 전에 보내는
preflight OPTIONS요청이 제대로 처리되지 않을 수 있음
- 파일 업로드 요청의 특성:
-
해결 방법:
CorsConfigurationSource분리: CORS 설정을 별도 Bean으로 분리하여 더 명확하게 관리- 헤더 명시적 설정:
multipart/form-data요청에 필요한 헤더들을 명시적으로 허용 OPTIONS요청 강화: 모든 경로에서 OPTIONS 요청을 허용하도록 변경- 필터 순서 명시: CORS 필터를 명시적으로 필터 체인에 추가
preflight캐시:maxAge설정으로 preflight 요청 최적화
package com.lighthouse.security.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
@Slf4j
@Configuration
@EnableWebSecurity
@ComponentScan(basePackages = {"com.lighthouse.security"})
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${FRONT_ORIGIN}")
private String frontOrigin;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.addAllowedOrigin(frontOrigin);
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
// multipart/form-data 요청을 위한 추가 설정 - 프로필사진 업로드 시 사용
configuration.setAllowedHeaders(Arrays.asList(
"Authorization", "Content-Type", "Content-Disposition", "Content-Length",
"X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
"Access-Control-Request-Headers"
));
configuration.setExposedHeaders(Arrays.asList(
"Access-Control-Allow-Origin", "Access-Control-Allow-Credentials"
));
configuration.setMaxAge(3600L); // preflight 캐시 시간 설정 (1시간)
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public CorsFilter corsFilter() {
return new CorsFilter(corsConfigurationSource());
}
// 접근 제한 무시 경로 설정 – resource
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/assets/**", "/*", "/api/member/**",
"/swagger-ui.html", "/webjars/**", "/swagger-resources/**", "/v2/api-docs");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable()
.csrf().disable()
.formLogin().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors().configurationSource(corsConfigurationSource()); // CorsConfigurationSource 명시적 지정
http
.authorizeRequests() // 경로별 접근 권한 설정
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 모든 OPTIONS 요청 허용
.antMatchers(HttpMethod.PUT, "/api/member").authenticated()
.anyRequest().permitAll();
}
}Metadata
Metadata
Assignees
Labels
No labels