시큐리티에 대해서
스프링 시큐리티 : 어플리케이션의 보안(인증 및 권한)을 담당하는 스프링 하위 프레임워크
시큐리티를 이해하기 위해선 관련 용어와 아키텍처(시스템 구성 및 동작 원리)에 대해 확실히 알아야 한다.
용어 :
접근 주체(Principal) : 보호된 대상에 접근하는 유저
인증(Authenticate) : 현재 유저가 누구인지 확인(로그인) , 어플리케이션 작업 수행할 수 있는 주체임을 증명
인가(Authorize) : 현재 유저의 권한을 검사한다.
아키텍처 :
로그인을 예로 들어 설명하면,
사용자가 로그인 요청을 하게 되면 AuthenticationFilter를 거치게 된다.
이 AuthenticationFilter는 로그인 요청이 오면 이 필터가 제일 처음에 작동한다.
로그인 요청을 할 때 Http body에 username과 password를 달고 오는데,
이 2가지를 가지고 UsernamePasswordAuthenticationToken을 만든다.
그러면 AuthenticationManager라는 애가 그냥 username과 password를 받는 것이 아니라
UsernamePasswordAuthenticationToken이라는 객체를 받아서
AuthenticationManager가 Authentication이라는 객체를 만든다.
Authentication이라는 객체를 만드는 방법은, UsernamePasswordAuthenticationToken을
UserDetailService한테 던진다. 그럼 UserDetailService는 username을 가지고 데이터베이스에서
해당 username을 가진 유저가 있는지 확인한다.
있다면 Authentication라는 객체를 만들고, 없으면 만들지 않는다.
시큐리티의 원리
우리가 로그인을 요청할 때, 시큐리티가 가로채게 할 것이다.
가로채게 설정하면 username과 password를 가져가서 로그인 진행을 하고
유저정보를 시큐리티 세션에 저장해준다.
그런데 이때 중요한 것이 뭐냐면 우리가 만든 User오브젝트를 넣을 수는 없다.
왜냐하면 타입이 정해져 있기 때문이다.
Type : UserDetails
이 타입을 유저정보에 저장시켜야 하니까 User 오브젝트를 저장시킬 수 없는 것이다.
그러면 어떻게 해야 할까?
UserDetails가 User 오브젝트를 extends 하는 방법이 있다. (상속)
이러면 같은 타입이 되기 때문이다. (상속해 타입을 맞춰줌)
( 예를 들어, Animal이라는 객체가 있고 Dog라는 객체가 있을 때
Dog extends Animal이라고 하면 Animal은 Dog의 부모가 된다.
new Dog를 하게 되면 메모리에 Animal과 Dog가 같이 뜨게 된다.
이때부터는 Dog가 Dog타입이 되기도 하고 Animal타입이 되기도 한다. (Animal자체는 Animal타입임)
왜냐하면 Dog extends Animal 했기 때문이다. 이렇게 타입이 두 가지를 가지게 된다.
이런 개념을 다형성이라고 한다. )
그럼 이 시큐리티 세션안에 들어갈 수 있는 User 오브젝트가 UserDetails 타입이여야 하니까
우리가 유저정보를 집어넣을 때 UserDetails 타입으로 맞춰주기만 하면 된다.
참고로 시큐리티가 로그인 진행을 대신해주는데, password 정보가 1234 이렇게 들어오면 로그인이 안된다.
시큐리티에서 이를 금지시켜놓기 때문이다.
해쉬로 암호화를 시켜야 로그인 진행이 되게 설정되어 있다.
해쉬 함수 중에서 이 프로젝트에선 구현이 쉽고 비교적 강력한 Bcrypt (비크립트)를 사용할 것이다.
해쉬란?
비밀번호 해쉬 후 회원가입하기
SecurityConfig.java
UserApiController.java
UserApiController.java에서 해당부분을 잘라내기하고
UserService.java에 회원가입에 붙여넣는다. 여기가 setRole을 배치하는데 적합해서 그냥 이동시킨 것이고,
이제 UserService.java에다가 해쉬관련 코드를 작성해보자
회원가입시 user.js를 호출하기 때문에 권한때문에 오류가 발생한다.
또한 user.js에서 회원가입시 location.href = "/"로 가는데 이것 역시 막혀있어서
SecurityConfig.java에서 다음처럼 코드를 추가해준다.
csrf토큰이 뭔지 왜 비활성화 하는지는 이후 csrf에 대한 설명에서 다루겠다.
실행결과 :
이렇게 회원가입에 성공하면
비밀번호가 해쉬로 잘 들어간 모습이다.
XSS와 CSRF
XSS 공격
CSRF 공격
이를 막기 위해선 GET방식이 아닌 POST 방식을 사용한다.
하이퍼링크는 GET방식이기 때문에 저런 주소 동작을 GET방식이 아니라 POST방식으로 설계하면
이런 공격을 당하지 않는다.
이를 막기 위한 또 다른 방법이 있는데
Referrer 검증이다. 같은 도메인 상에서 요청이 들어오지 않는다면 차단하는 것이다.
또 다른 방법은 CSRF Token 을 사용하는 방법이 있다.
랜덤한 수를 사용자의 세션에 저장하여 사용자의 모든 요청에 대하여 서버단에서 검증하는 방법이다.
//로그인시, 또는 작업화면 요청시 CSRF 토큰을 생성하여 세션에 저장한다.
session.setAttribute("CSRF_TOKEN",UUID.randomUUID().toString());
//요청 페이지에 CSRF 토큰을 셋팅하여 전송한다.
<input type="hidden" name="_csrf" value="${CSRF_TOKEN}" />
아까 CSRF 토큰을 비활성화 했던 이유는 이 블로그 프로젝트는 ajax를 통해(자바스크립트) 로그인 요청을 하기 때문에
여기 CSRF토큰이 없기 때문에 막아버리기 때문이다. (CSRF토큰이 없으면 스프링 시큐리티가 막아버린다.)
즉, 요청 시에 CSRF토큰이 없이 요청을 하면 그냥 막아버린다는 것이다. 그래서 비활성화 했던 것이다.
필요시에 나중에 CSRF토큰을 추가하고 활성화하면 된다.
다음번엔 앞서 배웠던 스프링 시큐리티1,2로 얻은 학습을 토대로 스프링 시큐리티 로그인을 적용하겠다.
참고자료 :
'자바 스프링 > 부트 블로그 JPA 프로젝트' 카테고리의 다른 글
#24 CRUD 게시판 - Create (글 작성하기) (0) | 2022.05.15 |
---|---|
#23 스프링 시큐리티 로그인 (0) | 2022.05.14 |
#21 스프링 시큐리티 (0) | 2022.05.12 |
#20 기존 방식의 로그인 (0) | 2022.05.12 |
#19 ResponseDto 수정 (0) | 2022.05.10 |