▼ What ?
시험이 끝나고 오랜만에 진행한 GDSC 커리큘럼 6주차엔 웹 개발의 꽃이라고도 볼 수 있는 '회원가입'과 '로그인/로그아웃' 기능을 구현해보는 시간이다. (벌써 로그인을 구현해볼 차례라니..) 그리고 이후에 과제로 진행된 부분은 '글쓴이 표시'와 '수정/삭제 기능' 을 구현하는 것이다. 질문과 답변을 저장할 때 작성자 아이디도 함께 저장되도록 하여 글쓴이가 표시될 수 있도록 하고, 작성한 질문과 답변을 수정하거나 삭제할 수 있는 기능을 추가해보려고 한다.
▼ 회원가입
회원 정보 엔티티
- 질문, 답변을 엔티티를 생성했던 것처럼 회원 정보를 위한 엔티티를 생성해주는데, 회원 정보 엔티티에 필요한 속성(attribute)은 무엇이 있을까?
➜ 최소한 'username(사용자 ID)', 'password(비밀번호)', 'email(이메일)' 이 필요 ! - 회원은 질문, 답변 도메인에 해당하지 않으므로 'user' 라는 도메인이 따로 필요하다.
➜ [ com.gdsc.webboard.user ] 패키지를 새로 생성해주자. - 유저를 관리할 'SiteUser' 엔티티 클래스를 생성해주자.
( 스프링 시큐리티에 이미 'User' 라는 이름의 클래스가 있어서 패키지명이 다르긴 하지만 잘못된 패키지를 사용했을 때 발생하는 오류를 방지해주기 위해 'SiteUser' 라고 이름을 지었다. )
package com.gdsc.webboard.user;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Entity
public class SiteUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
@Column(unique = true)
private String email;
}
🔻 @Column(unique = true)
➜ 'unique = true' 는 중복되지 않는 유일한 값만 저장되도록 한다.
➜ username과 email에는 동일한 값이 저장 X
➜ 아래처럼 데이터베이스 테이블에 'UK_' 로 시작하는 Indexes들이 생성된다.
User 리포지터리 & 서비스
- User 엔티티를 생성해줬으니, User 리포지터리와 서비스도 만들어줘야 한다.
- User 리포지터리
➜ SiteUser 엔티티의 기본키(PK)의 타입은 'Long' 이므로 지네릭을 아래처럼 선언해줬다.
package com.gdsc.webboard.user;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<SiteUser, Long> {
}
- User 서비스
➜ User 리포지터리를 이용하여 User 데이터를 생성하는 'create' 메서드를 추가했다.
➜ 이때 유저의 비밀번호를 암호화하여 저장하기 위해 'BCryptPasswordEncoder' 클래스를 사용 !
package com.gdsc.webboard.user;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
public SiteUser create(String username, String email, String password) {
SiteUser user = new SiteUser();
user.setUsername(username);
user.setEmail(email);
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
user.setPassword(passwordEncoder.encode(password));
this.userRepository.save(user);
return user;
}
}
🔻BCryptPasswordEncoder 클래스
➜ BCrypt 해싱 함수를 사용해서 비밀번호를 암호화하는 방식.
➜ 위 코드처럼 직접 객체를 생성(new)하는 방식은 바람직하지 못한 경우가 많다.
➜ 이후에 암호화 방식을 변경하면 이 'BCryptPasswordEncoder' 클래스를 사용한 코드를 모두 찾아 수정하므로, 'PasswordEncoder' 를 빈(bean)으로 등록해서 사용하는 것이 좋다.
( PasswordEncoder는 BCryptPasswordEndcoder 클래스의 인터페이스이다. )
- PasswordEncoder 빈(bean)을 생성해주자.
➜ @Configuration이 적용된 SecruityConfig 클래스에 @Bean 메서드를 생성하는 방법 이용.
...
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
...
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- UserService 클래스도 수정해주자.
➜ BCryptPasswordEncoder 객체를 직접 생성하여 사용하지 않고, 빈(bean)으로 등록한 PasswordEncoder 객체를 주입받아 BCryptPasswordEncoder 클래스의 encoder 메서드를 사용하도록 한다.
package com.gdsc.webboard.user;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public SiteUser create(String username, String email, String password) {
SiteUser user = new SiteUser();
user.setUsername(username);
user.setEmail(email);
user.setPassword(passwordEncoder.encode(password));
this.userRepository.save(user);
return user;
}
}
회원가입 폼
- 회원가입을 위한 폼(형식) 클래스를 생성해주자.
- @Size ➜ 폼 유효성 검증시 문자열의 길이를 체크해주는 애너테이션.
- @Email ➜ 해당 속성의 값이 이메일 형식과 일치하는지 검증해주는 애너테이션.
- @Size ➜ 폼 유효성 검증시 문자열의 길이를 체크해주는 애너테이션.
package com.gdsc.webboard.user;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserCreateForm {
@Size(min = 3, max = 25)
@NotEmpty(message = "사용자ID는 필수항목입니다.")
private String username;
@NotEmpty(message = "비밀번호는 필수항목입니다.")
private String password1;
@NotEmpty(message = "비밀번호 확인은 필수항목입니다.")
private String password2;
@NotEmpty(message = "이메일은 필수항목입니다.")
@Email
private String email;
}
회원가입 컨트롤러
- 유저 엔티티, 서비스, 폼 모두 생성했으니, 이제 유저 컨트롤러를 생성할 차례이다.
➜ "/user/signup" URL이 GET으로 요청되면 회원가입을 위한 템플릿을 렌더링하고 POST로 요청되면 회원가입이 진행되도록 한다.
( @Valid 애너테이션을 이용해서 userCreateForm에 대한 검증(UserCrateForm 클래스 내에 정의된 사항)을 수행한다. )
➜ 회원가입이 정상적으로 완료되면 메인 페이지로 리다이렉트되도록 한다.
( return "redirect:/"; )
package com.gdsc.webboard.user;
import jakarta.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Controller
@RequestMapping("/user")
public class UserController {
private final UserService userService;
@GetMapping("/signup")
public String signup(UserCreateForm userCreateForm) {
return "signup_form";
}
@PostMapping("/signup")
public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "signup_form";
}
if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
bindingResult.rejectValue("password2", "passwordInCorrect",
"2개의 패스워드가 일치하지 않습니다.");
return "signup_form";
}
userService.create(userCreateForm.getUsername(),
userCreateForm.getEmail(), userCreateForm.getPassword1());
return "redirect:/";
}
}
🔻 bindingResult.rejectValue(필드명, 오류코드, 에러메시지)
➜ 'password1' 과 'password2' 가 일치하는지 검증에 실패했을 경우에 실행
➜ 해당 필드에 대한 에러 정보(오류 메시지, 오류코드)를 전달하기 위해 사용하는 메서드이다.
( 대형 프로젝트에선 체계적인 관리를 위해 오류코드를 잘 정의해서 사용해야 한다. )
회원가입 템플릿
- 컨트롤러까지 생성해줬으니 전달해줄 회원가입 템플릿도 작성해줘야 한다.
[ /web-board/src/main/resources/templates/signup_form.html ]
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
<div class="my-3 border-bottom">
<div>
<h4>회원가입</h4>
</div>
</div>
<form th:action="@{/user/signup}" th:object="${userCreateForm}" method="post">
<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<div class="mb-3">
<label for="username" class="form-label">사용자ID</label>
<input type="text" th:field="*{username}" class="form-control">
</div>
<div class="mb-3">
<label for="password1" class="form-label">비밀번호</label>
<input type="password" th:field="*{password1}" class="form-control">
</div>
<div class="mb-3">
<label for="password2" class="form-label">비밀번호 확인</label>
<input type="password" th:field="*{password2}" class="form-control">
</div>
<div class="mb-3">
<label for="email" class="form-label">이메일</label>
<input type="email" th:field="*{email}" class="form-control">
</div>
<button type="submit" class="btn btn-primary">회원가입</button>
</form>
</div>
</html>
네비게이션 바에 회원가입 링크 추가하기
- 화원가입 화면으로 이동하는 링크를 네비게이션 바에서 로그인 버튼 옆에 추가하기 위해 navbar 템플릿에 아래 코드를 추가로 작성해주자.
[ .../resources/templates/navbar.html]
<li class="nav-item">
<a class="nav-link" th:href="@{/user/signup}">회원가입</a>
</li>
회원가입 후 데이터 확인해보기
- H2 console에서 회원 정보를 확인해볼 수 있다.
중복 회원가입
- 이미 가입한 유저와 동일한 사용자ID 또는 이메일 주소로 회원가입을 진행하면, 'unique=true' 옵션으로 인해 위와 같은 에러 페이지가 나타난다.
➜ 500 에러 페이지가 나타나지 그대로 나타나지 않도록, UserController 클래스의 signup 메서드에 회원가입시 발생하는 오류를 처리하는 코드를 추가해주자.
package com.gdsc.webboard.user;
...
import org.springframework.dao.DataIntegrityViolationException;
...
public class UserController {
...
@PostMapping("/signup")
public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "signup_form";
}
if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
bindingResult.rejectValue("password2", "passwordInCorrect",
"2개의 패스워드가 일치하지 않습니다.");
return "signup_form";
}
try {
userService.create(userCreateForm.getUsername(),
userCreateForm.getEmail(), userCreateForm.getPassword1());
}catch(DataIntegrityViolationException e) {
e.printStackTrace();
bindingResult.reject("signupFailed", "이미 등록된 사용자입니다.");
return "signup_form";
}catch(Exception e) {
e.printStackTrace();
bindingResult.reject("signupFailed", e.getMessage());
return "signup_form";
}
return "redirect:/";
}
}
🔻 사용자ID 또는 이메일 주소가 동일할 경우엔 'DataIntegrityViolationException' 가 발생하기 때문에, 이 예외를 위와 같이 처리해주면 더이상 500 에러 페이지가 나타나지 않는다.
▼ 로그인 / 로그아웃
로그인 구현하기
- '스프링 시큐리티' 는 사용자ID와 비밀번호로 로그인을 하는데 거쳐야 할 복잡한 과정을 쉽게 진행할 수 있도록 도와준다.
- 일단 스프링 시큐리티에 로그인 URL을 등록해주자 !
...
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
.csrf((csrf) -> csrf
.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
.headers((headers) -> headers
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
.formLogin((formLogin) -> formLogin // 로그인 URL 등록
.loginPage("/user/login")
.defaultSuccessUrl("/"))
;
return http.build();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
🔻 formLogin 메서드
➜ 스프링 시큐리티의 로그인 설정을 담당한다.
➜ 로그인 URL : "/user/login"
➜ 로그인 성공시에 이동하는 디폴트 페이지 : "/" (루트 URL)
- 로그인 URL을 "/user/login" 으로 등록해줬으니 이제 이 URL에 대한 매핑을 할 수 있도록 User 컨트롤러를 수정해주자.
➜ 실제 로그인을 진행하는 @PostMapping 방식의 메서드는 추가하지 않는 이유는 ?
➜ 스프링 시큐리티가 대신 처리하므로 구현할 필요가 없기 때문에 !
...
public class UserController {
...
@GetMapping("/login")
public String login() {
return "login_form";
}
}
- 이제 로그인 페이지를 나타내기 위한 로그인 템플릿을 생성해줘야 한다.
➜ 스프링 시큐리티는 로그인에 실패한 경우엔 로그인 페이지로 리다이렉트 되게 하며, 페이지 파라미터로 error를 함께 전달한다.
➜ 따라서, 페이지 파라미터로 error가 전달된 경우 "사용자ID 또는 비밀번호를 확인해 주세요." 라는 오류메시지가 출력되도록 작성해주었다.
[ /web-board/src/main/resources/templates/login_form.html ]
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
<form th:action="@{/user/login}" method="post">
<div th:if="${param.error}">
<div class="alert alert-danger">
사용자ID 또는 비밀번호를 확인해 주세요.
</div>
</div>
<div class="mb-3">
<label for="username" class="form-label">사용자ID</label>
<input type="text" name="username" id="username" class="form-control">
</div>
<div class="mb-3">
<label for="password" class="form-label">비밀번호</label>
<input type="password" name="password" id="password" class="form-control">
</div>
<button type="submit" class="btn btn-primary">로그인</button>
</form>
</div>
</html>
- 아직은 로그인을 할 수 없다.
➜ 스프링 시큐리티에 로그인 기준을 설정해주지 않았기 때문에 !
스프링 시큐리티를 통해 로그인을 수행하는 방법
(1) 시큐리티 설정 파일에 직접 아이디, 비밀번호를 등록하여 인증을 처리하는 메모리 방식
(2) 데이터베이스에서 회원 정보를 조회하는 방식
➜ 이전에 회원가입을 통해 회원 정보를 데이터베이스에 저장했으므로 이 방식을 채택하는 것이다. - 그럼 이제 해야할 일은 ?
➜ 데이터베이스에서 사용자를 조회하는 서비스 클래스(UserSecurityService)를 생성하고 그 서비스를 스프링 시큐리티에 등록해주면 된다.
➜ 하지만, 그전에 먼저 서비스에서 필요로 하는 리포지터리(UserRepository)와 'UserRole' 라는 클래스도 만들어줘야 한다.
- UserRepository
➜ 'UserSecurityService' 는 데이터베이스에서 유저를 조회하는 기능이 필요하므로 'findByusername' 메서드를 추가해줘야 한다.
package com.gdsc.webboard.user;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<SiteUser, Long> {
Optional<SiteUser> findByusername(String username);
}
- UserRole
➜ '스프링 시큐리티' 는 인증 뿐만 아니라 권한도 관리하기 때문에, 인증 후에 사용자에게 부여할 권한도 필요하다 !
package com.gdsc.webboard.user
import lombok.Getter;
@Getter
public enum UserRole {
ADMIN("ROLE_ADMIN"),
USER("ROLE_USER");
UserRole(String value) {
this.value = value;
}
private String value;
}
🔻 'enum' 으로 작성해줬기 때문에 불변한 상수 자료형이므로 @Setter없이 @Getter만 사용 가능하도록 한다.
enum (참고 자료) - [Software Design Pattern] 싱글톤 패턴 (Singleton Pattern) — Uykm_Note (tistory.com)
[Software Design Pattern] 싱글톤 패턴 (Singleton Pattern)
▼ Why ? 자바 스터디를 진행할 때 '객체지향 프로그래밍' 챕터에서 생성자를 공부하다 '싱글톤 패턴' 에 대해선 가벼운 정도로만 공부하고 넘어갔었던 적이 다. 그런데, 이번에 스프링(Spring)에서
ukym-tistory.tistory.com
- UserSecurityService
➜ 스프링 시큐리티 설정에 등록할 서비스 클래스를 생성한다.
➜ 스프링 시큐리티가 제공하는 'UserDetailsServe' 인터페이스를 구현(implements)해줘야 한다.- 사용자명으로 유저 엔티티 클래스인 SiteUser 객체를 조회한다.
- 그 사용자명에 해당하는 데이터가 없을 경우 'UsernameNotFoundException' 에러를 발생시킨다.
- 조회된 사용자의 사용자명(username)이 "admin" 인 경우엔 ADMIN 권한을 부여하고 그 이외의 경우엔 USER 권한을 부여한다.
- 사용자명 'siteUser.getUsername()', 패스워드 'siteUser.getPassword()', 권한 'authorities' 을 인자값으로 전달하고 스프링 시큐리티의 User 클래스 객체를 생성하여 리턴한다.
➜ 스프링 시큐리티는 이렇게 'loadUserByUsername' 메서드에 의해 리턴된 User 객체의 패스워드가 화면으로부터 입력 받은 비밀번호와 일치하는지를 검사하는 로직을 내부적으로 수행한다.
package com.gdsc.webboard.user;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class UserSecurityService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<SiteUser> _siteUser = this.userRepository.findByusername(username);
if (_siteUser.isEmpty()) {
throw new UsernameNotFoundException("사용자를 찾을수 없습니다.");
}
SiteUser siteUser = _siteUser.get();
List<GrantedAuthority> authorities = new ArrayList<>();
if ("admin".equals(username)) {
authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.getValue()));
} else {
authorities.add(new SimpleGrantedAuthority(UserRole.USER.getValue()));
}
return new User(siteUser.getUsername(), siteUser.getPassword(), authorities);
}
}
🔻 loadUserByUsername 메서드
➜ 사용자명으로 비밀번호를 조회하여 리턴하는 메서드이다.
( 스프링 시큐리티가 구현하도록 강제한다. )
- SecurityConfig 클래스에 시큐리티의 인증을 담당하는 AuthenticationManager 객체를 빈(bean)으로 등록하는 코드를 추가한다.
...
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
...
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
🔻 AutehnticationManager
➜ 유저 인증시 앞서 작성해둔 UserSecurityService 클래스와 PasswordEncoder 클래스를 사용한다.
- 이제 로그인 페이지에 들어갈 수 있도록 로그인 링크를 네비게이션 바에 추가해주면 된다.
[ /web-board/src/main/resources/templates/navbar.html ]
<nav th:fragment="navbarFragment" class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="/">SBB</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" th:href="@{/user/login}">로그인</a> <!-- 로그인 링크 버튼 -->
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/user/signup}">회원가입</a>
</li>
</ul>
</div>
</div>
</nav>
- 데이터베이스에 없는 username 또는 잘못된 password를 입력시 다음과 같은 오류 메시지가 나타나고(스프링 시큐리티 ➜ login_form),
로그인이 정상 수행되면 메인 화면으로 이동한다(formLogin).
- 로그인 후에도 네비게이션 바에 여전히 "로그인" 링크가 남아있다.
➜ 로그인을 한 상태에선 해당 링크가 "로그아웃" 링크로 바뀌게 navbar 템플릿을 수정해주자.
➜ 로그인을 안한 상태면 'sec:authorize="isAnonymous()' 가 참(true), 로그인을 한 상태면 ' sec:authorize="isAuthenticated()' 가 참(true).
[ /web-board/src/main/resources/templates/navbar.html ]
<nav th:fragment="navbarFragment" class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="/">SBB</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" sec:authorize="isAnonymous()" th:href="@{/user/login}">로그인</a>
<a class="nav-link" sec:authorize="isAuthenticated()" th:href="@{/user/logout}">로그아웃</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/user/signup}">회원가입</a>
</li>
</ul>
</div>
</div>
</nav>
🔻 "로그아웃" 링크는 "/user/logout" URL로 설정해두었다 !
로그아웃 구현하기
- 앞서 생성한 "로그아웃" 링크를 클릭하면, 로그아웃 기능을 구현하지 않았기 때문에 404 에러 페이지가 나타난다.
➜ "로그인" 을 구현할 때처럼 '스프링 시큐리티' 를 사용하여 쉽게 구현 가능하다 !
- SecurityConfig 클래스만 수정해주면 된다.
➜ 로그아웃 URL을 "/user/logout" 으로 설정해주고, 로그아웃이 성공하면 메인 페이지(루트 URL "/")로 이동하도록 한다.
➜ 추가적으로 로그아웃시에 생성된 사용자 세션도 삭제되도록 했다.
...
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll())
.csrf((csrf) -> csrf
.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
.headers((headers) -> headers
.addHeaderWriter(new XFrameOptionsHeaderWriter(
XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
.formLogin((formLogin) -> formLogin
.loginPage("/user/login")
.defaultSuccessUrl("/"))
.logout((logout) -> logout // 로그아웃을 위한 시큐리티 설정
.logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true))
;
return http.build();
}
}
🔻 logoutRequestMatcher(new AntPathRequestMatcher())
➜ 로그아웃을 실행할 URL을 설정하는 메서드.
🔻 logoutSuccessUrl()
➜ 로그아웃시 입력 받은 URL로 이동하도록 하는 메서드.
🔻 invalidateHttpSession(true) & deleteCookie("JSESSIONID")
➜ 세션과 쿠키를 지우는 메서드.