[우아한테크코스] 5월 12일 TIL

3 minute read

[infra] 인증과 인가

인증: 이 사람 맞는지 입증(웹에서 로그인의 과정)
인가: 서비스에 등록된 유저의 신원 입증(웹에서 글을 쓸 수 있는 권한)

사용자 —-인증을 위한 정보—-> 서버 <—> db
<— 인증결과 —-
사용자 —-데이터 요청 + 인증결과—-> 서버
<— 요청 데이터 —

  • 세션을 통한 인가
    사용자 —- 로그인 —-> 서버 <—> db + session storage
    <— session id —-
    사용자 —- 데이터 요청 + 쿠키 —-> 서버
    <— 요청 데이터 —

  • 토큰을 통한 인가
    사용자 —- 로그인 —-> 서버 <—> db
    <— token —-
    사용자 —- 데이터 요청 + token —-> 서버
    <— 요청 데이터 —

  1. 인증하기 request header
  2. 인증 유지하기 browser
  3. 안전하게 인증하기 server
  4. 효율적으로 인증하기 token
  5. 다른 채널을 통해 인증하기 oauth

server와 client는 무상태성

  1. browser storage에 저장하고 사용한다면? -> 모두가 볼 수 있다.
  2. server의 db를 사용(session을 사용한다.) -> 서버를 여러개 두면 session이 없어서 문제 발생(server 하나 하나가 session 가지는 경우)
  3. session 스토리지의 사용 -> client가 많아지면 터진다.
    세션 저장소에 인증정보 저장하고 session id 사용자에게 반환
  4. token을 이용해 인증과 인가를 관리하자.
    Json Web Token: security key로 토큰 발급
    token을 훔쳐보면 안되니까 만료 기한 사용 -> refresh token과 expire time 사용
    토큰이 상태를 관리해줘서 세션이 필요 없다
  • 인증을 위한 Spring MVC Config
    권한 체크가 필요한 로직에 @RequestHeader(value="Authorization")을 추가하는데 모든데에 필요하다면?
    Interceptor: Client와 Controller 사이로 오가는 요청과 응답
    권한 체크 여부의 역할을 HandleInterceptorAdapter로 분리하고 preHandle이라는 메서드도 있고, 응답될 때 사용하는 메서드도 있다.
    이거를 WebMvcConfigurer로 설정한다.
    HandlerMethodArgumentResolver를 이용해 인자에 어떠한 클래스가 있을 때 동작하도록 하는 것이다.

지하철 노선관리 피드백

layer 별 역할을 명확히 주도록 리팩토링을 진행하자

  • spring MVC application architecture
    client <-> ** presentation layer <-> business <-> persistence ** <-> db

비즈니스 로직을 도메인에 넣을 것인가? 서비스에 넣을 것인가?
도메인에 비즈니스 로직을 넣어서 객체가 어떠한 일을 하는지 알 수 있도록 하자.

[Spring] mvc-config

  1. controller에 @AuthenticationPrincipal을 검증이 필요한 파라미터 앞에 등록
     @GetMapping("/members/me")
     public ResponseEntity<LoginMember> findMemberOfMine(@AuthenticationPrincipal LoginMember loginMember) {
         return ResponseEntity.ok().body(loginMember);
     }
    
  2. 이때의 @AuthenticationPrincipal은 어노테이션으로 @interface라고 하고 아래와 같이 저장 필요
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AuthenticationPrincipal {
     boolean required() default true;
    }
    
  3. HandlerMethodArgumentResolver를 사용해 어떠한 어노테이션이 있는 경우에 실행하도록 하는 메서드 작성
    public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
    
     @Override
     public boolean supportsParameter(MethodParameter parameter) {
         return parameter.hasParameterAnnotation(AuthenticationPrincipal.class); //해당 어노테이션 있는 경우를 찾는다.  
     }
    
     @Override
     public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
         return new LoginMember(1L, "email", 120);
     }   //들어온 값에 의해 인증되었으면 그에 해당하는 객체를 넘긴다.  
    } 
    
  4. HandlerInterceptorAdater를 구현해 헤더로 들어온 토큰 검증
    public class LoginInterceptor extends HandlerInterceptorAdapter {
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         String accessToken = request.getHeader("Authorization");
         if (accessToken == null) {
             throw new AuthorizationException();
         }
    
         return super.preHandle(request, response, handler);
     }
    }
    

들어온 토큰이 옳지 않은 경우 @ResponseStatus(HttpStatus.UNAUTHORIZED)를 이용해 예외를 만들어 처리

@ResponseStatus(HttpStatus.UNAUTHORIZED)
public class AuthorizationException extends RuntimeException {
    public AuthorizationException() {
    }

    public AuthorizationException(String message) {
        super(message);
    }
}
  1. @Configuration이 등록된 클래스를 통해 요청 시 응답할 것들 미리 설정
    • 요청에 따른 정적 페이지 매핑
      ```java @Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer {

    @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(“/”).setViewName(“home”); } }

           - 요청 시 Interceptor 동작  
    ```java 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
     registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
    }
    
       - HandlerMethodArgumentResolver를 통해 인증받은 객체 반환 (??)   ```java @Override public void addArgumentResolvers(  List<HandlerMethodArgumentResolver> argumentResolvers) {  argumentResolvers.add(new HeaderVersionArgumentResolver()); } ```  
    

총 정리: client 요청 -> spring 구동 -> dispatcher servlet 실행, 이때 configuration을 통한 설정 세팅 -> 요청 들어오면 configuration에 따른 interceptor, view, argumentResolver 등이 있는지 보고, 보고 있으면 실행하도록 한다.
이때 argumentResolver의 경우 supportsParameter를 통해 특정 어노테이션이 있는지 확인하고, 있으면 resolveArgument를 하고, 이때 parameter는 해당 어노테이션이 달린 요소(LoginMember)가 된다. resolve에서 NativeWebRequest에 대해 풀어서 요소를 반환하곤 한다.

[Spring] auth

  • session
    spring: 사용자 접근 -> 인증되면 httpSession에 setAttribute(“SESSION_KEY”, password)으로 인증 정보 저장, 얘가 sessionStorage
    이후 쿠키로 sesssion id 관리하는 건 다른 애가 수행 상태 존재
    브라우저가 종료 되면 세션, 쿠키 파기
  • token
    사용자 접근 -> 인증되면 특정한 암호화를 거친 token 반환 -> 사용자의 localStorage에 저장 -> 만료 기한이 지나면 재사용 불가
    로그인 할때마다 localStorage에서 꺼내 쓰는 방법 무상태성 유지 가능