스프링 시큐리티

  • JWT는 JSON 객체로 안전하게 전송하기 위해 사용한다.
  • header, payload, signiture 구성을 갖는다. base64로 각각 인코딩에서 클라이언트에 돌려준다. 이는 데이터의 무결성을 보장하기 위해 base64로 사용한다. 디코딩해서 확인할 수 있다. 
  • 클라이언트가 서버에 요청을 할때 JWT가 신뢰할 수 있는 토큰인지 검증하는데 서버는 header, payload, signiture를 알고있다. JWT가 유효한지 검증하는데 header랑 payload 그리고 서버가 가지고 있는 secret key를 가지고 HMACSHA 256으로 암호화해서 (직접 할 필요는 없다. 스프링 시큐리티에 관련 라이브러리가 존재한다.) 이 값을 클라이언트에 요청한것과 같으면(서명된 부분) 이전에 온 JWT가 같은지 검증이 완료된다. 
  • SHA256을 쓰지 않고 RSA를 쓰면 공개키, 개인키를 적용할 수 있는데 서버가 header, payload된것을 개인키로 서명하여 JWT를 발급한 후에 이후 검증할 때는 공개키로 확인해본다. 이 방법에서는 secret key가 필요하지 않는다. 
  • 정리해보면 ASE64(헤더).BASE64(페이로드).BASE(HS256암호화(lowSig)) = JWT 토큰 이다. (RSA방식이 아닌경우)
  • SHA256을 직접할 필욘 없고 Spring Library를 이용해서 진행한다.  
  • SecurityConfig 에 인증할 경로를 지정할 수 있다. > hasRole도 지정할 수 있다. 
  • SecurityConfig에서는 formLogin을 disable 처리하고 httpBasic도 disable (id,pw방식), session 생성 시 stateless하게 지정한다. 
  • @CrossOrigin 을 직접 사용하진 않고, JWT를 이용하기 위해서는 corsConfig 파일을 설정해주어야 한다. 
    • 예를 들어서 setAllowCredentials(true) - 서버가 응답한것을 JS에서 처리할 수 있도록 할지 설정하는것
    • addAllowedOrigin("*") / addAllowedHeader("*") / addAllowedMethod("*") 각각에 대해서 모든 ip 허용, header에 응답 허용, 모든 HTTP 메서드 요청 허용이 있다.  
    • 위와 같이 요청을 다 허용해주고 나서 리소스에다가 /api/**의 모든 주소는 위 설정한 모든 허용의 configuration을 따르도록 한다. 
  • 쿠키는 동일 도메인에서만 요청이 오면 처리한다. 쿠키 방식은 다른 도메인 요청을 풀어주게 되면 JS에 의해서 보안에 취약해 질 수 있다. e.g) XSS 스크립트 공격 방식
  • HTTP Basic 방식
    • headers에 Authorization에 id,pw 를 요청하면 http basic방식이다. 확장성에는 좋지만 id,pw가 암호화가 되지 않아 노출될 수 있다.
  • HTTP bearer 방식
    • Authorization에 토큰을 넣는방식이다. token을 들고있어서 유효시간에 맞춰 JWT 토큰을 발급받는 형식이다. 
  • Filter로 이러한 토큰의 유효성 검사를 수행할 수 있다. SecurityConfig 이전에 필터를 실행시킬 수 있는데 addFilterBefore에 클래스 타입으로 SecurityContextPersistenceFilter를 등록해주면 된다. 
  • 기본적으로 token을 검증할 때는 header에 Authorization에 value값으로 토큰을 들고 온다.
    • 토큰 검증은 RSA, HS256 을 사용한다.
  • SecurityConfig에 formLogin().disable()했기 때문에 /login로 이동이 안된다. 이런 경우 addFilter를 추가해준다. AuthenticationManager를 구현해준다. 로그인 시도시에 실행되는 함수이다.
  • 세션에 PrincipalDetails (디비에서 매치 후 가져오는 정보)를 담고 JWT 토큰을 응답한다 JWT 만 쓰지 않고 Seesion이랑 같이 사용하는 이유는 권한 관리를 수행해야 하기 때문이다.
  • UserDetails를 상속받고 오버라이딩된 클래스로 UsenamePasswordAuthenticationToken을 만들어서 시큐리티 컨텍스트의 authentication 에 넣어줍니다. authentication에 로그인한 정보가 담깁니다. authentication객체는 세션에 저장된다. 
  • 데이터베이스 암호화된 값을 비교할 때 BCryptPasswordEncoder 를 IoC에 등록해서 사용하면 된다.
  • 세션과 토큰 방식을 간단히 비교해보면
    • 세션 방식
      • 로그인이 정상적으로 되면 서버에 세션 ID를 생성하고 이를 받은 클라이언트는 쿠키 세션 ID에 응답한다. 요청할 때마다 쿠키값에 세션ID를 항상 들고 있으므로 서버쪽으로 요청하면 서버는 세션 ID가 유효한지 판단해서 유효하면 인증이 필요한 페이지로 접근한다. 
    • JWT 토큰 방식
      • 로그인이 정상적으로 되면 클라이언트 쪽으로 JWT 토큰을 응답한다. 요청할 때마다 JWT 토큰을 가지고 요청을 하고 서버는 JWT 토큰이 유효한지 판단한다. 단, 토큰의 유효성 검증을 위해 필터를 구성해야 한다. (securityConfig 에 addFilter를 이용하자!)
  • 시큐리티가 filter를 가지고 있는데 그 필터중에 BasicAuthenticationFilter라는 것이 존재한다. 권한이나 인증이 필요한 특정 주소를 요청했을 때 위 필터를 무조건 타게 된다. 
  • 아래의 코드 처럼 JWT 토큰 검증을 수행하고 username을 가져오게 된다.
  • JWT.require(Algorithm.HMAC512("cos")).build().verify(jwtToken).getClaim("username").asString();
  • 만약, 토큰이 유효하다면 Authentication 객체를 생성한다. new UsernamePasswordAuthenticationToken(세션 객체, password, 권한)을 통해서 디비와 비교해온다.
  • 설정 정보에 관한것은 JWT properties를 따로 빼두는것이 좋다.
  • jwt properties 를 만들어서 secret key, expiration_time, token_prefix, header_string 을 설정하는것이 좋다. > 실수를 방지할 수 있다. 직접 값을 대입하는것보다 properties에서 관리하는것이 좋다. 

'Spring' 카테고리의 다른 글

Criteria API  (0) 2023.01.28
의존성 주입  (1) 2023.01.21
빈 생명주기 & 콜백  (0) 2021.12.14
의존관계 자동 주입  (0) 2021.12.13
컴포넌트 스캔  (0) 2021.12.12