정수원님의 강의 스프링 시큐리티 완전 정복 [6.x 개정판] 보면서 공부한 내용입니다.
💡 주의사항
- base64로 인코딩한 값은 암호화된 값이 아니어서 디코딩이 가능하기 때문에 인증 정보가 노출된다
- 그렇기에 반드시 HTTPS(보안 프로토콜)와 같이 TLS 기술과 함께 사용해야한다
💡 TLS 란?
- 정보를 암호화해서 송수신하는 프로토콜로 웹사이트 주소는 HTTPS로 시작한다.
💡API 내 authenticationEntryPoint의 담당역할
- 인증을 받지 못한 경우 다시 로그인 페이지로 이동하도록 도와줌
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) // 어떤 요청에도 인증을 받겠다
.httpBasic(basic -> basic.authenticationEntryPoint(new CustomAuthenticationEntryPoint()));
return http.build();
}
💡 BasicAuthenticationFilter는 일반적으로 세션을 사용하지 않고
인코딩된 문자열을 통해 계속적으로 요청을 통해서 인증을 처리한다.
💡 암호화되는 값
base64(username, expirationTime, algorithmName, algorithmHex(이하 동일 + password + key))
http.rememberMe(... -> ...
.alwaysRemember(true) // 쿠키가 항상 생성되어야하는지의 여부 (기본 : false)
.tokenValiditySeconds(3600) // 토큰의 유효한 시간
.userDetailsService(..) // 사용자 정보를 가져옴
.rememberMeParameter("remember") // 기본적으로 설정되어있음
.RememberMeCookieName("remember") // 인증을 위한 토큰을 저장하는 쿠키 이름
.key("security") // 기억하기 인증을 위해 생성된 토큰을 식별하는 키
remember 체크 생성
체크 후 cookie 확인 => remember 쿠키 생성
세션을 지워도 쿠키에서 저장되어있기 때문에 다시 접근해도 인증을 받지 않아도됨
=> this.securityContextHolderStrategy.getContext().getAuthentication()가 true인 경우: 자동인증 수행 x (사용자가 인증 상태에 있는 것)
-> 세션을 삭제했음에도 불구하고 remember라는 캐시때문에 자동인증이 실행됨
-> 해당 과정을 통해 자동 로그인 실행됨
💡 인증된 사용자와 익명 사용자가 있을 때 익명 사용자는 실체가 없는 것으로 해석하기 쉽다.
단, 스프링 시큐리티는 인증된 사용자와 익명 사용자를 동일한 기준(객체로)에서 구분하여 SecurityContext안에 저장을 한다. 차이점은 익명 사용자의 인증 객체는 세션에 저장하지 않는 것이다.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.anonymous(anonymous -> anonymous
.principal("guest") // 익명 사용자 이름 설정
.authorities("ROLE_GUEST") // 익명 사용자 권한 설정
);
return http.build();
}
public String method(Authentication authentication) { // 인증 객체가 들어옴
if (authentication instanceof AnonymousAuthenticationToken) { // 익명일 때는 AnonymousAuthenticationToken객체가 들어오는 것이 아닌 null로 들어옴
return "anonymous";
} else {
return "not anonymous"; // null인 경우 반환
}
}
public String method(@CurrentSecurityContext SecurityContext contest) { // 익명 사용자의 값을 얻을 때
return context.getAuthentication().getName();
}
SecurityContextHolder 에 Authentication 객체가 없을 경우 감지하고 필요한 경우 새로운 Authentication 객체로 채운다
AnonymousAuthenticationFilter의 역할
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/anonymous").hasRole("GUEST") // 해당 권한만 가진 사용자만 접근 가능
.requestMatchers("/anonymousContext","/authentication").permitAll() // 익명 사용자를 참조하는 방법
.anyRequest().authenticated()) // 어떤 요청에도 인증을 받겠다
.formLogin(Customizer.withDefaults()) // 인증을 못받았을 때 formLogin을 통해 인증을 받음
// 익명 사용자의 권한
.anonymous(anonymous -> anonymous
.principal("guest")
.authorities("ROLE_GUEST")// 익명 사용자만 접근할 수 있는 자원 설정
// 익명 사용자라 할지라고 권한이 따로 있으므로 해당 권한을 가진 자만 접근 가능하도록 설정
// 만약, 인증을 받은 사용자라 할지라도 해당 권한이 없으면 익명 사용자가 접근할 수 있는 자원에는 접근 안되
);
}
@GetMapping("/anonymous")
public String anonymous(Authentication authentication) {
return "anonymous";
}
@GetMapping("/authentication")
public String authentication(Authentication authentication){
if(authentication instanceof AnonymousAuthenticationToken){
return "anonymous";
} else {
return "null";
}
}
@GetMapping("/anonymousContext")
public String anonymousContext(@CurrentSecurityContext SecurityContext context){
// null이 아닌 실제 익명 객체를 참고하고 싶을 때 context를 통해 익명 객체를 받을 수 있다
return context.getAuthentication().getName();
}
💡 CSRF 기능이란?
사용자 요청에 대해서 세션을 악의적인 목적으로 사용하지 못하도록 스프링 시큐리티가 활성화하는 기능 (공격에 대비)
http.logout(httpSecurityLogoutConfigurer -> httpSecurityLogoutConfigurer
.logoutUrl("/logoutProc") // 로그아웃이 발생하는 URL 을 지정
.logoutRequestMatcher(new AntPathRequestMatcher("/logoutProc","POST")) // 로그아웃이 발생하는 RequestMatcher 을 지정
// Method 를 지정하지 않으면logout URL이 어떤 HTTP 메서드로든 요청될 때 로그아웃 할 수 있다 (GET, PUT, DELETE 모두 가능)
.logoutSuccessUrl("/logoutSuccess") // 로그아웃이 발생한 후 이동하는 URL (getMapping 해줘야함)
.logoutSuccessHandler((request, response, authentication) -> { // 사용할 LogoutSuccessHandler 를 설정
response.sendRedirect("/logoutSuccess"); // LogoutSuccessHandler가 더 우선시됨
})
.deleteCookies("JSESSIONID“, “CUSTOM_COOKIE”) // 로그아웃 성공 시 제거될 쿠키의 이름 지정
.invalidateHttpSession(true) // HttpSession을 무효화해야 하는 경우 true (기본값), 그렇지 않으면 false
.clearAuthentication(true) // 로그아웃 시 SecurityContextLogoutHandler가 인증(Authentication)을 삭제 해야 하는지 여부 : true (기본값)
// fasle 인 경우 : Authentication 삭제 안함
.addLogoutHandler((request, response, authentication) -> {}) // 기존의 로그아웃 핸들러 뒤에 새로운 LogoutHandler를 추가
// 기존의 것은 그대로 수행
.permitAll() // logoutUrl(), RequestMatcher() 의 URL 에 대한 모든 사용자의 접근을 허용
// 특정한 경우에만 필요한 것
HttpSessionRequestCacherequestCache=newHttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("customParam=y");
http
.requestCache((cache)->cache
.requestCache(requestCache)
);
RequestCachenullRequestCache=newNullRequestCache();
http
.requestCache((cache)->cache
.requestCache(nullRequestCache)
);