시큐리티 의존성을 추가합니다.
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-security'
...
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/auth/**", "/js/**", "/css/**", "/image/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/auth/user/login")
.loginProcessingUrl("/auth/api/v1/user/login")
.defaultSuccessUrl("/");
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Configuration: 수동빈 등록
@EnableWebSecurity : 스프링 시큐리티 설정들을 활성화
csrf().disable() : csrf 토큰 해제
authorizeRequests() : URL별 권환 관리를 설정하는 옵션
antMatchers() : 권한 관리 대상을 지정하는 옵션
- "/" 등 지정된 URL들은 permitAll() 열람 권한
anyRequest().authenticated(): 다른 요청들은 인증이 되어야함
formLogin() : 권한이 없는 유저가 페이지 요청시 → 로그인 페이지로 이동
loginPage() : 로그인 페이지
loginProcessingUrl() : 스프링 시큐리티가 해당 주소로 요청오는 로그인을 가로채서 대신 로그인
defaultSuccessUrl() : 로그인이 성공하면 해당 URL로 이동
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
...
}
- 시큐리티가 이를 지켜보다가 요청의 아이디와 비밀번호 파라미터를 가로챈다.
- 가로챈 정보를 통해 로그인 진행
- 시큐리티 세션에 (로그인 유저 정보) 저장
- 세션에 저장된 유저 정보를 DI를 통해 가져온다.
- 이 때 User 오브젝트를 세션에 저장하지 못하고, UserDetails 오브젝트로 저장한다.
위의 작동 과정에서 User 오브젝트를 세션에 저장하지 못하고, UserDettails를 세션 저장소에 저장 할 수 있습니다.
package community.board.config.auth;
import community.board.domain.User;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
@Getter
public class PrincipalDetail implements UserDetails {
private User user;
public PrincipalDetail(User user) {
this.user = user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(() -> user.getRoleKey());
return collection;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true; // 계정의 만료 여부(true: 만료되지 않음)
}
@Override
public boolean isAccountNonLocked() {
return true; // 계정이 Lock 상태 여부(true: 잠겨있지 않음)
}
@Override
public boolean isCredentialsNonExpired() {
return true; // 패스워드의 만료 여부(true: 만료되지 않음)
}
@Override
public boolean isEnabled() {
return true; //계정 활성화 여부 (true: 활성화)
}
}
@RequiredArgsConstructor
@Service
public class PrincipalDetailService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User principal = userRepository.findByUsername(username).orElseThrow(
() -> new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. " + username));
return new PrincipalDetail(principal);
}
}
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final PrincipalDetailService principalDetailService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(principalDetailService).passwordEncoder(bCryptPasswordEncoder());
}
...
}
PrincipalDetailService를 통해 해쉬화된 비밀번호를 DB에 있는 패스워드와 비교 가능합니다.
@GetMapping("/auth/user/login")
public String userLogin(@AuthenticationPrincipal PrincipalDetail principalDetail) {
return "user/user-login";
}