우아한테크코스 level 2-3 지하철 경로 조회 미션 회고 및 학습 프로필
와일더의 학습 프로필 🃏
atdd-subway-path 미션을 하면서 새롭게 학습한 내용을 정리했습니다.
[Authentication] 인증 - 5
내용
식별된 정보로 서비스에 동록된 유저의 신원을 입증하는 과정
- base64Encode 를 통해 로그인 정보를 인코딩한다.
- 요청 헤더에 만들어진 로그인 정보를 담아서 보내준다.
- DB 를 체크하고 실제로 값이 있으면 ok 응답을 한다.
매번 인증을 해야한다. 브라우저의 스토리지를 사용해서 이를 해결한다.
링크
[Authorization] 인가 - 2
내용
인증된 사용자에 대한 자원 접근 권한 확인
Oauth
다른 웹사이트 상의 자신들의 정보에 대해 접근 권한을 부여할 수 있는 공통적인 수단 (개방형 표준)
- 동작 순서
- 유저 브라우저에서 Oauth 제공자 측으로 로그인을 시도한다.
- Oauth 에서 유저 브라우저에 아이디가 존재하는지 요청한다.
- 유저 브라우저에서 권한 부여를 설정해서 Oauth 측에 전송한다.
- Oauth 에서 유저 브라우저에 code 를 반환한다.
- 유저 브라우저에서 Query param 에 code 가 있으면 서비스에 요청을 보낸다.
- 서비스에서 Oauth 측에 액세스 토큰을 요청한다.
- Oauth 가 서비스에 토큰을 제공하게 되어 권한을 가질 수 있게된다.
- 유저 브라우저에서 요청을 서비스로 하면 서비스가 Oauth 측에 요청을 보내고 Oauth 는 서비스에 응답하면 서비스가 유저 브라우저에 Oauth 로부터의 응답을 보낸다.
장점
- 사용자
- 서비스에 ID/PW 를 알려주지 않아도 된다.
- 원할 때 엑세스 토큰의 권한 취소가 가능하다.
- 서비스
- 유저의 엑세스 토큰만 가지고 있으면 된다.
- 사용자의 ID/PW 를 몰라도 허가 받은 API 접근이 가능하다.
링크
[HTTP] CORS (Cross Origin Resource Sharing) - 5
내용
기존 브라우저의 정책은 보안상의 이유로 다른 도메인의 리소스를 가져오는 것이 불가능했다. (Single-Origin-Policy)
이를 해결하기 위해 등장한 표준 기술이 CORS 다. 도메인이 다른 자원에 리소스를 요청할 때 접근 권한을 부여하는 매커니즘이다. 다른 도메인의 자원을 쓰려면 자원의 주인이 허락한 규약을 지켜야하는 그러한 규약을 표준화 한 것이 CORS 다.
-
CrossOrigin 애너테이션을 통해 쉽게 해결할 수 있다.
public class PathApiController { @CrossOrigin("http://localhost:8081") // 옵션을 주지 않으면 모든 도메인에 대해 개방된다. @GetMapping("/paths") public ResponseEntity findPath() { ... } }
-
세부적으로 자신의 서버에 CrossOrigin 을 Configuration 을 추가해서 하는 방법도 있다.
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedMethods("*").allowedOriginPatterns("*"); } }
인터셉터를 사용할 경우 CrossOrigin 의 검증이 이루어지기 전에 요청이 반환 될 수 있는데, 이 경우에는 Option 을 주어서 해결할 수 있다.
링크
[AWS] Load Balancer - 2
내용
서버에 가해지는 부하(=로드)를 분산(=밸런싱)해주는 장치 또는 기술을 통칭한다. 클라이언트와 서버풀 사이에 위치하며, 한 대의 서버로 부하가 집중되지 않도록 트래픽을 관리해 각각의 서버가 최적의 퍼포먼스를 보일 수 있도록 한다. 로드밸런싱은 여러 대의 서버를 두고 서비스를 제공하는 분산 처리 시스템에서 필요한 기술이다.
Scale-out 방식으로 성능이 낮은 서버를 두 대 이상 배치할 경우 서버로 트래픽을 균등하게 분산해주는 로드밸런싱이 반드시 필요하다.
L4
- Transport Layer(IP, Port) 레벨에서 로드 밸런싱을 한다.
- TCP, UDP
- IP 주소나 포트번호, MAC 주소, 전송 프로토콜에 따라 트래픽을 나누는 것이 가능하다.
L7
- Application Layer(사용자의 Request) 레벨에서 로드 밸런싱을 한다.
- HTTP, HTTPS, FTP
- HTTP 헤더, 쿠키 등과 같은 사용자의 요청을 기준으로 특정 서버에 트래픽을 분산하는 것이 가능한다.
링크
[Token] JWT (Json Web Token) - 5
내용
JSON 객체를 사용하여 가볍고 자가수용적인 방식으로 정보를 안정성있게 전달해주기 위한 토큰이다.
- 다음과 같은 형태로 구성된다.
sdfjsadlkfjaskldf.sdklfjasljfa.asdklfjaslkfj
// 헤더(header).내용(payload).서명(signiture)
- 헤더는 사용한 해쉬 알고리즘과 토큰 타입 정보가 들어있다.
- 내용은 json 으로된 데이터가 들어있다.
- 서명은 암호화 키(예를들어 base64UrlEncode)가 들어있다.
JWT 자체는 해독하기 쉽기 때문에 비밀번호 같은 민감한 정보는 담지 않는다. 시크릿 키를 서버 내부에 잘 관리해야 한다. 세션 스토리지와 연결하지 않고 시크릿 키를 이용해서 토큰을 만들어 낸다. 본인이 가진 시크릿 키로 유효성 검사를 진행한다.
문제점
- 해커에게 탈취당하면 access 권한을 관리자로 바꿀 수 있다.
- 만료기간을 정해서 문제를 해결하려고 한다.
- 하지만 짧은 인증기간 때문에 불편한데, 이 때문에 나온 것이 Refresh Token 이다.
최초 로그인 시 액세스 토큰(만료기한 : 30분)과 Refresh 토큰(만료기한 : 2주)을 함께 만들어 Refresh 토큰은 따로 저장소에 저장하고 클라이언트에게 두 정보를 전송한다. 클라이언트가 요청할 때 엑세스 토큰이 만료되었다면 Refresh 토큰을 확인 해보고 올바른 Refresh 토큰이라면 새로운 액세스 토큰을 생성해서 전달한다.
해독 순서
- 이름으로 유저를 찾아낸다.
- 만료시기를 확인한다.
- 권한까지 확인할 수 있다.
- 단, 비밀번호는 담지 않는다. 토큰 자체가 유효성 검사를 통과했기 때문에 비밀번호의 역할을 할 수 있다.
사용 과정
- 브라우저에서 회원정보를 입력한다.
- 서버에서 JWT 를 만든다.
- 만든 JWT 를 브라우저에 보낸다.
- 브라우저는 JWT 를 가지고 서버에 데이터 요청을 한다.
- 서버는 서명을 확인하고 유저 정보를 클라이언트에게 제공한다.
목적
- 세션에 유저 정보를 유지하지 않고 회원인증이 가능해져서 stateless 한 서버를 만들 수 있다.
- 안정성 있는 정보교류
저장방식
- Session or Local
- Cookie
단점
- 사용자 계정을 비활성화하려면 토큰 만료를 기다려야 한다.
- 암호 변경이 이루어지더라도 사전에 인증받은 토큰이 만료되기 전까지 사용이 가능 하다.
- 토큰 만료 시 재인증 해야한다.
- ‘상태 비 저장’ 측면을 위반하지 않고는 토큰을 파기할 수 없다.
링크
[HTTP] Cookie - 2
내용
쿠키는 유저들의 효율적이고 안전한 웹 사용을 보장하기 위하여 사용하는 기술이다. 웹사이트 접속 시 접속자의 개인장치에 다운로드 되고 브라우저에 저장된다. 쿠키는 만료일이 있으며, 자동으로 삭제되는 쿠키도 있고 수동으로 삭제할 수 있는 쿠키도 있다.
Connectionless, Stateless 한 HTTP 특성에 의해 상태 정보를 유지하지 않기 때문에 이를 보완하기 위해 나온 것이 쿠키다.
쿠키의 구성
- Name: 쿠키의 이름
- Value: 쿠키의 내용(일반적으로 raw 값을 노출시키지 않기 위해 보안 처리를 함)
- Domain: 쿠키가 전송될 호스트를 명시하고 명시되지 않았을 때는 현재 문서 위치의 호스트를 기본값으로 잡는다.
- Path: 쿠키 헤더를 전송하기 위해서 요청되는 URL 내에 반드시 존재해야하는 URL 이며, “/” 문자는 디렉티브 구분자로 구분한다.
- Expires / Max-Age: 만료기한을 걸어둔 쿠키의 쿠키 만료 시간을 제공한다.
- Size: 쿠키의 크기
쿠키의 한계
- XSS 공격
- 스니핑 공격
- 공용 PC 에서 쿠키값 유출
- 호스팅 공유 문제
- 쿠키의 개인정보 침해 위험
링크
[HTTP] Session - 2
내용
인증된 사용자의 식별자와 랜덤한 문자열로 세션 아이디를 만들어서 응답의 헤더로 넘겨준다.
세션이 만료되면 해커가 가져가더라도 유요하지 않은 정보가 된다. 세션이 탈취되면 서버에서 삭제하면 이용할 수 없게 된다. 보안상으로 좋다.
여러 개의 서버를 운영할 경우 서버당 하나의 세션을 가지고 있기 때문에 로드밸런스에 의해 다른 서버에 요청을 하게되면 해당 세션에 로그인 정보가 없을 경우 로그인이 실패하게 된다. 이 문제를 세션 스토리지를 통해 해결했다. 세션을 하나로 통합해서 관리하는 것이다. 단, 클라이언트의 로그인 요청이 몰리면 서버가 터진다. 어떤 방법을 쓰더라도 문제가 발생하는 이유는 통신을 할 때 사용하는 HTTP 와 서버가 지향하는 RestAPI 가 ‘무상태성’을 기초로 하기 때문이다. 패러다임의 충돌이 발생하는 것이다.
정보의 요청안에 사용자의 정보를 담아보자 -> 토큰
링크
[Spring] Interceptor - 1
내용
특정 URI 로 요청시 controller 로 가는 요청을 가로채는 역할을 한다. 스프링에서 관리되며 스프링 내의 모든 객체에 접근이 가능하다.
인터셉터를 사용하지 않고 로그인 처리를 한다면 모든 요청마다 Controller 에서 Session 을 통해 로그인 정보가 남아 있는지를 확인 해야 한다. 인터셉터를 이용하면 요청에 대해서 Interceptor 를 먼저 수행해서 Session 에 로그인 정보가 있는지 확인해 주게되며, 중복 코드가 줄어들게 된다.
HandlerInterceptor 인터페이스와 HandlerInterceptorAdapter 추상 클래스가 지원된다.
링크
[SPRING] @AuthenticationPrincipal - 4
내용
인증된 객체를 사용할 경우 사용하는 애너테이션이다.
-
검증용 애너테이션을 만든다.
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface AuthenticationPrincipal { boolean required() default true; }
-
토큰 검증 기능을 구현한다.
public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver { private AuthService authService; public AuthenticationPrincipalArgumentResolver(AuthService authService) { this.authService = authService; } @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(AuthenticationPrincipal.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { String token = AuthorizationExtractor.extract((HttpServletRequest) webRequest.getNativeRequest()); return authService.findMemberByToken(token); } }
-
Configuration 을 구현한다.
@Configuration public class AuthenticationPrincipalConfig implements WebMvcConfigurer { private final AuthService authService; public AuthenticationPrincipalConfig(AuthService authService) { this.authService = authService; } @Override public void addArgumentResolvers(List argumentResolvers) { argumentResolvers.add(createAuthenticationPrincipalArgumentResolver()); } @Bean public AuthenticationPrincipalArgumentResolver createAuthenticationPrincipalArgumentResolver() { return new AuthenticationPrincipalArgumentResolver(authService); } }