스프링 부트로 프로젝트를 생성하면 기본적으로 제공되는 디렉터리나 파일들이 있는데, 이 디렉터리와 파일들은 정해진 컨벤션을 따르고 있기 때문에 마음대로 변경할 수 없다. 따라서 프로젝트를 시작하기 전에 각 디렉터리와 파일의 위치와 의미를 이해하는 것은 매우 중요하다.
스프링 부트로 만든 프로젝트는 기본적으로 메이븐이 제공하는 프로젝트 구조를 따른다. 따라서 각 세 개의 소스 폴더가 제공되는데 src/main/java에는 일반적은 자바 소스를 등록하고 src/main/resources에는 자바 소스가 아닌 XML이나 프로퍼티 파일을 등록한다. 그리고 JUnit 기반의 테스트 케이스는 src/test/java에 작성한다.
static 폴더는 이름 그대로 HTML 같은 정적인 웹리소스가 저장된다. 그리고 templates 폴더에는 타임리프 같은 템플릿 기반의 웹리소스가 저장된다. 그리고 마지막으로 application.properties파일이 있는데 이 파일에는 프로젝트 전체에서 사용할 프로퍼티 정보들을 저장한다.
The Tomcat connector configured to listen on port 8080 failed to start. The port mat already action:
현재 8080 포트로 구동 중인 톰캣 서버가 있는데, 또다시 톰캣 서버를 8080 포트로 구동하려 했기 때문에 에러가 발생했다.
이때는 콘솔에서 빨간색 정지 버튼을 클릭하여 이미 구동 중인 톰캣 서버를 중지해야 한다. 그리고 다시 Application을 실행하면 정상적으로 실행 결과를 확인할 수 있다.
메인 클래스에 있는 @SpringBootApplication은 @EnableAutoConfiguration과 @ComponentScan을 포함하고 있다. 스프링 부트는 @ComponentScan을 먼저 처리하여 사용자가 등록한 빈을 먼저 메모리에 올린다. 그리고 나중에 @EnableAutoConfiguration을 실행하여 자동설정에 의한 빈 등록을 처리한다. 따라서 우리가 생성한 빈을 자동 설정으로 생성한 빈이 덮어써버린 것이다. 이 문제를 해결하는 가장 쉽고 편한 방법은 자동 설정 클래스에서 @Conditional 어노테이션을 사용하여 조건에 따라 빈 등록을 처리하는 것이다. @ConditionalOnMissingBean은 등록하려는 빈이 메모리에 없는 경우에만 현재의 빈 등록을 처리하도록 한다. 따라서 사용자가 정의한 JDBCConnectionManager빈이 @ComponentScan 설정에 의해 먼저 등록된다면 자동설정인 @EnableAutoConfiguration이 동작하는 시점에는 이미 등록된 빈을 사용하고 새롭게 빈을 생성하지 않는다. 수정된 파일을 저장하고 board-spring-boot-starter 프로젝트를 다시 install한다.
가장 먼저 스프링에서 제공하는 SpringRunner를 사용하기 위해서 @RunWith를 설정했다. 이 설정을 해야만 스프링 부트 기반의 테스트 케이스에서 가장 중요한 @SpringBootTest 어노테이션을 사용할 수 있다. 복잡한 테스트 설정들을 자동으로 처리하고, 테스트 관련 객체들도 메모리에 올리기 위해서 개발자가 할 일은 테스트 케이스 클래스에 @SpringBootTest를 헌선하는 것뿐이다.
테스트가 실행되기 전에 테스트에 사용할 프로퍼티들을 "key=value"형태로 추가하거나 proterties 파일에 설정된 프로퍼티를 재정의한다.
테스트할 클래스들을 등록한다. 만일 classes 속성을 생략하면 애플리케이션에 정의된 모든 빈을 생성한다.
애플리케이션이 실행될 때, 웹과 관련된 환경을 설정할 수 있다.
@SpringBootTest 속성 중에서 proterties 속성이 중요한데, 이 속성을 이용하면 외부(application,properties)에 설정된 프로퍼티 정보를 재정의하거나 새로운 프로퍼티를 등록하여 사용할 수 있다.
HttpSession을 이용하여 간단하게 구현.
그리고 이렇게 구현한 기능을 이후에 스프링 시큐리티로 변환
인덱스 페이지는 [글목록 바로가기]와 [로그인] 두 개의 링크만 제공한다. 이 중에서 [로그인] 링크를 클릭하면 브라우저는 GET방식으로 login 요청을 전달하게 된다.사용자가 추가된 인덱스 페이지를 보기 위해서는 애플리케이션을 재실행해야 한다. 그리고 index.html을 언급하지 않고 'http://localhost:'8080'까지만 요청하면 인덱스 페이지가 실행된다.
[로그인] 링크를 클릭했을 때 로그인 화면으로 이동할 수 있도록 LoginController를 작성
templates 폴더에 사용자에게 제공할 로그인 화면을 만든다. thymeleaf 사용
로그인 화면에서 [로그인] 버튼을 클릭했을 때 실질적인 사용자 인증을 처리할 메소드를 LoginController에 추가한다.
클래스 위에 추가된 @SessionAttributes어노테이션은 세션에 상태 정보를 저장할 때 사용하는데 뒤에("member")라고 설정했기 때문에 Model 객체 "member"라는 이름으로 저장된 데이터를 자동으로 세션에 등록한다.
@ControllerAdvice 어노테이션을 GlobalExceptionHandler 클래스에 선언함으로써 컨트롤러에서 발생하는 모든 예외를 GlobalExceptionHandler 객체가 처리하도록 한다. 그리고 발생된 예외의 타입에 따라 다양한 화면이 처리되도록 @ExceptionHandler를 가진 메소드를 여러 개 선언. handleCustomException() 메소드는 BoardException 타입의 예외가 발생했을 때 동작하는 메소드이다. 따라서 BoardException을 상속한 BoardNotFoundException이 발생했을 때 동작할 것이다.
만약 컨트롤러마다 별도의 예외 처리 로직을 구현하고 싶으면 컨트롤러에 @ExceptionHandler 어노테이션이 붙은 메소드를 추가로 작성하면 된다. ExceptionController클래스에 예외 처리 메소드를 추가한다.
애플리케이션에 등록된 모든 자원은 인증에 성공한 사용자만 접근할 수 있다. 프로젝트에 단지 시큐리티 스타터만 추가했을 뿐인데 말이다.
참고로 스프링 부트는 기본적으로 INFO 레벨 이상의 로그만 출력하도록 되어 있다. 따라서 시큐리티와 관련된 더 많은 로그 메시지를 확인하기 위해서는 로그 레벨을 DEBUG 레벨로 변경해야 한다. application.properties파일의 내용을 모두 복사하고 맨 아래에 시큐리티 관련 로그 설정을 추가한다.
# Security log level Setting
logging.level.org.springframework.security-debug
스프링 시큐리티는 시큐리티 필터들의 상호작용에 의해 처리된다고 했다. 그런데 스프링 시큐리티를 구성하는 필터 중에서 가장 중요한 필터는 UsernamePasswordAuthenticationFilter다. 이 UsernamePasswordAuthenticationFilter가 실제로 사용자가 입력한 인증 정보를 이용해서 인증을 처리해주기 때문이다.
그리고 두 번째로 중요한 객체가 FilterSecurityInterceptor인데, FilterSecurityInterceptor는 인증에 성공한 사용자가 해당 리소스에 접근할 권한이 있는지를 검증하기 때문이다.
사용자가 리소스를 요청하면 가장 먼저 인증 관리 필터가 사용자 요청을 가로챈다. 그리고 인증 관리 필터는 인증 처리와 관련된 필터들을 이용하여 사용자 인증을 처리하는데, 만약 인증되지 않은 사용자가 접근하면 인증에 필요한 정보를 요청한다. 그리고 사용자가 인증 절차(로그인)를 통해 인증 정보를 입력하면 파일이나 데이터베이스에 저장되어 있는 사용자 정보를 읽어 사용자가 입력한 인증 정보를 검증한다. 만약 인증에 실패하면 다시 인증 정보를 요청하고 인증을 통과한 걍우에만 사용자가 요청한 리소스로 요청을 전달한다.