본문 바로가기

ETC/Error

회원가입시 발생한 문제들

팀원이 OAuth 로그인과 Spring Security 설정을 하고 main 브랜치에 merge했다는 연락을 받았다.

집에와서 나도 form 로그인과 회원가입을 구현하는데 다양한 문제가 발생해서 기록해본다.


0.  데이터베이스 설정 누락 및 칼럼 없음

브라우저에서 회원가입하는데 500 에러가 발생했다.

인텔리제이를 열어보니 DB 연결 설정이 없다고하고 application.yml 파일이 사라져있었다.

git에 시크릿키랑 DB 정보가 그대로 노출되서 Git 캐시를 삭제하고, 다시 커밋 후 푸시& 풀 을 하면서 파일이 날아간거같다.

다시 yml 파일을 재생성.

Error starting ApplicationContext. To display the condition evaluation report 
re-run your application with 'debug' enabled.
2024-10-06T00:01:13.724+09:00 ERROR 62905 
--- [backend] [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and 
no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).


Process finished with exit code 1

 

role이라는 칼럼이 없다고해서 팀원에게 물어보니 varchar10 정도로 만들면된다고해서 그거도 추가.

ALTER TABLE user ADD COLUMN roles VARCHAR(10);

 

다시 리엑트로 회원가입 버튼을 클릭해보니 다른 문제가 발생하였다.


1. CORS 에러 

프론트에서 프록시 설정도 되어있고 초기세팅때 cors config 설정하고 통신 성공까지 확인했는데도  cors 에러가 왜에에에 발생하냐구..

 

@CorssOrigin 을 사용하면 엔드포인트에 맞춰서 CORS를 허용할 수 있다고한다. (🤔)

@CrossOrigin(origins = "http://localhost:3000")

 

난 새롭게 들어온 spring security에 cors설정이 없어서 cors config를 securiyConfig에 추가하였다. 

@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .csrf(csrf -> csrf.disable())
                ....
    }

@Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:3000");
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }

 

 


2. JWT Filter 작동 문제

사진을 못찍었는데 JWPFilter 어쩌고 하면서 토큰이 없다는 에러 메세지가 떴다.

'에에 회원가입하는데 토큰이 왜필요하지?' jwtFilter  파일을 열어보았다. 

낯설다.. 내가 짠게 아니라 낯설지만 회원가입시 토큰 인증 예외처리가 안된것이 보인다.. 추가..

@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("do JWT Filter ! request = {}", request);

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        // 회원가입 요청 예외 처리 추가
        if ("/form/register".equals(httpRequest.getRequestURI())) {
            log.info("request register URI = {}", httpRequest.getRequestURI());
            chain.doFilter(request, response);
            return;
        }

        // 로그인 요청을 예외 처리
        if ("/form/login".equals(httpRequest.getRequestURI())) {
            log.info("httpUri = {}", httpRequest.getRequestURI());
            chain.doFilter(request, response);
            return;
        }
        
        ....
    }

 

다시 리엑트로 회원가입 버튼을 클릭해보니 다른 문제가 발생

이번에는 또 뭐가 문제일까

 

3. HTTP 500 Internal Server Error : JSON parse error

어.음.에? 백엔드에서 발생한에러를보니 생소한걸?

org.springframework.http.converter.HttpMessageNotReadableException: 
JSON parse error: Cannot deserialize value of type 
`java.lang.String` from Array value (token `JsonToken.START_ARRAY`)

 

 

팀원이 토큰을 배열로 잘못만들었다고 고친다고했던게 이거였던건가

