비밀번호를 암호화 하지 않고(평문으로) 저장하면 Security로 로그인 할 수 없음
→ Spring Security는 기본적으로 비밀번호가 암호화되어 있다고 가정
Spring Security는 {암호화방식}암호화된비밀번호 형식으로 저장함
// 내부적으로 이런 과정이 일어남
passwordEncoder.matches("1234", "1234")
// java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
// : 암호화 방식을 식별할 수 없다
비밀번호 저장 시
- BCrypt 암호화 사용
- 임시로 평문 사용 (비밀번호 앞에 접두사 {noop} 사용) → user.setPassword("{noop}1234");
BCryptPasswordEncoder
@Bean // -> 해당 메서드의 리턴되는 오브젝트를 IoC로 등록해줌
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// Service
...
private final PasswordEncoder passwordEncoder;
...
@Transactional
public void saveUser(User user) {
String rawPassword = user.getPassword();
String encPassword = passwordEncoder.encode(rawPassword);
user.setPassword(encPassword);
userRepository.save(user);
}
로그인
@Configuration
@EnableWebSecurity // 스프링시큐리티 필터가 스프링 필터체인에 등록됨
public class SecurityConfig{
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable()) // CSRF 비활성화
.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/**").authenticated() // 인증 필요
.requestMatchers("/manager/**").hasAnyRole("ADMIN", "MANAGER") // 권한 확인
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().permitAll() // 나머지는 허용
)
.formLogin(form -> form
.loginPage("/loginForm") // 권한,인증이 필요한 요청은 /loginForm으로 리다이렉트 됨
.loginProcessingUrl("/login") // POST /login이 호출되면 시큐리티가 낚아채서 대신 로그인 진행
.defaultSuccessUrl("/")); // 성공하면 "/" 메인페이지로 리다이렉트
return http.build();
}
}
.loginProcessingUrl("/login")
POST /login이 호출되면 Spring Security가 낚아채서 로그인을 진행해줌
→ /login 컨트롤러 만들지 않아도 됨
로그인 후 세션 생성
로그인 성공 → Security Session 생성
Security ContextHolder라는 키값에 세션 정보를 저장
[객체 계층 구조]
Security Session
ㅤㅤㅤㅤㅤㅤ└── Authentication (인증 객체)
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ└── UserDetails (사용자 상세 정보 객체)
1. 로그인 처리
시큐리티가 /login 주소 요청을 낚아채서 로그인을 진행
2. UserDetailsService 호출
Spring Security → PrincipalDetailsService.loadUserByUsername() 호출
3. 사용자 조회 및 UserDetails 생성
User userEntity = userRepository.findByUsername(username);
return new PrincipalDetails(userEntity);
4. Authentication 객체 생성
반환된 UserDetails가 Authentication 객체 내부에 저장
5. Security Session 저장
Authentication이 SecurityContextHolder에 저장
//UserDetails 구현
public class PrincipalDetails implements UserDetails {
private User user;
public PrincipalDetails(User user) {
this.user = user;
}
// 해당 유저의 권한을 리턴
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return authorities;
}
...
@Service
public class PrincipalDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if (userEntity != null) {
return new PrincipalDetails(userEntity);
}
return null;
}
// 반환된 UserDetails는 Authentication 내부에 들어감
// Security Session에는 저 Authentication이 들어감
}