<TIL> 56. Spring Security

YUJIN LEE·2023년 3월 11일
0

개발log

목록 보기
51/149

Spring Security?

Spring Security 프레임워크는 스프링 서버에 필요한 인증 및 인가를 위해 많은 기능을 제공.

Spring Security 적용하는 법.

  1. build.gradle에 implementation 추가
    // 스프링 시큐리티
    implementation 'org.springframework.boot:spring-boot-starter-security'

  2. Spring Security 활성화
    WebSecurityConfig

package com.sparta.springsecurity.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf().disable();
        
        http.authorizeRequests().anyRequest().authenticated();

        // 로그인 사용
        http.formLogin();
        
        return http.build();
    }

}

CSRF?

CSRF(사이트 간 요청 위조, Cross-site request forgery)

  • 공격자가 인증된 브라우저에 저장된 쿠키의 세션 정보를 활용해 웹 서버에 사용자가 의도하지 않은 요청을 전달.
  • CSRF 설정이 되어있는 경우 html에서 CSRF 토큰 값을 넘겨줘야 요청 수신 가능
  • 쿠키 기반의 취약점을 이용한 공격, REST 방식의 API에서는 disable 가능
  • POST 요청마다 처리해 주는 대신, CSRF protection을 disable

Spring Security와 Filter

Spring Security는 요청이 들어오면 Servlet FilterChain을 자동으로 구성 후 거치게 함.
FilterChain은 여러 Filter를 Chain형태로 묶어놓은 것.

Filter?
톰캣과 같은 웹 컨테이너에서 관리되는 서블릿 기술.
Filter는 Client 요청이 전달되기 전후의 URL 패턴에 맞는 모든 요청에 필터링.
CSRF, XSS 등의 보안 검사를 통해 올바른 요청이 아닐 시 이를 차단.

Spring Security -> Filter를 사용해 인증/인가를 구현.

SecurityFilterChain

Spring의 보안 Filter를 결정하는 데 사용되는 Filter
session, jwt 등의 인증방식들을 사용하는데 필요한 설정을 완전히 분리할 수 있는 환경 제공.

AbstractAuthenticationProcessingFilter

사용자의 credential을 인증하기 위한 베이스 Filter

사용자 인증을 하는 filter

UsernamePasswordAuthenticationFilter

AbstractAuthenticationProcessingFilter를 상속한 Filter.

기본적으로 아래와 같은 Form Login 기반 사용할 때 username과 password 확인해 인증.

Form Login 기반은 인증이 필요한 URL 요청이 들어왔을 때 인증 되지 않았다면 로그인 페이지 반환.


SecurityContextHolder


SecurityContextHolder에는 스프링 시큐리티로 인증을 한 사용자의 상세 정보 저장

SecurityContext?
SecurityContextHolder로 접근할 수 있고, Authentication 객체를 가짐.

