Spring Security를 잘 이해하려면 Cookie, Session, Token(Json Web Token아님)부터 알아야한다
- 인증/인가
- Spring Filter → 굉장히 잘 사용한 경우(추상화 된 기술) Spring Security
Cookie
-웹 브라우저(크롬, 엣지, 파이어폭스 등)에서 사용자의 컴퓨터에 저장되는 정보
- 이 정보는 서버에서 사용자의 브라우저로 전송되고, 브라우저는 해당 정보를 저장해두었다가 이후에 같은 서버에
요청을 보낼 때마다 함께 전송한다
- 쿠키는 사용자의 상태나 세션을 유지하거나 사용자 경험을 개선하기 위해 사용된다
Response 예시
set-cookie:
sessionId=abcd;
expires=Sat, 11-Dec-2023 00:00:00 GMT;
path=/; domain=notion.so;
Secure
- 사용자 로그인 세션 관리, 광고 트래킹 등에 주로 사용된다.
- Cookie는 모든 HTTP 요청이 발생할 때 마다 항상 서버에 전송된다.
- 네트워크 트래픽이 추가적으로 발생된다.
- 최소한의 정보만 사용해야한다.(sessionId, 인증 token)
- 보안에 취약하다.
- 서버에 전송하지 않고 브라우저에 단순히 데이터를 저장하고 싶다면?
- Web Storage를 사용한다. (localStorage, sessionStorage)
- 보안에 취약하다.
- 보안에 취약하기 때문에 주민번호와 같은 민감정보를 저장하면 안된다.
- Web Storage를 사용한다. (localStorage, sessionStorage)
브라우저 개발자도구 (F12)
- 브라우저에 저장된 Cookie 들을 확인할 수 있다
Cookie Header
- Set-Cookie : Server에서 Client로 Cookie 전달(Response Header)
- Cookie : Client가 Server에서 받은 Cookie를 저장하고 HTTP 요청시 Server로 전달(Request Header)
Cookie의 생명주기
- Set-Cookie: expires=Sat, 11-Dec-2023 00:00:00 GMT;
- 해당 만료일이 도래하면 쿠키가 삭제된다.
- Set-Cookie: max-age=3600 (second, 3600초는 한시간. 60 * 60)
- 0이 되거나 음수를 지정하면 쿠키가 삭제된다.
- 세션 Cookie : 만료 날짜를 생략하면 브라우저 완전 종료시 까지만 유지된다.(Default)
- ex) 브라우저를 완전 종료 후 다시 페이지를 방문했을 때 또 로그인을 해야된다
- 영속 Cookie : 만료 날짜를 입력하면 해당 날짜까지 유지
Cookie의 도메인
쿠키가 아무 사이트에서나 생기고 동작하면 안된다!(필요없는 값 전송, 트래픽 문제 등)
- ex) domain=notion.so
- 명시한 문서 기준 도메인 + 서브 도메인 포함
- domain=notion.so를 지정하여 쿠키 생성
- notion.so + dev.notion.so와 같은 서브 도메인에도 쿠키 접근
- 생략하면 현재 문서 기준 도메인만 적용
- domain=notion.so 에서만 Cookie 접근
Cookie의 경로
- 1차적으로 도메인으로 필터링 한다. Path는 2차 필터
- ex) 일반적으로 path=/ 루트(전체)로 지정한다
- 위 경로를 포함한 하위 경로 페이지만 쿠키 접근
- path=/api
- /api → 가능
- /api/example → 가능
- /example → 불가능
Cookie 보안
- Secure
- 기본적으로 Cookie는 http, https 구분하지 않고 전송한다.
- Secure를 적용하면 https인 경우에만 전송한다. s = Secure
- HttpOnly
- XSS 공격을 방지한다(배운내용 입니다 여러분..).
- 자바스크립트에서 접근 불가(브라우저 Console에 document.cookie 사용 불가)
- HTTP 전송시에만 사용
로그인 상태유지
- 한번 로그인에 성공하면 HTTP Response에 쿠키를 담아서 브라우저에 전달한다.
- 브라우저는 요청마다 Cookie를 함께 전송한다.
- 실제로는 보안상의 문제로 userId=1과 같은 index 정보를 저장한다. → 이것 또한 보안문제가 있음
- 세션 Cookie : 만료 날짜를 생략하면 브라우저 완전 종료시 까지만 유지된다.(Default)
- ex) 브라우저를 완전 종료 후 다시 페이지를 방문했을 때 또 로그인을 해야된다
- 영속 Cookie : 만료 날짜를 입력하면 해당 날짜까지 유지
- 세션 Cookie : 만료 날짜를 생략하면 브라우저 완전 종료시 까지만 유지된다.(Default)
요구사항에 맞추어 세션 Cookie를 사용할지 영속 Cookie를 사용할지 결정하면 된다.
@Entity
public class User {
private Long id;
private String userId; // 로그인 ID
private String password; // 로그인 비밀번호
private String name;
}
@Getter
public class LoginRequest { // DTO
@NotBlank
private String id; // 사용자가 입력한 아이디
@NotNull
private String pwd; // 사용자가 입력한 비밀번호
}
@Getter
public class UserDto { // Response
private Long id;
// 이외 응답에 필요한 데이터들을 필드로 구성하면 된다.
// 필요한 생성자
}
@Controller
@Requiredargsconstructor
public class UserController {
private final UserService userService;
@PostMapping("/login")
public String login(
@Valid @ModelAttribute LoginRequest request,
HttpServletResponse response // 쿠키값 세팅에 필요
) {
// 로그인 유저 조회
UserDto loginUser = userService.login(request.getId(), request.getPwd());
if (loginUser == null) {
// 로그인 실패 예외처리
return "login";
}
// 로그인 성공 처리
// 쿠키 생성, Value는 문자열로 변환하여야 한다.
Cookie cookie = new Cookie("userId", String.valueOf(loginMember.getId()));
// 쿠키에 값 세팅 (expire 시간을 주지 않으면 세션쿠키가 됨, 브라우저 종료시 로그아웃)
// Response Set-Cookie: userId=1 형태로 전달된다.
response.addCookie(cookie);
return "redirect:/"; // 메인페이지로 리다이렉트
}
}
@Service
@Requiredargsconstructor
public class UserService {
private final UserRepository userRepository;
public UserDto login(String inputId, String inputPwd) {
// 1. 유저 조회
// 2. 입력한 값과 유저의 비밀번호 일치여부 조회
// 3. 일치하지 않으면 예외처리
// 3-1. 일치하면 return Type인 UserDto에 맞추어 Controller로 전달
return new UserDto(user);
}
}
- 로그인에 성공하면 쿠키를 생성하고 HttpServletResponse 객체에 에 담는다.
- Response Header를 확인해보면 Set-Cookie: userId=1 형태로 전달된다.
- Cookie 이름(Key값)은 userId 이고 회원 index 값을 담아둔다.
- 만료 시간을 지정하지 않아서 세션 쿠키로 만들어진다.
- 브라우저 종료 전까지 userId 가 모든 Request Header Cookie에 담겨서 전달된다.
- 저장된 쿠키를 사용하는 API 예시
- 요구사항
- 로그인한 회원이면 home 페이지로 이동(메인(home) 페이지를 보려면 로그인 필수)
- 로그인하지 않은 회원이면 login 페이지로 이동
- 요구사항
@Controller
@Requiredargsconstructor
public class HomeController {
private UserService userService;
@GetMapping("/")
public String home(
// @CookieValue(required = true) 로 필수값(default) 설정
// required = false 이면 필수값 아님.
@CookieValue(name = "userId", required = false) Long userId, // String->Long 자동 타입컨버팅
Model model
) {
// 쿠키에 값이 없으면 로그인 페이지로 이동 -> 로그인 X
if(userId == null) {
return "login";
}
// 실제 DB에 데이터 조회 후 결과가 없으면 로그인 페이지로 이동 -> 일치하는 회원정보 X
UserDto loginUser = userService.findById(userId);
if(loginUser == null) {
return "login";
}
// 정상적으로 로그인 된 사람이라면 View에서 사용할 데이터를 model 객체에 데이터 임시 저장
model.addAttribute("loginUser", loginUser);
// home 화면으로 이동
return "home";
}
}
쿠키의 보안 문제
- 쿠키 값은 임의로 변경할 수 있다.
- 위 예시코드와 같은 방식을 사용하면 Client가 임의로 쿠키의 값을 변경하면 서버는 다른 유저로 인식한다. userId = 내맘대로 수정가능
- 브라우저 → 개발자도구 → Application 탭 → Storage/Cookies/URL → 값 조정 가능
- 실제로는 암호화되어 저장되어있는 Value들을 볼 수 있다!
보안 대처방법
- 쿠키에 중요한값을 저장하지 않는다.
- 사용자 별로 일반 유저나 해커들이 알아보지 못하는 값을 노출한다. → 보통 암호화된 Token을 쿠키에 저장한다. → Access Token, Refresh Token
- 서버에서 암호화된 Token과 사용자를 매핑해서 인식한다.
- Token은 서버에서 관리한다.(생성[만료시간 등], 검증)
- 토큰은 해커가 임의의 값을 넣어도 동작하지 않도록 만들어야 한다. → JWT : ADSFSADFKJLJ!@#LKAJSDFLKAJSASDASDFASD
- 해커가 토큰을 탈취해도 사용할 수 없도록 토큰 만료시간을 짧게 설정한다.
- 탈취가 의심되는 경우 해당 토큰을 강제로 만료시키면 된다. ex) 접속기기 혹은 IP가 다른 경우 등 → Logic
'TIL' 카테고리의 다른 글
TIL 240625 Session (0) | 2024.06.26 |
---|---|
TIL 240621 Validation (0) | 2024.06.24 |
TIL 240617 과제 피드백 AOP (0) | 2024.06.18 |
TIL 240614 Mockito, 통합테스트 (0) | 2024.06.17 |
TIL 240613 단위 테스트 (1) | 2024.06.14 |