충분히 쌓여가는
스프링 시큐리티를 사용한 인증, 인가 본문
의존성 추가
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
testImplementation 'org.springframework.security:spring-security-test'
}
엔티티 생성
컬럼명 | 자료형 | null 허용 | 키 | 설명 |
id | BIGINT | N | 기본키 | 일련번호, 기본키 |
VARCHAR(255) | N | 이메일 | ||
password | VARCHAR(255) | N | 패스워드 | |
create_at | DATETIME | N | 생성일자 | |
updated_at | DATETIME | N | 수정일자 |
src>main>java>com.enough.project에 domain 디렉터리 생성>User.java 생성
@Table(name = "users")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false)
private Long id;
@Column(name = "email", nullable = false, unique = true)
private String email;
@Column(name = "password", nullable = false)
private String password;
@Builder
public User(String email, String password, String auth) {
this.email = email;
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("user"));
}
@Override // 사용자의 id 반환
public String getUsername() {
return email;
}
@Override // 사용자의 패스워드 반환
public String getPassword() {
return password;
}
@Override // 계정 만료 여부 반환
public boolean isAccountNonExpired() {
return true;
}
@Override // 계정 잠금 여부 반환
public boolean isAccountNonLocked() {
return true;
}
@Override // 패스워드 만료 여부 반환
public boolean isCredentialsNonExpired() {
return true;
}
@Override // 게정 사용 가능 여부 반환
public boolean isEnabled() {
return true;
}
}
src>main>java>com.enough.project에 repository 디렉터리 생성>UserRepository.java 생성
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
src>main>java>com.enough.project에 service 디렉터리 생성> UserDetailService.java 생성
(로그인 진행 시 사용자 정보를 가져옴)
@RequiredArgsConstructor
@Service
public class UserDetailService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public User loadUserByUsername(String email) {
return userRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException((email)));
}
}
시큐리티 설정
src>main>java>com.enough.project에 config 패키지 생성>WebSecurityConfig.java 생성
@RequiredArgsConstructor
@Configuration
public class WebSecurityConfig {
private final UserDetailService userService;
@Bean
public WebSecurityCustomizer configure() {
return (web) -> web.ignoring()
.requestMatchers(toH2Console())
.requestMatchers("/static/**");
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
.requestMatchers("/login", "/signup", "/user").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/articles")
.and()
.logout()
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.and()
.csrf().disable()
.build();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userService)
.passwordEncoder(bCryptPasswordEncoder)
.and()
.build();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
회원가입 구현
서비스 메서드 코드 작성(사용자 정보를 담고 있는 객체)
src>main>java>com.enough.project에 dto 디렉터리 생성>AddUserRequest.java 생성
@Getter
@Setter
public class AddUserRequest {
private String email;
private String password;
}
객체를 인수로 받는 회원 정보 추가 메서드 작성
src>main>java>com.enough.project>service>UserService.java 생성
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public Long save(AddUserRequest dto) {
return userRepository.save(User.builder()
.email(dto.getEmail())
.password(bCryptPasswordEncoder.encode(dto.getPassword()))
.build()).getId();
}
}
컨트롤러 작성
src>main>java>com.enough.project>controller>UserApiController.java 생성
@RequiredArgsConstructor
@Controller
public class UserApiController {
private final UserService userService;
@PostMapping("/user")
public String signup(AddUserRequest request) {
userService.save(request);
return "redirect:/login";
}
}
회원가입, 로그인 뷰 작성
view 컨트롤러
src>main>java>com.enough.project>controller>UserViewController.java 생성
@Controller
public class UserViewController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/signup")
public String signup() {
return "signup";
}
}
view 생성
src>main>resources>templates>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>
src>main>resources>templates>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>
로그아웃 구현
src>main>java>com.enough.project>controller>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) {
new SecurityContextLogoutHandler().logout(request, response, SecurityContextHolder.getContext().getAuthentication());
return "redirect:/login";
}
}
실행 테스트
src>main>resources>application.properties를 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:
console:
enabled: true
build.gradle에 h2 추가
dependencies {
// ...
runtimeOnly 'com.h2database:h2'
}
테스트 실행
서버 실행 후 localhost:8080 입력 -> redirect되어 http://localhost:8080/login 으로 이동됨
localhost:8080/signup 이동
회원가입 후 로그인 실행 시
확인 작업
localhost:8080/h2-console
로그아웃
localhost:8080/logout 입력 시 로그인 페이지로 이동됨
'Spring > project' 카테고리의 다른 글
글 작성 view 구현 (1) | 2023.12.25 |
---|---|
게시판 기능(CRUD) (0) | 2023.12.25 |
게시판 리스트 페이지 생성 (0) | 2023.12.24 |
thymeleaf를 사용하여 레이아웃 적용 (0) | 2023.12.24 |
프로젝트 생성 및 기본 index 페이지 (0) | 2023.12.24 |