본 포스팅에서는 가독성을 위해 코드를 캡쳐해 업로드 했으며, 사용된 코드는 EunsilSon/spring-security-form-login에서 확인할 수 있습니다.
개발 환경
Spring Boot 3.2.6 / Spring Security 6 / JDK17 / MySQL / Mustache
추천 레퍼런스
본 포스팅은 메타코딩 - Springboot - 시큐리티 특강을 참고하여 작성했으며, 해당 강의를 ❗강력 추천❗합니다. 강의에 나온 코드와 약 80% 동일하며, 다른 방식으로 구현한 코드가 조금 있지만 동일한 결과를 출력하는 코드입니다.
추가로, 망나니개발자 - [SpringBoot] Spring Security란?을 읽고 Spring Security의 전체적인 실행 흐름을 파악할 수 있어 실제 구현할 때 이해하는데 많은 도움이 되었습니다.
폼 로그인에서 사용되는 주요 클래스와 전체적인 흐름은 아래와 같습니다.
IndexController
: 페이지 이동 및 회원가입 진행
데이터베이스에 User 정보 저장
PrincipalDetailService
: Security가 대신 로그인을 진행
PrincipalDetails
: Security Session 등록
IndexController
: 로그인 성공 후 index.html 리다이렉트
┌── config/
│ ├── auth/
│ │ ├── PrincipalDetailService
│ │ └── PrincipalDetails
│ ├── SecurityConfig
│ └── WebMvcConfig
├── controller/
│ └── IndexController
├── model/
│ └── User
└── repository/
└── UserRepository
implementation 'org.springframework.boot:spring-boot-starter-security'
Spring Security를 추가하고 바로 애플리케이션을 실행하면 콘솔창에 패스워드가 하나 출력됩니다.
스프링 시큐리티는 기본적으로 모든 요청에 대해 인증을 요구하기 때문에 인증되지 않은 사용자가 접근 시 ‘/login’ 경로로 리다이렉트 됩니다.
아래와 같이 폼에 입력하면 로그인이 가능합니다.
Username: user
Password: 콘솔창에 뜬 패스워드
로그인 성공 후 리다이렉션 경로를 설정해주지 않아 404 에러가 발생합니다.
Mustache는 스프링 부트에서 공식적으로 지원하는 템플릿 엔진입니다.
implementation 'org.springframework.boot:spring-boot-starter-mustache'
템플릿의 환경을 설정해줍니다.
회원가입과 로그인 과정에서 필요한 JPA 메서드를 설정합니다.
existsBy
: 엔티티 존재 여부 확인findBy
: 엔티티 추출로그인 성공 시 이동하게 되는 인덱스 페이지입니다.
로그인 페이지를 직접 만들어 줍니다. 아래에 회원가입 페이지로 연결되는 링크를 작성합니다.
회원가입 페이지에서 사용자가 입력한 정보들이 IndexController의 ‘/join’ 경로에 post 메서드로 전달됩니다.
Mustache의 기본 폴더 경로는 ‘src/main/resources’ 이므로, 경로를 임의로 변경하지 않는다면 html 파일의 이름만 반환해도 페이지 이동이 가능합니다.
회원가입 로직은 Service로 따로 빼는 것이 좋지만, 몇 줄 없어서 Controller에 적었습니다.
회원가입 후, 로그인 페이지로 리다이렉트 합니다.
bCryptPasswordEncoder.encode
: 전달 받은 패스워드를 암호화하여 데이터베이스에 저장합니다. 시큐리티 설정을 본인의 프로젝트 상황에 맞게 설정합니다.
@EnableWebSecurity
: Spring Filter가 Spring Filter Chain에 등록BCryptPasswordEncoder
: 패스워드 암호화에 사용될 구현체를 지정합니다.authorizeHttpRequests
: 인증이 필요한 URL과 접근을 허용할 URL을 지정합니다.formLogin
: 폼 로그인을 위한 여러 설정loginPage
: 기본 URL은 ‘/login’ 이지만, 다른 url을 사용하고 싶을 때 지정합니다.loginProcessingUrl
: 개발자가 컨트롤러에 로그인 요청을 받을 메서드를 작성하지 않아도 security가 대신 로그인을 진행합니다.defaultSuccessUrl
: 로그인 성공 후 이동할 주소를 지정합니다./login 주소 요청이 왔을 때 security가 낚아채서 로그인을 진행하는 클래스입니다.
SecurityConfig에 작성한 loginProcessingUrl(”/login”)
을 통해 /login 요청이 오면 Spring Security가 자동으로 UserDetailsService
타입으로 IoC 되어 있는 loadByUsername
함수를 실행합니다.
username과 같은 엔티티가 있다면 해당 엔티티를 꺼내 반환하고, 없다면 null을 반환합니다.
로그인에 성공한뒤 시큐리티 session을 만드는 클래스입니다.
Security Session
Security Context Holder
: 보안 주체에 대한 세부 정보Security Context
: Authentication 타입의 객체를 보관하며, 객체를 꺼낼 수 있음Authentication
: 현재 접근하는 주체의 정보와 권한을 UserDeatils 타입으로 가짐
Security Context Holder로 Security Context에 접근하고, Security Context를 통해 Authentication에 접근해서 객체를 꺼내는데, 그 객체의 타입이 결국 UserDetails이다.
그래서 Spring Security에 세션을 저장하기 위해 UserDeatils를 구현한 클래스를 만든다…!!!
모든 구현이 끝났습니다! 전체적인 실행 흐름을 콘솔로 출력한 결과입니다.
데이터베이스에도 사용자 정보가 잘 저장되었으며, 패스워드는 암호화 된 상태로 저장됩니다.
Security를 이용해 로그인을 구현한 포스팅이 많은데, 따라하다가도 전체 코드를 올린 경우가 드물고, 코드 한 줄 마다 왜 사용 했고, 어떻게 흐름이 이어지는지 알기가 어려워 security가 이렇게 어려운 건가 주눅 들었습니다...
그렇게 한동안 헤매다가 좋은 글과 강의를 발견해 드디어 이해할 수 있게 되었습니다!
본 포스팅에서 사용한 코드를 깃허브에 올려놨으니 많은 분들께 도움이 되었으면 좋겠습니다 😄