스프링 서버에 필요한 인증/인가를 위해 기능을 제공하는 프레임워크
build.gradle
// 스프링 시큐리티
implementation 'org.springframework.boot:spring-boot-starter-security'
springSecurityFilterChain
서블릿 filter빈 생성 → 보안처리 담당UserDetailsService
빈 생성user
와 랜덤 비밀번호 (콘솔 출력) 를 사용한 폼 기반 인증 지원disable
disable
RequestMatcher
인터페이스를 사용하면 URL 뿐 아니라 HttpServletRequest
에 있는 거라면 실행 여부를 결정할 수 있음스프링 시큐리티에서 지원하는 인증
SecurityContextHolder
로 접근, 현재 인증한 사용자의 Authentication
을 갖고 있음AuthentiacationManager
의 입력으로 사용Authentication
에서 접근 주체(Principal)에 부여한 권한 (role, scope 등)username and password
OAuth 2.0 Login - OpenID connect. 소셜네트워크 로그인
등등…
// SpringSecurity docs
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
// MySelectShop (JwtAuthFilter.java)
public void setAuthentication(String username) {
SecurityContext context = SecurityContextHolder.createEmptycontext();
Authentication authentication = jwtUtil.createAuthentication(username);
context.setAuthentication(authentication);
SecurtiyContextHolder.setContext(context);
}
SecurityContext
생성SecurityContextHolder.getContext().setAuthentication(authentication)
대신 new SecurityContext
를 생성Authentication
객체 생성UsernamePasswordAuthenticationToken(userDetials, password, authorities)
를 사용SecurityContext
on the SecurityContextHolder
SecurityContextHolder
에 접근하면 된다. // Access Currently Authenticated User
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
UserDetails
인스턴스임isAuthenticated()
는 false
를 리턴GrantedAuthority
로 추상화 // MySelectShop
public class UserDetailsImpl implements UserDetails {
...
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
UserRoleEnum role = user.getRole();
String authority = role.getAuthority();
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthroty(authority);
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(simpleGrantedAuthority);
return authorities;
}
...
}
// Authentication 객체 생성 시
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
💡
UsernamePasswordAuthenticationToken
은Authentication
을 implements한AbstractAuthenticationToken
의 하위 클래스로 인증 객체를 만드는 데 사용됨
AuthentiacationEntryPoint
를 사용해서 credential을 요청고 그 후 이 필터는 제출한 모든 요청을 처리함HttpServletRequest
에서 Authentication
을 만듦Authentication
타입은 AbstractAuthenticationProcessingFilter
의 하위클래스에 따라 다름UsernamePasswordAuthenticationFilter
⇒ 제출된 HttpServletRequest
에 있는 username 과 password로 UsernamePasswordAuthenticationToken
생성SecurityContextHolder
를 비움RememberMeServices.loginFail
실행. RememberMe를 설정하지 않았다면 아무것도 실행되지 않음AuthenticationFailureHandler
실행SessionAuthenticationStrategy
에 로그인 했음을 통보SecurityContextHolder
에 Authentication
세팅 후 SecurityContextPersistenceFilter
가 HttpSession
에 SecurityContext
저장RememberMeServices.loginSuccess
실행. 노설정이면 무동작ApplicationEventPublisher
- InteractiveAuthenticationSuccessEvent 발생AbstractAuthenticationProcessingFilter를 상속한 Filter
Reading the Username & Password
HttpServletRequest
에서 username 과 password를 읽을 수 있는 기본 메커니즘Form Login
html 폼 기반 username/password 인증
인증이 필요한 URL 요청이 들어왔을 때 인증이 되지 않았다면 로그인 페이지를 반환
FilterSecurityInterceptor
에서 AccessDeniedException
을 던져 인증되지 않은 요청이 거절됨을 알림ExceptionTranslationFilter
에서 인증을 시작하고 AuthenticationEntryPoint
에서 설정한 LoginUrl 페이지로 리다이렉트 응답을 전송스프링 시큐리티에선 폼 로그인이 디폴트로 활성화됨
Default Form Log In
// SpringSecurity docs
protected void configure(HttpSecurity http) {
http
...
.formLogin(withDefaults()));
}
// MySelectShop (WebSecurityConfig.java)
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
...
// 로그인 사용
http.formLogin();
return http.build();
}
Custom Log in Form Configuration
// SpringSecurity docs
protected void configure(HttpSecurity http) throws Exception {
http
...
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
}
// MySelectShop (WebSecurityConfig.java)
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
...
// Custom 로그인 페이지 사용
http.formLogin().loginPage("/api/user/login-page").permitAll();
return http.build();
}
UserDetailsService
가 리턴하는 값DaoAuthenticationProvider
가 UserDetails
를 인증하고 이걸 principal로 가진 Authentication
을 리턴UsernamePasswordAuthenticationToken
타입의 Authentication을 만들 때 사용, 해당 인증객체는 SecurityContextHolder
에 세팅UserDetails
를 반환public class UserDetailsImpl implements UserDetails {
...
// 인증이 완료된 사용자 추가
// 사용자의 권한 GrantedAuthority로 추상화 및 반환
// Getter
...
}
public class UserDetailsServiceImpl implements UserDetailsService{
...
@Override
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
...
}
}
// MySelectShop (JwtUtil.java)
public Authentication createAuthentication(String username) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
PasswordEncoder
구현체를 커스텀하려면 빈을 정의하면 됨// MySelecShop (WebSecurityConfig.java)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
bCrypt
사용💡 적응형 단방향 함수는 내부적으로 리소스의 낭비가 심해 성능이 떨어질 수 있기 때문에 세션, 토큰 등의 인증방식을 사용하여 검증하는 것이 속도 및 보안 측면에서 유리함
일치여부 확인 함수
boolean matches(CharSequence rawPassword, String encodedPassword);
// 사용 예시
if (!passwordEncoder.matches( 입력한 비밀번호, 저장된 비밀번호)) {
throw new IllegalAccessError("비밀번호가 일치하지 않습니다.");
}
reference