스프링 - Spring Security 프레임워크

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

 

           ●  UsernamePasswordAuthenticationFilterSpring 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());
📌  UsernamePasswordAuthenticationTokenAuthentication을 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