데브코스 34일차 TIL

Heesu Song·2025년 4월 19일

데브코스 - 백엔드

목록 보기
32/32
post-thumbnail

❗️Reverse Proxy


Web Server 쪽에 위치하여 클라이언트의 접근을 최초로 받아 리퀘스트에 해당하는 Web Server에 배분해주는 역할을 한다.

클라이언트는 웹 서비스에 접근할 때 프록시로 요청하게 되고, 프록시가 배후(reverse)의 서버로부터 데이터를 가져오는 방식

리버스 프록시는 서버가 누구인지를 감추는 역할을 한다. 클라이언트는 리버스 프록시 서버를 먼저 호출하게 되기에 실제 서버의 IP를 알 수 없다.

내부 서버가 직접 서비스를 제공해도 되지만 이렇게 구성하는 이유는 보안 때문

→ 흔히 서버 아키텍처로 구성하는 web server(nginx) - WAS(tomcat) 분리 형태를 reverse proxy라고 보면 된다.


GrantedAuthority?


스프링 시큐리티에서 권한들과 역할은 기본적으로 GrantedAuthority에 저장된다. 

  • GrantedAuthority는 권한이나 역할의 이름을 반환하는 메소드를 제공
public interface GrantedAuthority extends Serializable {

    String getAuthority();

}
  • SimpleGrantedAuthority 클래스는 GrantedAuthority 인터페이스의 기본으로 구현되는 객체

RFC


Request for Comments"의 약자로, 인터넷 기술에 대한 연구, 혁신, 기법 등을 담은 문서

  • 콘텐츠에 대해 특별한 제한은 없지만 주로 프로토콜(protocol) 및 파일 형식등이 주요 주제이며 승인된 문서는 유일한 일련 번호를 갖게 되며 "RFC-일련번호" 형식으로 불린다

JWT


Json Web Token

사용자가 누구인지 기억하기 위한 수단

헤더.페이로드(본문).시그니처(서명)


  • alg : Signature에서 사용하는 알고리즘
  • typ : 토큰 타입

Signature에서 사용하는 알고리즘은 대표적으로 RS256(공개키/개인키)와 HS256(비밀키(대칭키))가 있다.


Payload(본문..?)


사용자 정보의 한 조각인 클레임(claim)이 들어있다.

  • sub : 토큰 제목(subject)
  • aud : 토큰 대상자(audience)
  • iat : 토큰이 발급된 시각 (issued at)
  • exp : 토큰의 만료 시각 (expired)

Signature


Signature는 헤더와 페이로드의 문자열을 합친 후에, 헤더에서 선언한 알고리즘과 key를 이용해 암호한 값


JWT 실습


JWT Token 생성


    public String issue() {

        String token = Jwts.builder()
                .subject("Hello World") // 페이로드에 제목
                .claim("spring", "easy") // 키, 값
                .issuedAt(new Date()) //유효기간 지정 -> 언제 발급 되었는지
                .expiration(new Date(new Date().getTime() + 60000L)) // 만료기간
                .signWith(Keys.hmacShaKeyFor("A5A10BC17B4E653C78BDD55FFFD9DEE3BE5790CD6F3F64DC1E82E791F9821C17".getBytes()), Jwts.SIG.HS256) // 서명 -> 어떻게 암호화 할지를 기재
                .compact();

        return "";
    }

Jwts → Jwt 객체를 만듬


설정값을 yml파일에 작성해서 관리


custom:
  jwt:
    validation:
      exp: 60000

yml 파일에서 생성하고 Service파일에서 @Value 를 통해 사용가능(경로 지정)

    @Value("${custom.jwt.validation.exp}")
    private Long exp;

ConfigurationProperties

  • .yml 파일에 있는 property를 자바 클래스에 값을 가져와서(바인딩) 사용할 수 있게 해주는 어노테이션
  • @ConfigurationProperties에 prifix를 설정
  • yml 파일에 있는 경로에 대해 바인딩
@Getter
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "custom.jwt")
public class JwtConfiguration {

그리고 메인 애플리케이션 실행 클래스에 @ConfigurationPropertiesScan를 붙여준다.


JWT - 보안 강화 방법


서버에서 정보를 관리하지 않기 때문에 서버는 정상적인 유저의 토큰인지, 해커에게 탈취된 토큰인지 알 수가 없다. → 추가적인 보안 강화가 필요

💡Http Only, Secure, Refresh Token 등 다양한 방법이 존재


Token 인증


인증용 token(Access Token)과 갱신용 Token(Refresh Token)으로 관리

Refresh Token은 DB에 들어가 있어야됨 → 나중에 죽여야(?)되기 때문에 (Black List)

인증용 token(Access Token)는 OAuth에서 사용중


Refresh Token


  • Access Token의 유효기간을 짧게 하면 사용자가 로그인을 자주 해야 함 --> 불편함을 줄일 수 있는 방법으로 나온 것이 Refresh Token
  • Refresh Token의 유효기간이 만료된다면 사용자가 재 로그인을 해주면 됨 -->Refresh Token도 탈취 가능성이 있기 때문에 적절한 유효기간 설정 필요


Access Token이 만료되면


