[Spring Security] 구현 과정 정리

JUNHYUK CHANG·2024년 1월 19일
0

TIL

목록 보기
7/33

Spring Security 를 공부하면서 이를 구현하기 위해 용어와 인터페이스, 클래스를 정리하고, 각 기능을 요약해보려고 한다.

JWT

Json Web Token 의 줄임말로, 사용자의 인증정보를 문자열로 구성된 "토큰" 으로 만들어 헤더에 전송하고, 받는 측에선 이를 분해하여 사용자의 인증 여부를 확인한다.

Filter? Authentication?

클라이언트에서 전송된 요청은 Controller 로 오기 전, Filter Chain 내부의 다양한 필터를 거치면서 필요한 기능을 수행한다.
SpringSecurity 는 DelegatingFilterProxy 내부에 FilterChainProxy 에 인증과 관련된 FilterChain 을 적용할 수 있게 해준다.
인증 과정은 Authentication(인증) 객체를 받아 인증 여부를 판단하고, 인증이 됐을 시 SecurityContextHolder에 인증 객체를 할당하는 것이 핵심이다.
SecurityContextHolder > Security Context > Authentication > [Principal, Credentials, Autorities

0. 사전준비

일단 Gradle 에 필요한 종속성을 추가해주어야 한다.

    // Spring Security 추가
	implementation("org.springframework.boot:spring-boot-starter-security")
	// jwt 관련 라이브러리 중 jjwt 추가
	implementation("io.jsonwebtoken:jjwt-api:0.12.3")
	runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.3")
	runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.3")

1. class JwtPlugin ()

JwtPlugin 에는 토큰 검증과 생성 역할을 구현하였다.

  • fun validation(jwt : string) : Result<Jws>
    • jwt 토큰을 받아와 검증하는 메서드
    • 결과값으로 Success / Failure 을 반환한다.
    • HmacSHA 알고리즘을 통해 생성한 key 를 사용하여 jwt 를 파싱하여 검증한다.
    • 토큰이 유효한 경우, 클레임(JWT의 내용)을 결과로 반환한다.
  • fun jgenerateAccessToken( subject:String, email : String, role : String) : String
    • 액세스 토큰을 생성하는 메서드
    • 토큰을 만드는 과정에서 Claims 를 직접 설정하여 담고자 하는 속성을 추가할 수 있다.
    • Jwts.builder() 를 사용하여 포함될 클레임을 설정하고 , 시크릿 키(HmacSHA 알고리즘으로 생성됨)로 서명을 추가한 뒤 최종적으로 JWT 를 생성하여 문자열로 반환한다.

2. @Bean class PasswordEncoderConfig

비밀번호를 암호화하여 저장하는 역할. 의존성을 주입하여 사용하기 위해 @Bean 으로 등록.

  • fun passwordEncoder() : PasswordEncoder
    - 비밀번호를 암호화(encode) 하는 PasswordEncoder 인터페이스 객체를 반환.
    - BCryptPasswordEncoder() 를 반환한다.
    이후 사용자의 비밀번호를 저장할 시 passwordEncoder.encode(비밀번호) 를 통해 암호화된 비밀번호를 저장할 수 있다.

저장된 비밀번호를 확인할 때는 passwordEncoder.matches( 입력 비번 , 저장된 비번 )으로 확인이 가능하다. 단, BCrypt 를 통해 암호화된 비밀번호는 다시 복원이 어려워(단방향 암호화) 같 은 비밀번호를 입력하여 같은 방법으로 암호화 했을 때, 서로 일치 여부만 확인할 수 있다.
-> 그래서 비밀번호 찾기 하면 내 비밀번호를 찾아주는 것이 아니라 새 비밀번호를 주는 것

3. @Component class JwtAuthenticationFilter( private val jwtPlugin: JwtPlugin ) : OncePerRequestFilter()

클라이언트가 매 요청시 Jwt를 보내는데, 이를 검증하는 기능을 담당.
클라이언트의 요청은 "Authorization": Bearer {JWT} 로 전달되는데, Bearer는 JWT 혹은 OAuth를 통한 토큰 인증 용도로 지정한다.
요청된 Header에서 JWT만 추출하여 검증. Authentication 객체에 인증됨을 표기하고, SecurityContext 에 저장하면 된다.

이 과정을 처리하기 위해 Filter 를 만들어야 하는데, interface Filter 를 상속받아 void doFilter() 를 구현하는 것이 핵심이다.

  • override fun doFilterInternal( )
    • jwtPlugin.validateToken(jwt) 를 통해 토큰을 검증하고, 성공시 반환된 claims를 확인하여 내부의 payload 에서 정보를 가져온다.
    • 가져온 정보는 UserPrincipal( 사용자 정보를 담는 데이터 클래스. autoricies 라는 사용자 권한 정보도 담김 ) 에 저장한다.
    • Authentication 객체를 생성하고 정보를 담아 토큰을 만든 뒤 SecurityContextHolder 에 담아 다음 필터를 호출한다.

4. class JwtAuthenticationToken(private val principal : UserPrincipal, details : WebAuthenticationDetails ) : AbstractAuthenticationToken(principal.authoricies)

사용자가 인증되었음을 나타내는 토큰. userPrincipal 정보와 details ( 요청하고 있는 주소, 세션 정보) 를 담는다. 위의 필터에서 Authentication 객체를 생성할 때 쓰인다.

5. class SecurityConfig()

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig()
private val jwtAuthenticationFilter: JwtAuthenticationFilter,
private val authenticationEntryPoint: CustomAuthenticationEntryPoint,
private val accessDeniedHandler: AccessDeniedHandler,
)

SpringSecurity 를 사용하여 웹 보안을 설정하는 클래스.
필요 없는 필터를 끄거나 원하는 위치에 필터를 추가할 수 있고, 특정 경로를 설정하여 항상 승인 처리를 나게하거나 인증 된 사용자만 허용하도록 할 수 있다.

@EnableMethodSecurity 어노테이션을 통해 이후 Controller 에서 @PreAuthorize 어노테이션으로 메서드의 인가를 설정할 수 있다. hasRole( ) 사용.

@AuthenticationPrincipal userPrincipal: UserPrincipal 어노테이션을 통해 현재 인증된 UserPrincipal 을 가져와 파라미터로 전달할 수 있고, userPincipal.id 등의 사용자 정보를 가져올 수 있다.


처음 공부할 땐 무슨 말인지 뭘 하겠단건지 모른 채 코드를 작성하고 따라가는데 급급했지만, 한번 완성하고 다시 정리해보니 각 역할과 필요성이 어느정도 눈에 보이는 듯 하다. 무작정 작성하는 것이 아니라 큰 그림을 그리는 연습을 더 열심히 해야겠다.

0개의 댓글