Group Study (2020-2021)/ReactJS + Spring Boot ToyProject

[웹 3팀] 4. 회원가입/로그인 구현을 위한 trouble shooting

안녕_유지 2020. 12. 27. 23:35

앞선 [웹3팀]의 포스팅처럼 글을 MySQL, Mybatis, AWS RDS를 이용하여 데이터베이스에 저장했다.

글 뿐만 아니라, 회원 또한 데이터베이스에 연결하여 어떤 사람이 어떤 글을 썼는지 데이터베이스의 두 테이블을 연결(Join)하고자 했다. 이번 포스팅에서는 기술적인 부분 뿐만 아니라 어떤 오류가 발생해서 어떻게 해결했는지 경험을 바탕으로 작성하도록 하겠다. (코드는 깃헙에서 확인할 수 있으므로)

(Join이란, 데이터베이스에서 두개이상의 테이블이나 데이터베이스를 연결하여 데이터베이스를 연결하는 것을 의미한다.)

 

1. Spring Security

1-1. Spring에서 기본적으로 제공하는 로그인 폼이 멋져서 이걸 사용해야겠다고 생각을 했다. Spring Security를 이용하면 다양한 장점이 있었다. Spring Security란 스프링 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다.  인증(Authenticate, 누구인지?) 과 권한(Authorize, 어떤것을 할 수 있는지?)를 담당하는 프레임워크를 말한다. 자체제공 폼과 자동적인 보안검사외에도 사용하면 다양한 장점이 있다.

처음에 쉽게하려고 권한(관리자, 사용자, 전체)을 나누지 않고 일반 사용자가 가입만 할 수 있도록 하고싶었는데 아래에 이런 쉬운 생각으로 인해 고통을 겪었다. (아마도,,)

  •  보안과 관련하여 체계적으로 많은 옵션을 제공하여  일일히 작성하지 않고 편리하게 사용할 수 있음
  •  Filter 기반으로 동작하여 MVC와 분리하여 관리 및 동작
  • 어노테이션을 통한 간단한 설정
  • Spring Security는 기본적으로 세션 & 쿠키방식으로 인증
  • 서블릿 필터(filter)와 이들로 구성된 필터체인으로 구성된 위임모델 사용

 

 

victorydntmd.tistory.com/328?category=764331

 

[SpringBoot] Spring Security를 이용한 회원가입/로그인/로그아웃

이번 글에서는 Spring Security를 이용하여 회원가입 및 로그인을 구현해보도록 하겠습니다. 전체 코드는 깃헙을 참고하시길 바랍니다. 개발환경 IntelliJ 2019.02 Java 11 SpringBoot 2.1.9 Gradle 5.6 라이브..

victorydntmd.tistory.com

 

위의 블로그를 참고하여 어떤 원리로 이루어지는지 배우고, 직접 클론코딩 해보았다. 설정은 WebSecurityConfigurerAdapter라는 클래스를 상속받은 클래스에서 메서드를 오버라이딩하여 조정하여, 그 클래스를 구현했다.

package toyproject.toyproject.signuplogin.config;

import lombok.AllArgsConstructor;
import toyproject.toyproject.signuplogin.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;



@Configuration
@EnableWebSecurity //이 어노테이션이 spring security 설정할 클래스라고 정의
@AllArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private MemberService memberService;
    //WebSecurityConfigurer 인스턴스 편리하게 생성하기 위해 상속받음
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    //비밀번호 암호화 객체
    @Override
    public void configure(WebSecurity web)throws Exception{
        web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
        // resources/static 디렉터리 하위 파일 목록은 인증 무시(항상 통과) 언젠가 쓰일것을 대비
    }

    //
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 페이지 권한 설정
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/myinfo").hasRole("MEMBER")
                .antMatchers("/**").permitAll()
                .and() // 로그인 설정
                .formLogin()//로그인 폼 이용 할 수 있도록 함함
                .loginPage("/user/login")
                .defaultSuccessUrl("/user/login/result")
                .permitAll()
                .and() // 로그아웃 설정
                .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
                .logoutSuccessUrl("/user/logout/result")
                .invalidateHttpSession(true)
                .and()
                // 403 예외처리 핸들링
                .exceptionHandling().accessDeniedPage("/user/denied");
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(memberService).passwordEncoder(passwordEncoder());
    }
}

 

다만, 차이점은 진행중인 프로젝트는 Mybatis를 이용하고, React로 프론트엔드를 작성했지만, 위의 블로그에서는 JPA를 사용하고, HTML로 페이지들이 이루어졌다. 

  • @EnableWebSecurity
    • @Configuration 클래스에 @EnableWebSecurity 어노테이션을 추가하여 Spring Security 설정할 클래스라고 정의합니다.
    • 설정은 WebSebSecurityConfigurerAdapter 클래스를 상속받아 메서드를 구현하는 것이 일반적인 방법입니다. 
  • WebSecurityConfigurerAdapter 클래스
  • passwordEncoder()
    • BCryptPasswordEncoder는 Spring Security에서 제공하는 비밀번호 암호화 객체입니다.
    • Service에서 비밀번호를 암호화할 수 있도록 Bean으로 등록합니다.

이후 configure() 메서드를 오버라이딩하여, Security 설정을 해준다.

클론 코딩 후, 조금씩만 바꾸면 금방될 줄 알았지만, 사실 아니었다. 생각외로 잘 되지 않았다. 읽으면 이해는 되지만, 실제로 내가 하고 있는 프로젝트에 커스터마이징하기에는 아직 무리여서 다른 방법을 고안해보기로했다. 

