2024. 8. 14. 15:32ㆍ스프링
2024/08/14
※ Spring Security 프레임워크에 대해 알아보자.
▶ Spring Security 적용
👉 'Spring Security' 프레임워크는 Spring 서버에 필요한 인증 및 인가를 위해 많은 기능을 제공해줌으로써
개발의 수고를 덜어준다. 마치 'Spring' 프레임워크가 웹 서버 구현에 편의를 제공해 주는 것과 같다.
● 'Spring Security' 프레임워크 dependency 추가
// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
● 'Spring Security' 활성화
○ Spring Security 제외 해제
@SpringBootApplication
public class SpringAuthApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAuthApplication.class, args);
}
}
○ Spring Security 설정
⚙️ WebSecurityConfig 클래스
package com.sparta.springauth.config;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity // Spring Security 지원을 가능하게 함
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF 설정
http.csrf((csrf) -> csrf.disable());
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources 접근 허용 설정
.anyRequest().authenticated() // 그 외 모든 요청 인증처리
);
// 로그인 사용
http.formLogin(Customizer.withDefaults());
return http.build();
}
}
○ LoggingFilter, AuthFilter 등록 해제
⛓️ LoggingFilter
@Slf4j(topic = "LoggingFilter")
//@Component
@Order(1)
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 전처리
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
log.info(url);
chain.doFilter(request, response); // 다음 Filter 로 이동
// 후처리
log.info("비즈니스 로직 완료");
}
}
⛓️ AuthFilter
@Slf4j(topic = "AuthFilter")
//@Component
@Order(2)
public class AuthFilter implements Filter {
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
public AuthFilter(UserRepository userRepository, JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
if (StringUtils.hasText(url) &&
(url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
) {
// 회원가입, 로그인 관련 API 는 인증 필요없이 요청 진행
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
// 나머지 API 요청은 인증 처리 진행
// 토큰 확인
String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);
if (StringUtils.hasText(tokenValue)) { // 토큰이 존재하면 검증 시작
// JWT 토큰 substring
String token = jwtUtil.substringToken(tokenValue);
// 토큰 검증
if (!jwtUtil.validateToken(token)) {
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 사용자 정보 가져오기
Claims info = jwtUtil.getUserInfoFromToken(token);
User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
new NullPointerException("Not Found User")
);
request.setAttribute("user", user);
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
throw new IllegalArgumentException("Not Found Token");
}
}
}
}
🔎 여기서 잠깐, CSRF란?
🔥 CSRF란?
● CSRF(사이트 간 요청 위조, Cross-site request forgery)
○ 공격자가 인증된 브라우저에 저장된 쿠키의 세션 정보를 활용하여 웹 서버에 사용자가 의도하지 않은
요청을 전달하는 것을 말한다.
○ CSRF 설정이 되어있는 경우 html 에서 CSRF 토큰 값을 넘겨주어야 요청을 수신 가능하다.
○ 쿠키 기반의 취약점을 이용한 공격이기 때문에 REST 방식의 API에서는 disable이 가능하다.
○ POST 요청마다 처리해주는 대신 CSRF protection을 disable 하겠다.
➡️ http.csrf((csrf) -> csrf.disable());
▶ Spring Security의 Default 로그인 기능
● Username: user
● Password: Spring 로그 확인 (서버 시작 시마다 변경됨)
▶ Spring Security 이해하기
▼ Spring Security - Filter Chain
● Spring에서 모든 호출은 DispatcherServlet을 통과하게 되고 이후에 각 요청을 담당하는 Controller로 분배된다.
● 이 때, 각 요청에 대해서 공통적으로 처리해야 할 필요가 있을 때 DispatcherServlet 이전에 단계가 필요하며 이것이 Filter다.
● Spring Security도 인증 및 인가를 처리하기 위해 Filter를 사용하는데
○ Spring Security는 FilterChainProxy를 통해서 상세 로직을 구현하고 있다.
▼ Form Login 기반은 인증
● Form Login 기반 인증은 인증이 필요한 URL 요청이 들어왔을 때 인증이 되지 않았다면 로그인 페이지를 반환하는 형태다.
▼ UsernamePasswordAuthenticationFilter
● UsernamePasswordAuthenticationFilter는 Spring Security의 필터인 AbstractAuthenticationProcessingFilter 를 상속한 Filter다.
● 기본적으로 Form Login 기반을 사용할 때 username과 password를 확인하여 인증한다.
● 인증 과정
1. 사용자가 username과 password를 제출하면 UsernamePasswordAuthenticationFilter는 인증된 사용자의
정보가 담기는 인증 객체인 Authentication의 종류 중 하나인 UsernamePasswordAuthenticationToken을
만들어 AuthenticationManager에게 넘겨 인증을 시도한다. ➡️
2. 실패하면 SecurityContextHolder를 비운다. ➡️
3. 성공하면 SecurityContextHolder에 Authentication를 세팅한다.
👉 SecurityContextHolder
● SecurityContext는 인증이 완료된 사용자의 상세 정보(Authentication)를 저장한다.
● SecurityContext는 SecurityContextHolder 로 접근할 수 있다.
// 예시코드
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
context.setAuthentication(authentication); // SecurityContext 에 인증 객체 Authentication 를 저장합니다.
SecurityContextHolder.setContext(context);
👉 Authentication
● 현재 인증된 사용자를 나타내며 SecurityContext에서 가져올 수 있다.
● principal : 사용자를 식별한다.
○ Username/Password 방식으로 인증할 때 일반적으로 UserDetails 인스턴스다.
● credentials : 주로 비밀번호, 대부분 사용자 인증에 사용한 후 비운다.
● authorities : 사용자에게 부여한 권한을 GrantedAuthority로 추상화하여 사용한다.
<UserDetails>
@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;
}
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
📌 UsernamePasswordAuthenticationToken는 Authentication을 implements한 AbstractAuthenticationToken의
하위 클래스로, 인증객체를 만드는데 사용된다.
👉 UserDetailsService
📌 UserDetailsService는 username / password 인증방식을 사용할 때 사용자를 조회하고 검증한 후 UserDetails를 반환한다. Custom하여 Bean으로 등록 후 사용 가능하다.
👉 SecurityContextHolder
📌 검증된 UserDetails는 UsernamePasswordAuthenticationToken 타입의 Authentication를 만들 때 사용되며 해당 인증객체는 SecurityContextHolder에 세팅됩니다. Custom하여 사용 가능하다.
※ 위 이미지들은 스파르타코딩클럽에 저작권이 있으므로 무단 도용 금지 및 상업 목적으로 사용할 수 없습니다.
'스프링' 카테고리의 다른 글
스프링 - RestTemplate & Open API (0) | 2024.08.19 |
---|---|
스프링 - RESTful API이란 무엇일까? (0) | 2024.08.16 |
스프링 - Filter란? (0) | 2024.08.14 |
스프링 - 영속성 컨텍스트란? (0) | 2024.08.13 |
스프링 - Entity란? (0) | 2024.08.12 |