11주차 개념노트
Spring Security
자바 어플리케이션에 인증 인가를 모두 제공하는 쉽게 다룰 수 있는 프레임워크이다.
대부분의 시스템에서는 인증과 인가 처리를 해야하기 때문에 매우 유용한 프레임워크이다.
session fixation, clickjacking 등의 공격을 막아주는 기본적인 보안을 갖추고 있다.
Spring Security는 Servlet filter 체인을 자동으로 구성해서 요청이 filter를 거치게 한다.
사용자가 로그인 요청을 보내면 AuthenticationFilter로 사용자가 보낸 정보가 오고,
아이디와 비밀번호로 UserPasswordAuthenticationToken을 발급하게 되고,
이 토큰을 인증을 담당하는 AuthenticationManager에게 전달하게 된다.
AuthenticationManager는 UserPasswordAuthenticationToken을 순차적으로 AuthenticationProvider들에 전달해서 인증을 한다.
UsernamePasswordAuthenticationToken으로 아이디를 조회하고,
아이디를 기반으로 사용자 정보를 조회해서 입력받은 비밀번호와 대조하고, 일치하면 AuthenticationProvider에서 인증된 토큰을 AuthenticationFilter로 반환하고, AuthenticationFilter에서 LoginSuccessHandler로 전달한다.
LoginSuccessHandler로 전달된 Authentication객체를 SecurityContextHolder에 저장하면 인증이 끝난다.
@Configuration
설정 클래스에 붙여주는 어노테이션이다.
빈을 수동으로 등록하고 싶은 경우에 사용한다.
@Configuration은 @Component어노테이션을 포함하고 있기 때문에
스프링 컨테이너가 @Configuration이 붙은 클래스를 자동으로 빈으로 등록하고,
스프링 컨테이너는 @Configuration 의 @Bean이 붙은 메서드를 빈으로 생성해주기 때문에
스프링 컨테이너에 @Bean어노테이션으로 빈을 수동으로 등록할 수 있다.
@Value
스프링 컨테이너가 관리해주는 빈에 값을 외부에서 넣어줄 때 사용하는 어노테이션이다.
properties 파일에 넣어줄 값을 key=value형태로 넣어주고,
@Value("${key}")
를 필드나 생성자 인자에 붙여서 필드에 key에 해당하는 value 값을 넣어줄 수 있다.
javadoc
Javadoc은 Java를 위한 문서 생성기이다.
Java 소스 코드를 HTML 포맷으로 만들어주기 때문에 문서화를 쉽게 도와준다.
/**와 */사이에 원하는 내용을 태그와 함께 적어주고 싶은 대상 바로 위에 적어준다.
주요한 태그에는
작성자를 나타내는 @author,
해당 API를 더이상 사용하지 않는다는 @deprecated,
예외에 대한 내용에 대해 설명할 때 사용하는 @throws,
반환 값에 대해 설명하는 @return,
참고 대상에 대해 설명하는 @see 등이 있다.
인증(Authentication)과 인가(Authorization)
인증은 사용자가 누구인지 확인하는 절차이다.
인증은 로그인 요청 등으로 처리하게 되는데,
아이디와 비밀번호, 인증서, 보안카드, OTP, 지문, 홍채 등의 다양한 방법으로 인증을 할 수 있다.
인가는 인증 이후에 해당 사용자가 요청한 자원에 접근 가능한 사용자인지 확인해서 권한을 부여하는 과정이다.
Token 기반 인증 - JWT (JASON Web Tokens)
인증받은 사용자에게 토큰을 발급하고, 인증이 필요한 요청을 할 때 마다
헤더에 인증에 필요한 정보들이 포함된 토큰을 함께 보내는 방식이다.
Session&Cookie방식과 달리 서버에서는 상태를 관리하지 않고, 클라이언트에서 들어온 요청을 바로 처리한다.
JWT는 인증에 필요한 정보들을 암호화한 토큰이고, header, payload, verify signature 객체가 각각 필요하다.
header는 암호화 알고리즘인 alg와 토큰의 타입인 typ으로 구성된다.
payload에는 토큰에 담을 만료 일시, 사용자 정보 등의 claim이라고 하는 정보를 넣어둔다.
verify signature는 payload가 위변조되지 않았다는 것을 증명하기 위한 문자열이다.
header와 payload는 인코딩만 되고 암호화되지 않기 때문에 민감한 정보를 담으면 안된다.
하지만 verify signature는 secret key를 모르면 복호화할 수 없다.
인증 순서는 아래와 같다.
1. 사용자가 로그인을 하고,
2. 서버에서 사용자를 확인하고,
3. JWT를 발급해서 클라이언트에게 보내준다.
4. client는 인증이 필요한 요청을 할 때마다 토큰을 헤더에 넣어 보낸다.
5. 서버에서는 토큰의 verify signature를 secret key로 복호화하고, 검증한다.
6. 인증이 되면 사용자 id에 맞는 데이터를 client에 보내준다.
JWT는 토큰에 유저의 정보를 넣어두기 때문에 서버에서 사용자 정보를 관리하지 않아도 된다는 장점이 있다.
하지만 JWT는 한 번 발급되면 만료될 때까지 계속 사용할 수 있기 때문에
해커가 JWT를 탈취했다면 만료할 때까지 계속 해당 토큰을 사용할 수 있다는 단점이 있기 때문에
JWT의 유효기간을 짧게 잡고, 새로운 토큰을 발급하면 피해를 최소화할 수 있다.
Session & Cookie 기반 인증
서버 기반의 인증 방식으로, 서버에서 세션에 사용자 정보를 저장해두고
사용자가 헤더로 보내온 쿠키를 세션 저장소에서 받은 값과 대조해서 사용자가 서비스를 이용할 때 정보를 제공하는 방식이다.
인증 순서는 아래와 같다.
1. 사용자가 로그인을 하고,
2. 서버에서 사용자 정보를 확인하고,
3. 유효한 로그인 정보라면 세션을 생성하고, 세션 ID를 발급해서 클라이언트에게 보내준다.
3. 클라이언트는 세션 ID를 쿠키에 저장하고, 인증이 필요한 요청을 할 때 쿠키를 헤더에 넣어 요청을 한다.
4. 서버는 쿠키와 함께 온 요청을 받으면 쿠키를 검증하고, 세션 저장소에서 대응되는 정보를 가져온다.
5. 인증이 완료되고 서버는 사용자에게 데이터를 보내준다.
세션, 쿠키 기반 인증 방식의 단점은 쿠키만 탈취하면 서버는 사용자로 오인할 수 있다는 점이다.
또한 사용자의 정보를 세션에 저장해야되기 때문에 로그인된 사용자가 많아지면 서버에 부하를 줄 수 있다.
따라서 최근에는 토큰 기반의 인증 시스템을 많이 사용하고 있다.
CSRF(Cross-Site Request Forgery)
CSRF는 사용자가 로그인된 웹 사이트에 사용자가 의도치 않았지만 공격자가 의도한 요청을 하게 만드는 공격 방식이다.
CSRF는 어떤 웹사이트에 사용자가 로그인되어 있어서 웹사이트가 사용자의 웹 브라우저를 신용하는 상태를 노린 공격이다.
사용자가 웹사이트에 로그인한 상태에서 공격 코드가 삽입된 페이지를 열면
위조된 공격 요청이 신용하고 있는 웹 브라우저에서 온 것으로 인식하여 공격에 노출된다.
헤더, 쿠키 등을 이용해서 CSRF를 막는 방법 또한 CSRF라고 한다.
비대칭키(공개키) 암호화 방식
암호화와 복호화에 사용하는 키가 다른 암호화 방식이다.
비대칭키 암호화방식은 공개키와 개인키가 있다.
공개키는 공개된 키이다.
A가 B에게 비밀스러운 내용을 보내고 싶을 때 공개키는 누구나 볼 수 있기 때문에
B의 공개키로 비밀스러운 내용을 암호화해서 B에게 보낸다.
이 때 개인키는 자신만 갖고 있고, 개인키와 쌍을 이루는 공개키로 암호화된 메시지는 개인키로만 복호화할 수 있다.
따라서 누구나 보내고 싶은 사람의 공개키로 암호화해서 보내면 보내고 싶은 사람만 열어볼 수 있게 되는 메시지를 보낼 수 있다.
대칭키(공통키) 암호화 방식
암호화와 복호화에 동일한 대칭키를 이용하는 방식이다.
암호화 메시지를 수신하는 두 측에서 동일한 대칭키를 갖고 있고,
대칭키로 암호화를 해서 상대방에게 보내면 상대방은 대칭키로 암호화된 메시지를 복호화한다.
대칭키 암호화 방식은 비대칭키 암호화 방식보다 계산 속도가 빠르기 때문에 비대칭키 암호화 방식으로 대칭 키를 공유한 이후로는
대칭키를 이용해서 암호화한다.
Hash 암호화 방식
해시 함수는 어떤 데이터가 주어지든 고정된 길이의 데이터로 변환하는 함수이다.
해시 함수를 이용해서 암호화를 하는 방식이다.
해시 알고리즘은 특정 입력에 대해 같은 해시 값을 리턴하기 때문에
해시 값이 일치하는지 비교해서 비밀번호 일치 여부 등을 판단할 수 있다.
주의할 점은 무조건 고정된 길이의 데이터로 변환하기 때문에 서로 다른 입력에 대해 같은 해시 값이 도출될 수 있다는 점이다.
따라서 salt를 넣거나 해시함수를 여러번 돌리는 등의 방법으로 취약점을 보완할 수 있다.
encode와 decode
encode는 정보를 부호화/암호화하는 것을 의미하고,
decode는 부호화/암호화된 정보를 원래 정보로 돌려놓는 것을 의미한다.
압축, 암호화, 전자화 등은 모두 encoding에 해당하고,
압축을 풀거나, 암호를 해제하거나, 전자화된 정보를 아날로그 정보로 변환하는 등은 decoding에 해당한다.
HMAC256
HMAC연산을 위해 SHA256을 해시 함수로 사용하는 HMAC-SHA256의 줄임말이다.
HMAC은 메시지를 암호화하지 않지만 메시지의 암호화 여부와 관계없이 메시지는 HMAC해시와 함께 보내야 한다.
HMAC을 사용하면 데이터 무결성과 진본 확인을 할 수 있다.
기밀 키를 가진 쌍방은 스스로가 다시 메시지를 해싱하게 되고, 메시지가 진본인 경우 수신 후 연산되는 해시가 일치한다.
Argon2
같은 입력값을 넣어도 다른 값이 도출되는 해싱 알고리즘이다.
실행 시간, 필요한 메모리, 병렬 수준의 통제 변수를 사용하고,
입력값을 먼저 Blake2b로 해싱하고, 내부 함수를 반복 수행해서 값을 바꿔서 일정한 길이의 태그를 사용하는 방식이다.
Bouncy Castle
Java, C# 암호화에 사용되는 여러가지 API 모음이다.
curl
command line으로 다양한 프로토콜로 데이터를 주고받을 수 있게 도와주는 도구이다.
curl url 로 get요청을 보낼 수 있고,
curl -X POST --dtat "key=value" url 로 post요청을 보낼 수 있다.
헤더를 붙일 때는 - H 'header: value' 를 붙여주면 된다.
바디를 쓰고 싶을 때는 -d '{"key:"value"}'를 붙여주면 된다.
httpie
curl보다 조금 더 인간 친화적인 cli HTTP client이다.
http url로 get 요청을 보낼 수 있고,
http <Method> <url> <key>=<value>
위와 같이 key와 value에 바디에 들어갈 값을 넣어 요청을 보낼 수 있다.
헤더를 보내고 싶다면 헤더 정보를 따옴표로 감싸서 보낼 수 있다.
Spring Security의 X-Frame-Options
Spring Security는 기본적으로 사용자가 의도치 않은 클릭을 하게 될 수 있는 click jacking을 방지하기 위해
X-Frame-Options: Deny 설정이 되어 있다.
따라서 Spring Security를 사용하면 기본적으로 iframe에서 렌더링 되지 않는다.
만약 h2-console을 쓰거나 해서 iframe에서 렌더링이 필요한 경우
http.headers().frameOptions().disable(); 로 설정을 끄거나
http.headers().frameOptions().sameOrigin(); 으로 동일 도메인에서만 허용하도록 설정해줄 수 있다.
HTTP Headers
HTTP 헤더는 HTTP 메시지의 내용에 더해 추가적인 정보를 보내고 싶을 때 사용하고, 메타 데이터 등을 담고 있다.
클라이언트와 서버는 헤더를 보고 메시지에 대한 동작을 결정한다.
리소스에 대한 접근 권한을 설정하는 인증이나 캐시에도 헤더를 이용한다.
HTTP최초 버전인 0.9에는 헤더가 없었지만
문서의 메타 데이터를 표현하기 위해 전자메일의 메시지 스펙의 헤더 형식을 빌려오는 식으로 추가되었다.
따라서 HTTP헤더에는 이메일 메시지 헤더와 공통되는 부분이 있다.
헤더에 7bit ASCII코드 이외의 문자를 넣을 수 없고, 문자 인코딩이 있어서 ISO이외의 문자가 들어갈 수 없다.
전자메일 프로토콜과 HTTP의 가장 큰 차이점은 메일 프로토콜은 한 방향으로 메시지를 주고 받지만,
HTTP는 한 번의 통신에 요청과 응답이 모두 있다는 것이다.
따라서 HTTP에서는 전자메일에 없는 다양한 헤더가 있다.
자주 사용되는 헤더
Date - 메시지를 생성한 일시
If-Modified-Since - 조건부 GET, 리소스 갱신일시를 지정할 때 사용한다.
If-Unmodified-Since - 조건부 PUT, DELETE 리소스의 갱신일시를 지정할 때 이용한다.
Expires - 캐시 기한
Last-Modified - 리소스를 마지막으로 갱신한 일시
Retry-After - 다시 요청을 전송할 수 있는 일시
Session
웹 사이트에서 사용자의 정보를 서버에 저장하는 기술이다.
세션으로 웹 브라우저가 서버와 통신한 이후 브라우저를 종료할 때까지 인증 상태를 유지할 수 있다.
각 클라이언트에게 세션 ID를 부여하게 되고, 세션 ID로 각 클라이언트의 상태를 관리할 수 있게 된다.
@RequestAttribute
메서드 파라미터에 request attribute값을 넣어주고 싶은 경우에 사용한다.
HandlerInterceptor에서 request를 가로채서 request.setAttribute("key", value); 처럼 세팅을 해준 뒤
컨트롤러에서 @RequestAttribute으로 받아 쓸 수 있다.
HandlerInterceptor
요청을 가로채서 처리하기 위한 기능을 제공하는 인터셉터를 추가하기 위한 인터페이스이다.
3가지의 주된 메서드를 갖고 있다.
1. prehandle
실제 핸들러 실행 전에 호출된다.
2. postHandle
핸들러가 실행된 후에 호출된다.
3. afterCompletion
요청 이후 뷰가 생성된 뒤 호출된다.
Java의 Optional
null일 수도 있고 null이 아닐 수도 있는 값을 다룰 수 있는 클래스이다.
Optional을 사용하면 어떤 값이 null일 수 있을 때 .orElse로 쉽게 기본 값을 설정할 수 있고,
.ifPresent()로 값이 존재하는 경우에만 실행할 코드를 작성할 수 있어서 null에 대한 처리를 쉽게 도와준다.
만약 Optional타입의 객체가 null이 아니라면 isPresent() 가 참이고, get()메서드로 해당 값을 반환받을 수 있다.
orElse()
orElse()는 Optional클래스의 메서드이다.
orElse()에 인자로 기본 값을 넣어주면 만약 Optional 객체가 null일 때 기본 값을 반환해준다.
getReferenceById 와 findById 차이
getReferenceById 는 반환할 수 있는 것이 없다면 내부에서 예외가 발생하고,
findById 는 Optional타입을 반환하기 때문에 탐색했을 때 결과가 없어도 예외가 발생하지 않는다.
따라서 무언가를 조회할 때 반드시 존재해야하는 경우 getReferenceById 를 사용해야하고,
존재하지 않아도 되는 경우 findById 를 사용하면 좋다.
따라서 존재하지 않을 수도 있는 id를 찾거나 검색하는 행위에는 findById가 더 잘 어울린다.
또한 getReferenceById 는 EntityManager의 getReference 메서드를 호출하여 프록시 값만 가져온 후,
조회된 entity를 사용하는 순간 쿼리가 처리되는 lazy loading 으로 DB를 조회해 값을 가져온다.
하지만 findById 는 요청하는 순간 쿼리가 처리 된다.
findById는 요청하는 시점에 DB 를 조회하기 때문에 다른 객체에 할당할 때 사용하면 좋고, 내부 값을 필요로 하지 않는다.
Lazy loading
객체가 필요한 시점까지 로딩을 미루는 디자인 패턴이다.
로딩하는 데 오래 걸리는 이미지들을 lazy loading을 이용해서 처음에 모두 로딩하지 않고,
필요할 때 로딩하는 경우 모두 한 번에 로딩하는 것보다 로딩 시간을 줄일 수 있기 때문에 조금 더 사용성이 증가될 수 있다.