스프링 시큐리티로 안전한 로그인 시스템 만들기
안녕하세요! 개발과 기술을 사랑하는 여러분, 오늘은 Spring Security를 활용해 안전한 로그인 시스템을 구현하는 방법을 소개하겠습니다. 이 가이드는 로그인 기능을 커스터마이징하는 여러 단계로 구성되어 있으며, 이제 막 Spring을 배우기 시작한 초급자부터 중급 개발자에게도 유용할 것입니다. 그럼 시작해볼까요?
들어가며
웹 애플리케이션에서 사용자의 인증은 매우 중요한 부분입니다. 보안이 뒷받침되지 않은 로그인 기능은 사용자 데이터를 위협할 수 있죠. Spring Security는 이러한 인증 과정을 쉽게 관리할 수 있는 강력한 도구입니다. 이번 포스트에서는 기본적인 로그인 절차를 소개하고, 이를 기반으로 커스텀 인증 시스템을 만들어 보겠습니다.
1단계: 사용자 정의 인터페이스 구현
먼저, 사용자의 정보를 담는 UserEntity
클래스를 만듭니다. 이 클래스는 UserDetails
인터페이스를 구현하고 사용자에 대한 필요한 메서드를 정의합니다.
@Data
@Entity(name="USER_INFO")
@NoArgsConstructor
public class UserEntity implements UserDetails {
@Id
private String userId;
private String userPw;
private String userName;
@Builder
public UserEntity(String userId, String userPw, String userName) {
this.userId = userId;
this.userPw = userPw;
this.userName = userName;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(() -> "ROLE_USER");
}
@Override
public String getPassword() {
return this.userPw;
}
@Override
public String getUsername() {
return this.userId;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
2단계: 사용자 조회 메소드 추가
가입한 사용자를 조회하는 메소드를 UserRepository
인터페이스에서 정의합니다.
@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
UserEntity findByUserId(String username);
}
3단계: UserDetailsService 구현
CustomUserDetailsService
클래스를 만들어 사용자를 조회하고 로드하는 메소드 loadUserByUsername
를 구현합니다.
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity user = userRepository.findByUserId(username);
if (user == null) throw new UsernameNotFoundException("User not exist");
return user;
}
}
4단계: AuthenticationProvider 구현
이제 실제 인증 로직을 구현할 CustomAuthenticationProvider
를 작성합니다. 사용자의 인증 정보를 확인하고 필요한 경우 예외를 발생시킵니다.
@Component
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final CustomUserDetailsService customUserDetailsService;
private final PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
UserEntity user = (UserEntity) customUserDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, user.getUserPw())) {
throw new BadCredentialsException("Password is invalid");
}
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
5단계: 로그인 핸들러 구현
로그인 성공 또는 실패 시 처리할 핸들러를 구현합니다.
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
response.sendRedirect("/");
}
}
@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
response.sendRedirect("/login?error=invalid");
}
}
6단계: Security Config 수정
마지막으로 Spring Security의 설정을 수정해서 커스터마이즈된 인증 체계를 적용합니다.
@Slf4j
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
private final CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.successHandler(customAuthenticationSuccessHandler)
.failureHandler(customAuthenticationFailureHandler)
.permitAll()
.and()
.logout().permitAll();
}
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
7단계: 로그인 페이지 생성
로그인 폼을 담는 HTML 파일을 작성합니다. 아이디와 비밀번호를 입력받는 폼을 포함하고 있습니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<main class="form-signin w-100 m-auto">
<form method="post" action="/authenticate">
<h1 class="h3 mb-3 fw-normal text-center">로그인</h1>
<div class="form-floating">
<input type="text" class="form-control" name="username" placeholder="아이디">
<label>아이디</label>
</div>
<div class="form-floating">
<input type="password" class="form-control" name="password" placeholder="패스워드">
<label>패스워드</label>
</div>
<button class="w-100 btn btn-lg btn-primary" type="submit">로그인</button>
</form>
</main>
</body>
</html>
결론
이번 포스트에서는 Spring Security를 통해 안전하고 커스터마이즈 가능한 로그인 시스템을 구현하는 방법을 살펴보았습니다. 점차 복잡해지는 웹 애플리케이션의 보안을 강화하기 위해 이러한 시스템을 적용하는 것은 필수적입니다. 여러분도 이러한 방법을 통해 자신만의 로그인 시스템을 만들어 보세요!
의문이 있으신 분들은 댓글로 질문해 주세요. 다음 포스트에서 다시 만나요!
'개발' 카테고리의 다른 글
라즈베리파이에서 Node.js 최신버전으로 업데이트하는 방법 안내 (3) | 2024.11.14 |
---|---|
M1 Pro에서 Flutter 설치하는 완벽 가이드 (1) | 2024.11.14 |
Spring Boot와 MySQL을 활용한 회원가입 시스템 구축하기 (0) | 2024.11.14 |
Spring Boot와 Spring Security로 안전한 웹 애플리케이션 만들기 (0) | 2024.11.14 |
Mac에서 JDK 설치하는 방법 안내 (0) | 2024.11.14 |