팀원이 해보겠다 했으니, 일단 에러가 발생할때 전역으로 처리할 에러헨들러 파일을 하나 생성하여 예외 처리를 하였다.

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {

        e.printStackTrace(); 

        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Internal Server Error: " + e.getMessage());
    }
    
    @ExceptionHandler(ResponseStatusException.class)
    public ResponseEntity<String> handleNotFound(ResponseStatusException e) {
        if (e.getStatus() == HttpStatus.NOT_FOUND) {
            return ResponseEntity
                    .status(HttpStatus.NOT_FOUND)
                    .body("Error 404: " + e.getReason());
        }
        return handleException(e); 
    }

    @ExceptionHandler(UnauthorizedException.class) 
    public ResponseEntity<String> handleUnauthorized(UnauthorizedException e) {
        return ResponseEntity
                .status(HttpStatus.UNAUTHORIZED)
                .body("Error 401: " + e.getMessage());
    }

    @ExceptionHandler(BadRequestException.class) 
    public ResponseEntity<String> handleBadRequest(BadRequestException e) {
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body("Error 400: " + e.getMessage());
    }
}

 

다시 회원가입 시도


 

3.  DB에 중복된 값이 존재

fail 이라고 떠서 당연히 값이 안들어간 줄 알았는데 이미 중복된 값이 있어서 안된다고한다.

가입 성공하고난뒤의 로직을 설정하지않아서 성공한줄 몰랐다.

가입이 성공하면 바로 로그인창이 뜨도록 로직을 추가하였다. (중복확인 기능은 이따 추가하기로...)

 

 

서버를 재실행하고 다른 정보로 회원가입을 요청하니 DB에 잘 등록되었다.

 

하지만 DB를 확인하니 nickname, role, created_at이 안들어가는것을 확인.

 

 

[nickname, role 추가]

우선 리엑트에서 백엔드가 받아야할 테이터 명과 일치하게 form데이터를 변경하여 보냈다.

 

[created_at 추가]

ALTER TABLE user 
MODIFY COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP;

 

 

다 잘들어가는것을 확인

 

회원가입과 로그인 모두 잘되는 것을 확인!

하지만 또다른 문제 발생...

회원가입에서 로그인으로 바로 이동은되는데

로그인버튼을 눌러도 회원가입 폼이 나오고 회원가입 버튼을 눌러도 회원가입 폼이 나타난다.


4.  로그인 버튼을 눌러도 회원가입 폼이 나오는 문제

회원가입시 자동으로 로그인 모달이 나오게까지는 했다. 

그런데 Nav에서 로그인을 누를경우 회원가입 폼이 나타났다.

허어어...

 

처음에 코드를보고 생각한 원인은 2가지 였다.
1. state로 로그인 컴포넌트를 false로 설정해놓아서

 ➔ 로그인 회원가입 각각의 상태를 관리하여 통신 성공시 true,false로 관리 (❌) 

 ➔ 통신 성공시 Nav에서 들어오는 type을 강제로 변환하고 useEffect로 리랜더링 (❌)

2. Nav에서 props로 모달을 관리하는 함수를 전달하지 않아서 

 ➔ Nav에서 상태를 관리하고 Modal로 porps를 전달하여 변경되도록 변경 (❌)

3. ....등등

 

하지만 두 원인을 기반으로 시도한 방법들이 모두 되지않았다.

 

근데 참 신기한게 팀원이랑 카톡하면서 얘기하다보면 순간 번뜩할때가 있다.

정말 간단한 생각이였는데 기존의 모달이 열리는 조건과 state의 상태를 섞어서 조건을 주면 되지않을까했다.

 

const Modal = ({type, closeModal }) => { // Nav에서 모달의 type은 그대로 가져오고
  const [showLoginForm, setShowLoginForm] = useState(false); // login컴포넌트는 비활성 상태

// POST 요청문
	// 회원가입 요청성공시 LoginForm의 상태를 true로 변경
	setShowLoginForm(true); 

// return 문
	// 기존의 조건문
	{type === "login" ? (<LoginForm>) : (<RegisterForm>)}
    
    // showLoginForm이 false 더라도 nav에서 넘어온 타입이 login이면 loginForm이 보여지도록 변경
    {showLoginForm || type === "login" ? (<LoginForm>) : (<RegisterForm>)}
}

 

이 간단한 문제를 거의 2시간동안 헤메고 있었다.