마트철수

[071] JWT, Api Server Security 본문

KB IT's Your Life/교육

[071] JWT, Api Server Security

마트스 2024. 8. 21. 17:33

 

2024.08.21(수)
 
Spring 12일차

 


 

Spring


 

PART01

  • CH02. 스프링의 특징과 의존성 주입
  • CH03.1 스프링 MVC의 기본 구조
  • CH03.2 스프링 MVC의 Controller 1
  • CH03.3 스프링 MVC의 Controller 2
  • CH03.4 SpringLegacy 업데이트
  • CH04.1 스프링과 MySQL Database
  • CH04.2 MyBatis와 스프링 연동
  • CH05.1 영속, 비즈니스 계층의 CRUD 구현
  • CH05.2 비즈니스 계층
  • CH05.3 프레젠테이션(웹) 계층의 CRUD 구현
  • CH06.1 화면 처리
  • CH06.2 File, Upload, Download

Part 4. Rest API

  • CH07 Rest Controller
  • CH08.1 OpenApi
  • CH08.2 RestTemplate
  • Spring Security
  • CH10.2 로그인과 로그아웃 처리
  • CH10.3 member
  • CH10.4 UserDetails 사용하기
  • CH11 Api Server Security 기본 설정
  • CH11.1 JWT의 이해
  • CH11.2 JWT 자바 라이브러리

 


CH11 Api Server Security 기본 설정

 


JSP에서 로그인한 사용자 정보 보여주기

 

<sec:authorize access="">

  • 인증 여부 판단 및 접근 권한 체크
    • isAnonymous(): 로그인을 하지 않은 경우 True
    • isAuthenticated(): 로그인을 한 경우 True

# logout은 POST 요청 ... GET 요청 불가

# csrf.token 요청함 ... 없으면 404 error

 

sec:authorize access=""

 

 

SecurityConfig 전체 코드

 

 

SecurityConfig.java

 

  • 문자셋 필터
  • 경로별 접근 권한 설정
    • 매번 달라져야함 (★)
    • 예시로, 목록보기와 상세보기는 permitAll()
    • 쓰기, 수정, 삭제는 authenticated()
// 문자셋 필터
public CharacterEncodingFilter encodingFilter() {
    CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
    encodingFilter.setEncoding("UTF-8");
    encodingFilter.setForceEncoding(true);
    return encodingFilter;
}

@Override
    public void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(encodingFilter(), CsrfFilter.class);
        
        // ★ 경로별 접근 권한 설정
        http.authorizeRequests()
            .antMatchers("/security/all").permitAll()
            .antMatchers("/security/admin").access("hasRole('ROLE_ADMIN')")
            .antMatchers("/security/member").access("hasRole('ROLE_MEMBER')");

 

  • 로그인/로그아웃 구현
  • AuthenticationManager 구현 !!
    • auth로 필요한 정보를 취합 및 등록
    • 1) userDetailservice 2) passwordEncoder
http.formLogin() // GET요청 (개발자)
    .loginPage("/security/login") // POST요청
    .loginProcessingUrl("/security/login")
    .defaultSuccessUrl("/"); // 어디로 redirect할지

http.logout() // 로그아웃 설정 시작
    .logoutUrl("/security/logout") // POST: 로그아웃 호출 url
    .invalidateHttpSession(true) // 세션 invalidate
    .deleteCookies("remember-me", "JSESSION-ID") // 삭제할 쿠키 목록
    .logoutSuccessUrl("/security/logout"); // GET: 로그아웃 이후 이동할 페이지
    // 실전에서는 "/" 첫페이지로 이동하게 함
}

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        log.info("configure .........................................");
        auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }
}

 

  • 문자셋을 보안 필터 앞단에 설정하는 이유

보안 필터가 먼저 분석(resolve) → 그리고 문자셋 필터가 작용함 → 그래서 한글로 하면 다 깨짐

 

Api Server Security 기본 설정

 

 

Api 서버를 위한 기본 security 설정

  • cors(Cross Origin Resource Sharing) 허용
    • 서버주소: 포트번호(origin)
    • 다른 서버와도 통신하는 것은 본래 origin 위반임
    • 근데 허용해주면 됨 !!

 