리액트 <-> 스프링부트 <-> MySQL 이 관계가 아직 머릿속에 정리되지 않은듯 했다. 어렵다..

밑에는 블로그보고 실제 클론코딩한 결과페이지이다.

메인페이지
회원가입
로그인 성공

 

 

2. 하나의 데이터베이스에서 테이블 간 연결

결국, 위의 방법에서 잘 되지 않아 글 CRUD하는 방식과 동일하게 회원데이터를 만들고자했다. 이는 웹3팀의 다른 팀 멤버들의 포스팅을 보면 참고바란다. 보안 개나줘버려

하지만, 여기서도 문제가 발생했다. 서버를 돌려보니, 

  1. 리액트와 스프링이 연결이 되지 않았다. (그래서 여태까지 DB에 저장된 글 목록이 보이지 않음)
  2. 첫 시작페이지에 계속 로그인하라며 만들지도 않은 팝업창이 뜨고, 이를 무시하고 회원가입 페이지로 가서 회원가입을 하려고했더니 회원가입결과페이지가 아닌 다시 가입하라는 창이 떴다..!
  3. DB에 입력한 회원 데이터가 저장되지 않았다. 

 

localhost:8080이 아닌 localhost:3000으로 접속, 로그인 팝업
회원가입을 하려고 정보를 입력했음
갑자기 만들지도 않은 새로운 페이지가 뜸
회원 데이터 저장 안되었음

 

이를 각각 해결한 방법은

1번 문제는 굉장히 간단한 문제였다. 리액트와 스프링 서버를 동시에 돌리지 않고 스프링 서버만 돌렸다. (리액트 서버를 보고 있었다. 머쓱) 그리고 리액트에서 <form action="/user/login" method="post"> 이 줄을 쓰지 않아서 스프링으로 리액트상에서 입력받은 form을 넘기지 못했다. 

//Login.js

import React from 'react';
import './LogIn.css'

function LogIn() {


    return (
        <div className="login-template">
        
            <div className="title">
                User Login
            </div>
            <form action="/user/login" method="post">
                <div className="input-wrapper">
                    <input type="text" name="userID" placeholder="userID" />
                    <input type="password" name="userPW" placeholder="password" />
                    회원이 아니시라면 회원가입 후 로그인 해주세요.
                </div>
                <button type="submit">Sign In</button>
            </form>
        </div>
    );
}

export default LogIn;

 

2번 문제는 코어 멤버인 방경림님께서 도와주셨다. 바로 build.gradle에서 스프링 시큐리티 의존성을 삭제해야했다.

스프링 시큐리티 쓰면 기본적으로 모든 페이지가 인증이 필요한 페이지로 바뀌고 localhost:8080/login으로 리다이렉트 된다. 그래서 스프링 시큐리티를 안 쓸거면 의존성에서도 삭제해야한다. 

//수정된 build.gradle 일부

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compile 'org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2'
	compile 'org.springframework.boot:spring-boot-starter-jdbc'
	compile 'mysql:mysql-connector-java'
	compile 'org.mybatis:mybatis-spring:2.0.4'
	compile 'org.mybatis:mybatis:3.5.4'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
	//implementation 'org.springframework.boot:spring-boot-starter-security'
	//implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
	//compileOnly 'org.projectlombok:lombok:1.18.12'
	//annotationProcessor 'org.projectlombok:lombok:1.18.12'

}

 

3. 이는 MemberMapper.xml에 문제가 있었다. 이때 변수명에서 오류가 있어서 문제가 발생했는데, <sql>~</sql>안에 들어가는 변수는 만들어진 데이터베이스의 테이블 상의 변수명을 입력해주어야하고, <insert>~</insert>안에 들어가는 변수는 MemberDTO에서 만들어놓은 우리가 직접 입력하는 변수명을 적어주어야해서 다르다. 이를 '같은 값이니 당연히 같게 쓰겠지?' 생각하고 같게 썼더니 제대로 저장되지 않았다. 

//MemberMapper.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="toyproject.toyproject.mapper.MemberRepository">
    <sql id="memberColumns">
        mid, mname, mpasswd
    </sql>
    <insert id="insertMember" parameterType="toyproject.toyproject.domain.MemberDTO">
        INSERT INTO member (
        <include refid="memberColumns"></include>
        ) VALUES (
        #{userID}, #{userName}, #{userPW}
        )
    </insert>
    <select id="getMember" resultType="java.util.HashMap">
        SELECT * FROM member;
    </select>

 

위와 같은 오류들을 통해서 두개의 테이블에 각각 데이터가 들어가는 것은 성공했다. 하지만, 회원저장(회원가입) 후 로그인은 구현하지 못해서 결론적으로 두개의 테이블의 연결하여 회원과 글을 연결하는 것은 실패했다. 다음에 하는 프로젝트에는 CRUD말고 Spring Security를 이용하여 로그인/회원가입과 데이터베이스 join을 구현하고 싶다.

 

회원 가입 성공

 

 

Web3팀의 가장 마지막 포스팅이라 프로젝트 결과물을 한장 요약한 이미지와 깃허브 주소를 첨부하며 작성을 마무리하도록 하겠다. 

github.com/hyeju1123/webStudy3-toyProject

 

hyeju1123/webStudy3-toyProject

웹스터디 3팀 토이프로젝트. Contribute to hyeju1123/webStudy3-toyProject development by creating an account on GitHub.

github.com