스프링 시큐리티 로그인 기능 구현
스프링 시큐리티는 스프링 기반의 애플리케이션 보안을 담당하는 프레임워크입니다. 이를 이용하여 로그인 기능을 구현할 수 있습니다.
로그인 기능을 구현하기 위해서는 스프링 시큐리티에서 제공하는 AuthenticationManager와 UserDetailsService를 구현해야 합니다. AuthenticationManager는 인증(Authentication)을 담당하고, UserDetailsService는 사용자 정보를 가져오는 역할을 합니다.
또한, 로그인 페이지와 로그인 성공/실패 시의 처리를 위한 컨트롤러와 뷰를 구현해야 합니다. 로그인 폼에서 입력한 아이디와 비밀번호를 AuthenticationManager에 전달하여 인증을 수행하고, 인증 결과에 따라 로그인 성공/실패를 처리합니다.
먼저 스프링시큐리티의 설정 코드를 작성해줍니다.
SecurityConfig
@Configuration
public class SecurityConfig {
@Autowired
private PrincipalDetailService principalDetailService;
@Bean
public BCryptPasswordEncoder encodePWD(){
return new BCryptPasswordEncoder();
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD());
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http.csrf().disable() // csrf 토큰 비활성화 (테스트시 걸어두는 게 좋음)
.authorizeRequests()
.antMatchers("/", "/auth/**", "/js/**", "/css/**", "/image/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/auth/loginForm")
.loginProcessingUrl("/auth/loginProc")
.defaultSuccessUrl("/");
return http.build();
}
}
해당 서비스의 회원가입 로직에서 비밀번호는 BCryptPasswordEncoder
에 의해 해시 암호화 되므로 해당 객체를 빈에 등록해줍니다.
SecurityFilterChain
을 사용하여 필요한 권한을 설정할 수 있습니다.
위의 예제는 모든 사용자(로그인 하지 않은 사용자 포함)가 "/", "/auth/**", "/js/**", "/css/**", "/image/**"
의 접근을 가능하게 하며 스프링 시큐리티 기반 login을 "/auth/loginForm"
경로에서 진행되게 합니다. 로그인 Form Action Url은 "/auth/loginProc"
경로가 됩니다.
PrincipalDetailService
@Service
public class PrincipalDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User principal = userRepository.findByUsername(username)
.orElseThrow(()->{
return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. " + username);
});
return new PrincipalDetail(principal); // 시큐리티 세션에 유저 정보가 저장이 됨
}
}
UserDetailsService
인터페이스를 구현하는 서비스입니다. 스프링 시큐리티에서 사용자 인증 처리를 위해 UserDetailsService
를 이용합니다. loadUserByUsername
메서드를 오버라이딩하여 사용자 정보를 가져옵니다.
userRepository.findByUsername(username)
메서드를 이용해 해당 username을 가진 사용자 정보를 가져옵니다. 사용자 정보가 존재하지 않으면 UsernameNotFoundException
을 던집니다.
PrincipalDetail
객체를 생성하여 사용자 정보를 인자로 전달합니다. PrincipalDetail
객체는 UserDetails
인터페이스를 구현한 클래스로, 시큐리티 세션에 저장됩니다.
PrincipalDetail
@Getter
public class PrincipalDetail implements UserDetails {
private User user;
public PrincipalDetail(User user) {
this.user = user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// 계정이 만료되지 않았는지를 리턴 (true: 만료되지 않음)
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정이 잠겨있지 않는지를 리턴 (true: 잠기지 않음)
@Override
public boolean isAccountNonLocked() {
return true;
}
// 비밀번호가 만료되지 않았는지를 리턴 (true: 만료되지 않음)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정이 활성화(사용 가능) 인지를 리턴 (true: 활성화)
@Override
public boolean isEnabled() {
return true;
}
// 계정이 가지고 있는 권한 목록을 리턴한다. (권한이 여러개 있을 수 있어서 루프를 돌아야한다. 해당 프로젝트에서는 한개만)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collectors = new ArrayList<>();
collectors.add(() -> {
return "ROLE_" + user.getRole();
});
return collectors;
}
}
PrincipalDetail
클래스는 UserDetails
인터페이스를 구현하는 클래스입니다. UserDetails
인터페이스를 구현하여 사용자 정보를 스프링 시큐리티에서 사용할 수 있습니다.
PrincipalDetail
클래스에는 User
객체가 포함되어 있습니다. 이 객체에는 사용자의 아이디, 비밀번호, 권한 등의 정보가 저장되어 있습니다.
getPassword()
메서드를 오버라이딩하여 사용자 비밀번호를 반환하고, getUsername()
메서드를 오버라이딩하여 사용자 아이디를 반환합니다.
그 외에도, 계정 만료 여부, 계정 잠금 여부, 비밀번호 만료 여부, 계정 활성화 여부 등의 정보를 반환하는 메서드를 구현해야 합니다.
getAuthorities()
메서드는 계정이 가지고 있는 권한 목록을 반환합니다. 이 메서드에서는 user.getRole()
메서드를 이용하여 사용자 권한을 확인하고, ROLE_
접두사를 붙여서 권한을 생성합니다.
이렇게 코드를 설정 해놓으면 로그인인 필요한 페이지에 로그인 하지 않은 클라이언트가 접속했을 시, 스프링 시큐리티에 의해 loginForm
으로 이동됩니다.
해당 페이지에서 로그인을 진행하면 스프링 시큐리티에 의해 로그인이 진행되고
로그아웃은 아래의 하이퍼링크를 통해 스프링 시큐리티에 의해 자동으로 실행됩니다.
<a class="nav-link" href="/logout">로그아웃</a>
'Spring > 블로그 프로젝트' 카테고리의 다른 글
[Blog 프로젝트] 게시글 페이징 처리(Pageable) (0) | 2023.03.18 |
---|---|
[Blog 프로젝트] 게시글 작성 기능 (API 통신) (0) | 2023.03.18 |
[Blog 프로젝트] 회원가입 기능 구현 (0) | 2023.03.18 |
[Blog 프로젝트] JPA를 사용하여 DB에 테이블 생성 (0) | 2023.03.02 |
[Blog 프로젝트] Spring Boot 프로젝트 생성 (0) | 2023.03.02 |