Spring Security
- 스프링 서버에 필요한 인증 및 인가를 위해 많은 기능을 제공해 줌으로써 개발의 수고를 덜어줌. -> 공식 문서
- [Spring] 스프링 심화 - Spring Security 1
- 적용하기
- Spring Security의 요소 확인
- [Spring] 스프링 심화 - Spring Security 2
- Default Form Login 방식 사용
- UserDetails, UserDetailsService custom
- 비밀번호 암호화 이해 및 적용
- [Spring] 스프링 심화 - Spring Security 3
- CustomSecurityFilter 적용
- @AuthenticationPrincipal
- @Secured
- ExceptionHandling - 401, 403
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring()
.requestMatchers(PathRequest.toH2Console())
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.anyRequest().authenticated();
http.formLogin();
return http.build();
}
}
2. UserDetails, UserDetailsSerivce 커스텀
# UserDetailsImpl
public class UserDetailsImpl implements UserDetails {
private final User user;
private final String username;
private final String password;
public UserDetailsImpl(User user, String username, String password) {
this.user = user;
this.username = username;
this.password = password;
}
public User getUser() {
return user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
UserRoleEnum role = user.getRole();
String authority = role.getAuthority();
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(simpleGrantedAuthority);
return authorities;
}
@Override
public String getUsername() {return this.username;}
...
}
# UserDetailsServiceImpl
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다."));
return new UserDetailsImpl(user, user.getUsername(), user.getPassword());
}
}
UserDetails userDetails = userDetailsService.loadUserByUsername(username)
Authentication auth = new UsernamePasswordAuthenticationToken
(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
3. 비밀번호 암호화 이해 및 적용
- Spring Security 가 제공하는 적응형 단방향 함수인 bCrypt를 사용하여 비밀번호를 암호화
- 적응형 단방향 함수는 내부적으로 리소스의 낭비가 매우 심하기 때문에 API 요청 마다 사용자의 이름과 비밀번호를 검증하면 애플리케이션 성능이 크게 떨어질 수 있다. 따라서 세션, 토큰 과 같은 인증방식을 사용하여 검증하는 것이 속도 및 보안 측면에 유리하다.
- 사용자 검증 흐름
- 사용자는 회원가입을 진행한다.
- 사용자의 정보를 저장할 때 비밀번호를 암호화하여 저장한다.
- 사용자는 로그인을 진행한다.
- 사용자가 입력한 정보를 통해 저장된 암호화된 비밀번호를 가져와 사용자가 입력한 암호와 비교한다.
- 사용자 인증이 성공하면 사용자의 정보를 사용하여 JWT 토큰을 생성하여 Header에 추가하여 반환하고 Client 는 이를 쿠키저장소에 저장한다.
- 사용자는 게시글 작성과 같은 요청을 진행할 때 발급받은 JWT 토큰을 같이 보내고 서버는 이를 빠르게 인증 하고 사용자의 요청을 수행한다.
- 양방향 / 단방향
- 양방향 암호 알고리즘
- 암호화: 평문 → (암호화 알고리즘) → 암호문
- 복호화: 암호문 → (암호화 알고리즘) → 평문
- 단방향 암호 알고리즘
- 암호화: 평문 → (암호화 알고리즘) → 암호문
- 복호화: 불가 (
암호문 → (암호화 알고리즘) → 평문)
# WebSecurityConfig에 추가
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
+ PasswordEncoder는 matches 함수 제공
private boolean matches(CharSequence rawPW, String encodedPW)
+ 사용 예시
if(!passwordEncoder.matches("사용자가 입력한 비밀번호", "저장된 비밀번호")) {
throw new IllegalAccessError("비밀번호가 일치하지 않습니다.");
}
제네릭과 와일드카드