시큐리티 설정, 로그인/로그아웃, 회원가입 구현
시큐리티 설정하기
WebSecurityConfig.java
// 시큐리티 설정하기
@RequiredArgsConstructor
@Configuration
public class WebSecurityConfig {
private final UserDetailService userService;
// 1. 스프링 시큐리티 기능 비활성화
@Bean
public WebSecurityCustomizer configure() {
return (web) -> web.ignoring()
.requestMatchers(toH2Console())
.requestMatchers("/static/**");
}
// 2. 특정 HTTP 요청에 대한 웹 기반 보안 구성
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests() //3. 인증, 인가 설정
.requestMatchers("/login", "/signup", "/user").permitAll()
.anyRequest().authenticated()
.and()
.formLogin() //4. 폼 기반 로그인 설정
.loginPage("/login")
.defaultSuccessUrl("/articles")
.and()
.logout() //5. 로그아웃 설정
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.and()
.csrf().disable() //6.csrf 비활성화
.build();
}
// 7. 인증 관리자 관련 설정
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userService)
.passwordEncoder(bCryptPasswordEncoder)
.and()
.build();
}
//8. 패스워드 인코더로 사용할 빈 등록
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
자세한 설명
➊은 스프링 시큐리티의 모든 기능을 사용하지 않게 설정하는 코드.
즉,인증,인가 서비스를 모든 곳에 모두 적용하지 않는다.
- 일반적으로 정적 리소스(이미지,HTML 파일)에 설정한다.
- 정적 리소스만 스프링 시큐리티 사용을 비활성화하는 데 static 하위 경로에 있는 리소스와
h2의 데이터를 확인하는 데 사용하는 h2-console 하위 url을 대상으로 ignoring() 메서드를 사용한다.
➋는 특정 HTTP 요청에 대해 웹 기반 보안을 구성한다.
- 이 메서드에서 인증/인가 및 로그인,로그아웃 관련 설정할 수 있다.
➌은 특정 경로에 대한 액세스 설정을 한다.
• requestMatchers() : 특정 요청과 일치하는 url에 대한 액세스를 설정한다.
• permitAll() : 누구나 접근이 가능하게 설정한다. 즉, “/login”, “/signup”, “/user”로 요청이 오면 인증/인가 없이도 접근할 수 있다.
• anyRequest(): 위에서 설정한 url 이외의 요청에 대해서 설정.
• authenticated(): 별도의 인가는 필요하지 않지만, 인증이 접근할 수 있다.
➍는 폼 기반 로그인 설정을 한다.
• loginPage(): 로그인 페이지 경로를 설정한다.
• defaultSuccessUrl() : 로그인이 완료되었을 때 이동할 경로를 설정한다.
➎ 로그아웃 설정은 다음과 같다.
• logoutSuccessUrl() : 로그아웃이 완료되었을 때 이동할 경로를 설정.
• invalidateHttpSession() : 로그아웃 이후에 세션을 전체를 삭제할지 여부를 설정.
➏ CSRF 설정을 비활성화한다.
CSRF 공격을 방지하기 위해서는 활성화하는 게 좋지만 실습을 편리하게 하기 위해 비활성화해 두었다. 여기서는.
➐ 인증 관리자 관련 설정이다.
사용자 정보를 가져올 서비스를 재정의하거나, 인증 방법, 예를 들어 LDAP, JDBC 기반 인증 등을 설정할 때 사용합니다.
➑ 사용자 서비스를 설정합니다.
• userDetailsService() : 사용자 정보를 가져올 서비스를 서비스를 설정한다.
이 때 설정하는 서비스 클래스는 반드시 UserDetailsServiceS 상속받은 클래스여야 한다.
• passwordEncoder(): 비밀번호를 암호화하기 위한 인코더를 설정합니다.
➒ 패스워드 인코더를 빈으로 등록합니다.
1. 회원 가입 구현하기
1-1. 서비스 코드 구현
1) DTO 설정. AddUserRequest.java
@Getter
@Setter
public class AddUserRequest {
private String email;
private String password;
}
2) UserService 에 save() 메서드 구현
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
public Long save(AddUserRequest dto) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// UserRepository를 통해 새 사용자를 저장.
// - AddUserRequest에서 전달된 이메일과 비밀번호를 사용하여 새 사용자를 생성.
// - 비밀번호는 BCryptPasswordEncoder를 사용하여 안전하게 해싱됨
return userRepository.save(User.builder()
.email(dto.getEmail())
.password(encoder.encode(dto.getPassword()))
.build()).getId(); // 저장된 사용자의 ID를 반환
}
/*
이걸 리펙토링하면
public Long save(AddUserRequest dto) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
User user = userRepository.save(User.builder()
.email(dto.getEmail())
.password(encoder.encode(dto.getPassword())) // 비밀번호 해싱
.build());
return user.getId();
} 이렇게도 가능
*/
1-2. 컨트롤러 코드 구현
controller.UserApiController
@RequiredArgsConstructor
@Controller
public class UserApiController {
private final UserService userService;
@PostMapping("/user")
public String signup(AddUserRequest request) {
userService.save(request);
return "redirect:/login";
}
}
2. 회원 가입, 로그인 뷰 작성하기
2-1. 뷰 컨트롤러 구현
controller.UserviewController
@Controller
public class UserViewController {
@GetMapping("/login")
public String login() {
return "login";
}
}
2-2. 뷰 만들기
1) login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>로그인</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
<style>
.gradient-custom {
background: linear-gradient(to right, rgba(106, 17, 203, 1), rgba(37, 117, 252, 1))
}
</style>
</head>
<body class="gradient-custom">
<section class="d-flex vh-100">
<div class="container-fluid row justify-content-center align-content-center">
<div class="card bg-dark" style="border-radius: 1rem;">
<div class="card-body p-5 text-center">
<h2 class="text-white">LOGIN</h2>
<p class="text-white-50 mt-2 mb-5">서비스를 사용하려면 로그인을 해주세요!</p>
<div class = "mb-2">
<form action="/login" method="POST">
<input type="hidden" th:name="${_csrf?.parameterName}" th:value="${_csrf?.token}" />
<div class="mb-3">
<label class="form-label text-white">Email address</label>
<input type="email" class="form-control" name="username">
</div>
<div class="mb-3">
<label class="form-label text-white">Password</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<button type="button" class="btn btn-secondary mt-3" onclick="location.href='/signup'">회원가입</button>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
2) signup.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>회원 가입</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
<style>
.gradient-custom {
background: linear-gradient(to right, rgba(254, 238, 229, 1), rgba(229, 193, 197, 1))
}
</style>
</head>
<body class="gradient-custom">
<section class="d-flex vh-100">
<div class="container-fluid row justify-content-center align-content-center">
<div class="card bg-dark" style="border-radius: 1rem;">
<div class="card-body p-5 text-center">
<h2 class="text-white">SIGN UP</h2>
<p class="text-white-50 mt-2 mb-5">서비스 사용을 위한 회원 가입</p>
<div class = "mb-2">
<form th:action="@{/user}" method="POST">
<!-- 토큰을 추가하여 CSRF 공격 방지 -->
<input type="hidden" th:name="${_csrf?.parameterName}" th:value="${_csrf?.token}" />
<div class="mb-3">
<label class="form-label text-white">Email address</label>
<input type="email" class="form-control" name="email">
</div>
<div class="mb-3">
<label class="form-label text-white">Password</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
3. 로그아웃 구현하기
3-1. 컨트롤러 코드 추가
UserApiController.java
@RequiredArgsConstructor
@Controller
public class UserApiController {
private final UserService userService;
@PostMapping("/user")
public String signup(AddUserRequest request) {
userService.save(request);
return "redirect:/login";
}
//추가
@GetMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
// SecurityContextLogoutHandler를 사용하여 사용자 로그아웃 처리를 수행
new SecurityContextLogoutHandler()
.logout(request, response, SecurityContextHolder.getContext().getAuthentication());
// 로그아웃 처리 후, "/login" 경로로 리디렉션
return "redirect:/login";
}
}
3-2. 로그아웃 뷰 추가
articleList.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>블로그 글 목록</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
</head>
<body>
<div class="p-5 mb-5 text-center</> bg-light">
<h1 class="mb-3">My Blog</h1>
<h4 class="mb-3">블로그에 오신 것을 환영합니다.</h4>
</div>
<div class="container">
<button type="button" id="create-btn"
th:onclick="|location.href='@{/new-article}'|"
class="btn btn-secondary btn-sm mb-3">글 등록</button>
<div class="row-6" th:each="item : ${articles}">
<div class="card">
<div class="card-header" th:text="${item.id}">
</div>
<div class="card-body">
<h5 class="card-title" th:text="${item.title}"></h5>
<p class="card-text" th:text="${item.content}"></p>
<a th:href="@{/articles/{id}(id=${item.id})}" class="btn btn-primary">보러가기</a>
</div>
</div>
<br>
</div>
<!-- 추가 -->
<button type="button" class="btn btn-secondary" onclick="location.href='/logout'">로그아웃</button>
</div>
<script src="/js/article.js"></script>
</body>
4. 실행 테스트하기
4-1. 환경 변수 설정하기
application.yml - 데이터베이스 연결 정보 추가
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
defer-datasource-initialization: true
// 데이터베이스 연결 정보 추가
datasource: // 데이터베이스 정보 추가
url: jdbc:h2:mem:testdb
username: sa
h2: // H2 콘솔 활성화
console:
enabled: true
4-2. 실제 실행 테스트하기
http://localhost:8080 만 쳐도 /login 으로 연결
회원가입 버튼을 누르면 화면 전환
회원가입 데이터 입력하고 가입 처리하면
로그인 화면으로 리다이렉트
h2 콘솔 접속해서 확인해보면
데이터베이스에도 문제 없이 잘 값이 들어와 있음을 확인할 수 있음.
로그인 버튼 누르면 글 목록으로 이동
로그아웃 버튼을 누르면
다시 메인 화면으로 이동함을 확인할 수 있음.