Api Security 기본 설정

  • 접근 제한 무시 경로 설정
  • assets: 정적파일(이미지, css 등), api/member: 회원가입, 로그인
    • /** : 하위경로까지 포함
  // 접근 제한 무시 경로 설정 - resource
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/assets/**", "/*", "/api/member/**");
    }

 

  • cross origin
	// 다양한 도메인에서 서버에 요청을 보낼 수 있다
    // 다른 서버도 접근 가능하도록 (다른 origin)
    @Bean
    public CorsFilter corsFilter() {
//        CORS 설정을 적용한 url 소스 생성
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//        CORS 설정을 위한 객체 생성
        CorsConfiguration config = new CorsConfiguration();
//        자격 증명(쿠키, 인증 헤더 등)을 포함한 요청을 허용하도록 설정
        config.setAllowCredentials(true);
//        모든 도메인에서 오는 요청 허용 ("은 모두라는 의미)
        config.addAllowedOriginPattern("*");
//        모든 헤더 허용
        config.addAllowedHeader("*");
//        모든 HTTP 메서드 허용(GET, POST, PUT, DELETE)
        config.addAllowedMethod("*");
//        설정된 CORS 구성을 모든 경로("/**")에 적용 (하위 경로 포함)
        source.registerCorsConfiguration("/**", config); // 하위 경로까지 포함
//        설정된 소스 기반으로 새로운 CorsFilter 반환
        return new CorsFilter(source);
    }

 

  • 아래 구성은 많이 변경될 것임
  • 필요한 기능을 넣기 위해서
@Override
    public void configure(HttpSecurity http) throws Exception {
// 한글 인코딩 필터 설정
        http.addFilterBefore(encodingFilter(), CsrfFilter.class);
        http.httpBasic().disable() // 기본 HTTP 인증 비활성화
                .csrf().disable() // CSRF 비활성화
                .formLogin().disable() // formLogin 비활성화 관련 필터 해제
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 세션 생성 모드 설정
    }

    // Authentication Manger 구성
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }
}

 

 

JWT의 이해

 

  • Json Web Token
    • JSON 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web 토큰
    • 가장 많이 사용되는 토큰의 형태
  • JWT의 구조
    • Header.Payload.Signature 세 가지로 구성
    • 각 부분은 Base64로 인코딩되어 표현되며, 각각의 구성 요소는 .로 구분
    • 암호화되어있지 않음 !!

JWT의 구조: Payload에 실제 데이터가 들어감

 

 

 

디지털 서명의 원리

 

 

문서 T를 다시 받았는데, 수정 안 했는지에 대한 확신할 수 있나?

나에겐 원본이 없는 상태라서 비교 불가한 상태

 

내가 가지고 있는 키 프레임으로 hashCode를 만들어내서

T + key => hashCode 같은지 확인 !!

 

JWT의 Signature에서 secretKey를 합쳐서 해시코드를 만들어내냄

여기서 사용된 알고리즘이 Hs256임

이건 Header에서 확인 가능 !!

 


 

★ Payload(페이로드)

  • 토큰에서 사용할 정보의 조각들인 클레임(Claim)이 담겨있다
  • key: value → 클레임 !!
  • 발급된 클레임
    • iss: 토큰 발급자
    • exp: 토큰 만료 시간
  • 공개 클레임과 비공개 클레임
    • 비공개 클레인은 사용자 정의 클레임

 

Spring Security + JWT 동작 원리

 

  • 로그인
    1. 클라이언트에서 서버로 ID/PW로 로그인을 요청
      # 사용자가 알게
    2. 서버에서 검증 과정을 거쳐 해당 유저가 존재하면, Access Token + Refresh Token 을 발급
      ㄴ JSON 응답(성공)시, JWT를 발행해줌
      ㄴ 근데 JWT의 유효기간은 짧음 그래서 Refresh Token을 같이 발급해줌
      # 사용자 몰래
      # Refrest Token은 복잡해서 해당 수업에서는 다루지 않음
    3. 클라이언트는 요청 헤더에 2번에서 발급받은 Access Token을 포함하여 API를 요청
      ㄴ 유효하면 로그인한 사용자임을 확인
      # 사용자 몰래
  • 토큰은 브라우저(클라이언트)가 관리

 

Spring Security + JWT 동작 원리

 

  • Access Token
    • 인증된 사용자가 특정 접근할 때 사용되는 토큰
    • 확인이 가능한 토큰이기 때문에 비밀번호와 같은 걸 담으면 안됨
    • 유효 기간이 지나면 만료(expired)
  • Refresh Token
    • Access Token의 갱신을 위해 사용되는 토큰
    • 사용자가 지속적으로 인증 상태를 유지할 수 있도록 도와줌 (매번 로그인 다시 하지 않아도 됨)

 

JWT 자바 라이브러리

 

  • 의존성 추가
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
implementation("io.jsonwebtoken:jjwt-jackson:0.11.5")

 

  • Secret Key 준비
    • 암호화에 사용할 임의의 문자열
    • 개발 시에는 직접 지정 (랜덤으로 만들어지는 게 더 좋음)
private String secretKey = "아주 긴 임의의 문자열 지정";
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); // BASE64 인코딩

// 실전 운영 시 자동으로 생성시키는 방법
private Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 서버가 재기동 되면 key문자열이 갱신되므로 기존 발급 토큰은 사용불가

// 유효 기간
static final long TOKEN_PERIOD= 1000L * 60L * 5L; // 테스트용 5분 – 만기 확인용
// 짧게 5분으로 설정해서, 자동으로 로그아웃되는지 확인하기 위함

 

  • Payload 정보 구성
    • Claims 객체
      • date.getTime() + TOKEN_PERIOD : 현재 시간 + 만료 시간

Payload 정보 구성

 

  • JWT 검증
    • 유효시간 이전이면 true 리턴
    • 토큰이 해석되지 않는 경우 또는 유효 시간 만료인 경우 예외 발생
      ㄴ ExpiredJwtException: 유효 시간 만기 → 로그아웃 처리

JWT 검증

 

 

  • 하단 예외는 RuntimeException임 (필수는 아니지만, 확인해줘야함!)
  • 401 ERROR 응답 필수 → 클라이언트: 로그아웃되었구나
예외 ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException

 

 

API 로그인

  • LoginDTO
    • Spring Security 규약에 따라 username, password 프로퍼티를 가짐
  • UserInfoDTO
    • 로그인 성공 시 응답에 포함시킬 사용자 정보
  • AuthResultDTO
    • 로그인 성공 결과를 나타내는 응답

 

JsonResponse

  • 로그인 결괄르 필터에서 직접 Json 응답하기 윈한 유틸리티 클래스
  • static <T> void send(HttpServletResponse response, T result) throws IOExcetion
    • 200 응답
    • Jackson으로 T를 직렬화한 후 response로 직접 전송
  • static void sendError(HttpServletResponse response, HttpStatus status, String message)
    • 응답 코드와 에러 메세지를 출력

 

API를 통한 로그인 절차

  • JwtUsernamePasswordAuthenticationFilter
    • 로그인 url과 로그인 성공/실패 처리기를 등록
    • url은 /api/auth/login으로 결정 → 해당 url이 오면, login 요청으로 받아들임

JwtUsernamePasswordAuthenticationFilter

  • AuthenticationManager의 인증 절차는 formLogin 때와 동일
  • 약속된 login 요청인 경우 로그인 절차 수행
  • 생성자에서 의존성 주입
    • AuthenticationManager
    • LoginSuccessHandler
    • LoginfailureHandler

 

AuthenticationManaged 주의

 


 

이제 알고리즘 특강 제외

순수하게 Spring 공부가 가능한 일정이 8일 남았다.

 

마지막엔 Vue 연결도 하니깐,

Vue 교재를 다시 읽어보고 있는 중인데 ..

생각보다 더 어려운 것 같다.

 

 

'KB IT's Your Life > 교육' 카테고리의 다른 글

[073] 알고리즘: Dynamic Programming(DP)  (0) 2024.08.23
[072] API 로그인 및 사용자 인증  (0) 2024.08.22
[070] Spring 로그인과 로그아웃 처리  (0) 2024.08.20
[069] Rest API  (0) 2024.08.19
[068] REST API  (2) 2024.08.14