  1. Access Token을 헤더에 실어 요청 보냄.
  2. 서버에서 Access Token의 만료를 확인 후 권한 없음 신호 보냄.(서버 -> 사용자)
  3. 사용자는 Refresh Token + Access Token을 서버로 보냄.
  4. Access Token 조작 확인 후 수신 Refresh Token & DB의 Refresh Token을 비교해 동일하고 유효기간 유효 시 새로운 Access Token을 발급.(사용자(프론트엔드)에서 Access Token의 Payload를 통해 유효기간을 알 수 있음. 따라서 프론트엔드 단에서 API 요청 전에 토큰이 만료됐다면 바로 재발급 요청을 할 수도 있음.)

BlackList


사용자가 로그아웃을 요청했을 때, 클라이언트 측에서는 캐시에 저장된 jwt를 제거하지만 서버에서는 jwt를 무력화할 수단이 없다.

  • 때문에 서버에서는 Jwt에 대한 블랙 리스트를 만들어 Jwt의 유효기간이 만료될 때 까지 Redis와 같은 DB에서 관리
  • 만약 사용자가 Jwt를 이용하여 인가를 요청했을 시, Black List의 조회를 통해 사용자가 로그아웃한 Jwt인지 아닌지를 판별하는 것

BlackList 문제 해결 방법


Vault에 Key와 Value를 맞길 수 있다.

(값을 동적으로 넣어줄 수 있음)

→ 외부 API에게 secret key 를 맡기는 것.


JWT 토큰 발급(생성) - 최종


  • JwtTokenProvider 클래스 - issue 메서드
    public String issue(Long id, Role role) {

        String token = Jwts.builder()
                .subject(id.toString()) // 페이로드에 제목
                .claim("Role", role) // 키, 값
                .issuedAt(new Date()) //유효기간 지정 -> 언제 발급 되었는지
                .expiration(new Date(new Date().getTime() + jwtConfiguration.getValidation().getExp())) // 만료기간
                .signWith(getSecretKey(), Jwts.SIG.HS256) // 서명 -> 어떻게 암호화 할지를 기재
                .compact();

        return token;
    }

    private SecretKey getSecretKey() {
        return Keys.hmacShaKeyFor(jwtConfiguration.getSecrets().getAppKey().getBytes());
    }

토큰 유효성 검사


  • JwtTokenProvider 클래스 - validate 메서드
  try{
            Jwts.parser()
                    .verifyWith(getSecretKey())
                    .build()
                    .parseSignedClaims(token);

            return true;
        } catch (JwtException e) {
            log.error("token: {}", token);
        }

verifyWith 에 어떤걸로 서명했는지를 넣어주면 검사 가능 → parser 생성

Jws<Claims> claimsJws = parser.parseSignedClaims(token);

token 문자열을 읽어서 서명 검증, 안에 있는 Claims (payload) 를 꺼냄.

Claims →JWT에 담긴 key-value

JwtException

토큰 자체가 맛이 갔다고(?) 알려주는 에러


토큰안의 값을 꺼내는 작업


  • JwtTokenProvider 클래스 - parseJwt 메서드
        Jws<Claims> parsed = Jwts.parser()
                .verifyWith(getSecretKey())
                .build()
                .parseSignedClaims(token);

parsed는 분석되어서 나온 결과

        parsed.getPayload().getSubject();
        parsed.getPayload().get("Role");

get()안에 키를 넣어주면 내가 원하는 값을 꺼내올 수 있다.


Refresh Token과 Access Token 생성


  • 기존 토큰 생성 코드(issue 메서드)
    //토큰 생성
    public String issue(Long id, Role role) {

        String token = Jwts.builder()
                .subject(id.toString()) // 페이로드에 제목
                .claim("Role", role) // 키, 값
                .issuedAt(new Date()) //유효기간 지정 -> 언제 발급 되었는지
                .expiration(new Date(new Date().getTime() + jwtConfiguration.getValidation().getExp())) // 만료기간
                .signWith(getSecretKey(), Jwts.SIG.HS256) // 서명 -> 어떻게 암호화 할지를 기재
                .compact();

        return token;
    }

이 상태에서는 한 종류의 토큰만 생성이 가능 → refresh Token과 Access Token을 생성하기 위해서는 구조를 바꿔줘야함

  • JwtConfiguration 클래스
    @Getter
    @RequiredArgsConstructor
    public static class Validation {
        private final Long access;
        private final Long refresh;
    }

유효 시간을 다르게 설정해주고

  • JwtTokenProvider 클래스
    public String issueAccessToken(Long id, Role role) {
        return issue(id, role, jwtConfiguration.getValidation().getAccess());
    }

    public String issueRefreshToken(Long id, Role role) {
        return issue(id, role, jwtConfiguration.getValidation().getRefresh());

    }
  • 기존 issue 메서드에 expTime 매개변수를 추가로 받아주고
    .expiration(new Date(new Date().getTime() + expTime)) 만료 시간을 expTime 으로 설정해준다.
  • 그리고 유효 시간을 각 토큰에 맞게 넣어줄 수 있도록 메서드 생성

실제 Token


successHandler() → 인증이 성공했을 때 어떤 작업을 수행할지

  • OAuth2SuccessHandler 클래스

SimpleUrlAuthenticationSuccessHandler 을 상속받는다.

onAuthenticationSuccess 를 오버라이드 해서 인증 성공했을때

FilterChain

Chain을 넣어주지 않으면 중단됨 → doChain이 꼭 필요

success handler부터 멘탈 터져서 작성 불가…ㅎ

profile
Abong_log

0개의 댓글