스프링 서버에 필요한 인증/인가를 위해 기능을 제공하는 프레임워크
build.gradle
// 스프링 시큐리티
implementation 'org.springframework.boot:spring-boot-starter-security'
springSecurityFilterChain 서블릿 filter빈 생성 → 보안처리 담당UserDetailsService 빈 생성user 와 랜덤 비밀번호 (콘솔 출력) 를 사용한 폼 기반 인증 지원disabledisableRequestMatcher 인터페이스를 사용하면 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 SecurityContextHolderSecurityContextHolder 에 접근하면 된다. // 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