[CS] web 인증, 인가, JWT 구현(쿠키/세션)

호호빵·2022년 12월 19일
0

Computer Science

목록 보기
7/13

인증(Authentication)

: 해당 유저가 실제 유저인지 인증하는 개념
: 비연결성, 무상태

인가(Authorization)

: 해당 유저가 특정 리소스에 접근이 가능한지 허가를 확인하는 개념
: 스프링 시큐리티가 관리해줌

1. 쿠키-세션 방식의 인증

  • 서버가 특정 유저가 로그인 되었다는 상태를 저장하는 방식
  • 인증과 관련된 약간의 정보만 서버가 가지고 있고 유저의 인증관련 최소한의 정보는 저장해서 로그인을 유지 시킴

  1. 사용자가 로그인 요청을 보냅니다.
  2. 서버는 DB의 유저 테이블을 뒤져서 아이디 비밀번호를 대조해봐야겠죠?
  3. 실제 유저테이블의 정보와 일치한다면 인증을 통과한 것으로 보고 “세션 저장소”에 해당 유저가 로그인 되었다는 정보를 넣습니다.
  4. 세션 저장소에서는 유저의 정보와는 관련 없는 난수인 session-id를 발급합니다.
  5. 서버는 로그인 요청의 응답으로 session-id를 내어줍니다.
  6. 클라이언트는 그 session-id를 쿠키라는 저장소에 보관하고 앞으로의 요청마다 세션아이디를 같이 보냅니다. (주로 HTTP header에 담아서 보냅니다!)
  7. 클라이언트의 요청에서 쿠키를 발견했다면 서버는 세션 저장소에서 쿠키를 검증합니다.
  8. 만약 유저정보를 받아왔다면 이 사용자는 로그인이 되어있는 사용자겠죠?
  9. 이후에는 로그인 된 유저에 따른 응답을 내어줍니다.

+ [CS] web 쿠키와 세션



2. JWT 기반 인증

  • JWT(JSON Web Token)
    • Json 포맷을 이용하여 인증에 필요한 정보들을 암호화시킨 Claim 기반의 Web Token
    • 보통 쿠키 저장소에 담겨서 저장된 쿠키 라고 생각하면 됨
  • 쿠키/세션 방식과 유사하게 JWT토큰(Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별

  1. 사용자가 로그인 요청 보냄
  2. 서버는 DB의 유저 테이블에서 ID/PW 대조
  3. 실제 유저테이블의 정보와 일치한다면 인증을 통과한 것으로 보고 유저의 정보를 JWT로 암호화해서 내보냄
  4. 서버는 로그인 요청의 응답으로 JWT 토큰을 내어줌
  5. 클라이언트는 그 토큰을 저장소에 보관하고 앞으로의 요청마다 토큰을 같이 보냄
  6. 클라이언트의 요청에서 토큰을 발견했다면 서버는 토큰을 검증
  7. 이후에는 로그인 된 유저에 따른 응답을 내어줌

JWT를 사용하는 이유

  • 로그인 정보를 서버에 저장하지 않고 클라이언트에 JWT로 암호화하여 저장
    -> JWT 통해 인증 및 인가
  • 모든 서버에서 동일한 Secret Key 소유
    -> Secret Key를 통한 암호화/위조 검증 (복호화 시)
  • 동시 접속자가 많을 때 서버 측 부하 낮춤
  • 클라이언트/서버가 다른 도메인을 사용할 때
  • 단점
    • 구현의 복잡도 증가
    • JWT에 담는 내용이 커질수록 네트워크 비용 증가
    • 기 생성된 JWT를 일부만 만료시킬 방법 없음
    • Secret Key 유출 시 JWT 조작 가능

JWT는 어떻게 생겼나 - jwt.io

  • . 을 사용하여 세 부분으로 나누어짐
  • Header, Payload, Verify Signature
    - Header, Verify Signature : 암호화 관련된 정보 양식
    - Payload : 실제 유저의 정보가 들어있는 부분
  • 위 암호화 문자열을 해독하면 아래와 같은 모습

JWT 구현하기

1. JWT dependency 추가

compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'


# application.properties

jwt.secret.key=7ZWt7ZW0OTntmZTsnbTtjIXtlZzqta3snYTrhIjrqLjshLjqs4TroZzrgpjslYTqsIDsnpDtm4zrpa3tlZzqsJzrsJzsnpDrpbzrp4zrk6TslrTqsIDsnpA=

2. 토큰생성에 필요한 값
3. Header에서 토큰 가져오기
4. JWT 생성
5. JWT 검증
6. JWT에서 사용자 정보 가져오기


@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtil {

	// 2. 토큰생성에 필요한 값 
    // Header Key 값, 사용자 권한 값의 Key, Token 식별자, 토큰 만료시간 
    public static final String AUTHORIZATION_HEADER = "Authorization";
    public static final String AUTHORIZATION_KEY = "auth";
    private static final String BEARER_PREFIX = "Bearer ";
    private static final long TOKEN_TIME = 60 * 60 * 1000L;  // ms 단위, 총 60분

    @Value("${jwt.secret.key}")   // application.properties 에서 가져온 값을 
    private String secretKey;	  // secretKey에 담음
    private Key key;
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    @PostConstruct  // 객체가 생성될때 초기화 해줌
    public void init() {
        byte[] bytes = Base64.getDecoder().decode(secretKey);
        key = Keys.hmacShaKeyFor(bytes);
    }
    // application.properties에 넣은 jwt key -> 64base로 문자열을 변환한 것


    // 3. header에서 토큰을 가져오기
    public String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
            return bearerToken.substring(7);
        }
        return null;
    }

    // 4. JWT 토큰 생성
    public String createToken(String username, UserRoleEnum role) {
        Date date = new Date();

        return BEARER_PREFIX +
                Jwts.builder()
                .setSubject(username)
                .claim(AUTHORIZATION_KEY, role)
                .setExpiration(new Date(date.getTime() + TOKEN_TIME))
                .setIssuedAt(date)
                .signWith(key, signatureAlgorithm)
                .compact();
    }

    // 5. 토큰 검증
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException e) {
            log.info("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
        } catch (ExpiredJwtException e) {
            log.info("Expired JWT token, 만료된 JWT token 입니다.");
        } catch (UnsupportedJwtException e) {
            log.info("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
        } catch (IllegalArgumentException e) {
            log.info("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
        }
        return false;
    }

    // 6. 토큰에서 사용자 정보 가져오기
    public Claims getUserInfoFromToken(String token) {
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    }

}




스프링 숙련 - 인증, 인가
눈에 띈 벨로그 글 - 깔끔하게 정리한 쿠키,세션,JWT

profile
하루에 한 개념씩

0개의 댓글