// 예시코드
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
context.setAuthentication(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();
        System.out.println("authority = " + authority);

        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으로 등록 후 사용 가능!

UserDetails

검증된 UserDetails는 UsernamePasswordAuthenticationToken 타입의 Authentication(인증객체)을 만들 때 사용.
해당 인증 객체는 SecurityContextHolder에 세팅.
Custom해 사용가능.

비밀번호 암호화

회원 등록 시 비밀번호는 사용자가 입력한 문자 그대로 DB에 등록하면 안된다.
'정보통신망법, 개인정보보호법'에 의해 비밀번호 암호화(Encryption)가 의무.

Spring Security가 제공하는 적응형 단방향 함수인 bCrypt 를 사용해 비밀번호 암호화

적응형 단방향 함수 bCrypt

적응형 단방향 함수는 내부적으로 리소스의 낭비가 매우 심해, API 요청마다 사용자의 이름과 비밀번호를 검증하면 애플리케이션 성능이 크게 떨어질 수 있다.
따라서 세션, 토큰 과 같은 인증방식을 사용해 검증하는 것이 속도 및 보안 측면에서 유리.

양방향 / 단방향

양방향 암호 알고리즘

  • 암호화: 평문 -> (암호화 알고리즘) -> 암호문
  • 복호화: 암호문 -> (암호화 알고리즘) -> 평문

단방향 암호 알고리즘

  • 암호화: 평문 -> (암호화 알고리즘) -> 암호문
  • 복호화: 불가

사용자가 로그인 시 암호화된 패스워드를 기억해야할까?

  • Password 확인절차
  1. 사용자가 로그인을 위해 "아이디, 패스워드(평문)" 입력 -> 서버에 로그인 요청
    a. 서버에서 패스워드(평문) 암호화
    i. 평문 -> (암호화 알고리즘) -> 암호문
  2. DB에 저장된 "아이디, 패스워드(암호문)"와 일치 여부 확인

Password Matching

Spring Security에서는 비밀번호를 암호화하는 함수를 제공할 뿐만 아니라 사용자가 입력한 비밀번호를 저장된 비밀번호와 비교해 일치여부를 확인해주는 함수도 제공.

// 사용예시
// 비밀번호 확인
if(!passwordEncoder.matches("사용자가 입력한 비밀번호", "저장된 비밀번호")) {
throw new IllegalAccessError("비밀번호가 일치하지 않습니다.");
}

boolean matches(CharSequence rawPassword, String encodedPassword);
*
rawPassword: 사용자가 입력한 비밀번호.
*** encodedPassword: 암호화되어 DB에 저장된 비밀번호.

CustomSecurityFilter와 앞으로 MySelectShop에서 적용할 JwtAuthFilter의 차이점

  • 우리의 프로젝트에서는 토큰을 사용해 인증/인가 구현.
    지금까지의 실습환경과는 달리 Filter에서 ID, PWD가 아닌 토큰을 검증.
    but, 로그인 요청에는 토큰 x
    따라서 우리의 프로젝트에서는 로그인 검증은 서비스에서 진행.
    로그인 성공 후 반환된 토큰이 인증이 필요한 API 요청과 같이 들어왔을 때 토큰을 검증해 사용자를 인증처리 해주는 JwtAuthFilter로 적용할 예정.

즉, 실습에서는 토큰방식이 적용 X -> Filter에서 사용자가 요청한 로그인을 검증해 인증.
토큰방식 적용 시 -> 사용자의 로그인, 회원가입과 같은 요청은 Filter에서 인증되지 않게 permitAll 처리 -> 실제 검증 및 인증처리는 service 에서 수행, 그외의 인증이 필요한 요청 -> 로그인을 통해 발급받은 토큰을 같이 보내 Filter에서 토큰을 검증 후 인증처리.

@Secured

  • API 접근 권한 제어 이해
    '일반 사용자'는 관리자 페이지에 접속이 인가되지 않아야 한다.
  1. 스프링 시큐리티에 "권한(Authority)" 설정방법

1.1 회원 상세정보(UserServicelmpl)를 통해 "권한(Authority)" 설정 가능
1.2 권한을 1개 이상 설정 가능
1.3 "권한 이름" 규칙
a. "ROLE_" 로 시작하게 만듬
ex) "ADMIN" 권한 부여 -> "ROLE_ADMIN"
"USER" 권한부여 -> "ROLE_USER"

package com.sparta.springsecurity.entity;

public enum UserRoleEnum {
    USER(Authority.USER),  // 사용자 권한
    ADMIN(Authority.ADMIN);  // 관리자 권한

    private final String authority;

    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    public String getAuthority() {
        return this.authority;
    }

    public static class Authority {
        public static final String USER = "ROLE_USER";
        public static final String ADMIN = "ROLE_ADMIN";
    }
}
public class UserDetailsImpl implements UserDetails {
		// ...

		@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        SimpleGrantedAuthority adminAuthority = new SimpleGrantedAuthority("ROLE_ADMIN");
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(adminAuthority);

        return authorities;
    }
}
  1. 스프링 시큐리티를 이용한 API 별 권한 제어 방법
  • Controller에 "@Secured" 어노테이션으로 권한 설정 가능
    @Secured("권한 이름") 선언
    *
    권한 1개 이상 설정 가능
// (관리자용) 등록된 모든 상품 목록 조회
    @Secured("ROLE_ADMIN")
    @GetMapping("/api/admin/products")
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }
  • @Secured 어노테이션 활성화 방법
@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
@EnableGlobalMethodSecurity(securedEnabled = true) // @Secured 어노테이션 활성화
public class WebSecurityConfig {

HTTP Status Code

403(Forbidden)

클라이언트 오류 상태. 서버에 요청이 전달되었지만, 권한 때문에 거절됨.

MDN Docs) https://developer.mozilla.org/ko/docs/Web/HTTP/Status/403

profile
인정받는 개발자가 되고싶습니다.

0개의 댓글