ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • springboot2.7 + JWT + RSA256(openssl)
    WEB/BACK 2024. 7. 3. 10:36
    반응형

     

    후,, 인터넷에 deprecated 된 코드들 너무 많아서 직접 올리는 게시물

    DEPRECATED 예시
    
    // JWT 토큰 생성
        public String createToken(String userPk, List<String> roles, PrivateKey key) {
            Claims claims = Jwts.claims().setSubject(userPk); // JWT payload 에 저장되는 정보단위
            claims.put("roles", roles); // 정보는 key / value 쌍으로 저장된다.
            Map<String, Object> header = new HashMap<>();
            header.put("alg", "RS256");
            header.put("typ", "JWT");
            Date now = new Date();
            return Jwts.builder()
                    .setHeader(header) // 알고리즘과 토큰 타입을 헤더에 넣어줌
                    .setClaims(claims) // 유저의 이름(userPk)등이 담겨있음
                    .setIssuedAt(now) // 토큰 발행 시간 정보 iat
                    .setExpiration(new Date(now.getTime() + tokenValidTime)) // set Expire Time 언제까지 유효한지.
                    .signWith(SignatureAlgorithm.RS256, key)  // 사용할 암호화 알고리즘과
                    .compact();
        }

     

    signwith 안쓰임, builder 하고 set이런 형식 안쓰임.

     

    참고로 jwt 생성시에 alg가 hs256이 탑재 되어있고 rs256은 약간 더 복잡하다

     


     

    RSA256 알고리즘 사용하려면 pricate key, public key 두 가지가 필요하다.

    키는직접 파일로 떨궈서 사용하는 방법과 java단에서 만드는 방법 두가지가 있다.

    1. Getting private key, public key (직접 파일 만들기)

    openssl genrsa -out private_key.pem 2048
    openssl rsa -pubout -in private_key.pem -out public_key.pem
    openssl pkcs8 -topk8 -in private_key.pem -inform pem -out private_key_pkcs8.pem -outform pem -nocrypt

    ^ 위에 명령어를 수행하면 3가지 파일이 만들어진다.

    그중에서 private key, public key 를 사용한다. 첫 줄에 2048로 하게되면 256인거고 저 숫자를 변경하면 rsa512 등으로 변경할 수 있다.

    비대칭키는 소수와 같아서 6= 2x3 처럼 양쪽이 모두 있어야 복호화가 가능하다.

     

    key pair 를 java에서 만드는법 포스팅 >> https://bluemint.tistory.com/114

    2. 파일을 읽어와서 객체로 만든다.

    String privateKeyContent = new String(Files.readAllBytes(Paths.get(new FileSystemResource("folder/private_key_pkcs8.pem").getURI())));
    String publicKeyContent = new String(Files.readAllBytes(Paths.get(new FileSystemResource("folder/public_key.pem").getURI())));
    
    //System.out.println(privateKeyContent);
    System.out.println(publicKeyContent);
    
    privateKeyContent = privateKeyContent.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "");
    publicKeyContent = publicKeyContent.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");;
    
    KeyFactory kf = KeyFactory.getInstance("RSA");
    
    PKCS8EncodedKeySpec keySpecPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyContent));
     privateKey = kf.generatePrivate(keySpecPKCS8);
    
    X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyContent));
     publicKey = (RSAPublicKey) kf.generatePublic(keySpecX509);

     

    만약 키 파일을 resources에 위치시킨다면 이렇게 작성가능

    String privateKeyContent = new String(Files.readAllBytes(Paths.get(ClassLoader.getSystemResource("private_key_pkcs8.pem").toURI())));

     

    3. jwt 생성

    public String createToken( PrivateKey privateKey) {
    	// 현재 시간 가져오기
            LocalDateTime now = LocalDateTime.now();
    
            // 30분 뒤 시간 계산
            LocalDateTime thirtyMinutesLater = now.plusMinutes(30);
            //LocalDateTime thirtyMinutesLater = now.minusMinutes(30);
            
            // 30분 뒤 시간을 초로 변환
            long secondsSinceEpoch = thirtyMinutesLater.atZone(ZoneId.systemDefault()).toEpochSecond();
    
            // 초를 Date 객체로 변환
            Date dateThirtyMinutesLater = new Date(secondsSinceEpoch * 1000);
    
            // 결과 출력
            System.out.println("현재 시간: " + new Date());
            System.out.println("30분 뒤 시간: " + dateThirtyMinutesLater);
    		
            Map<String, Object> user = new HashMap<>();
            user.put("id", "redmon");
            user.put("email", "redmon@world.io");
    
            return Jwts.builder().claim("user", user).expiration(dateThirtyMinutesLater)
                    .signWith(privateKey).compact();
        }

    주의할 점은 expiration 이 '초'라는 것이다. claim의 exp는 optional이며 쓰지 않을시 만료시간이 없다. (=무제한)

    4. token validation

    rsa 에서는 publickey, token 을 가지고 복호화를 한다.

    public boolean validateToken( PublicKey publicKey, String token) {
        try {
            Jws<Claims> claims = Jwts.parser().verifyWith(publicKey).build().parseSignedClaims(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

     

    만약에 토큰 시간이 만료됐거나 알고리즘이 잘못된 경우   Jws<Claims> claims = Jwts.parser().verifyWith(publicKey).build().parseSignedClaims(token); 에서 바로 에러가 발생하게 되지만 따로  exception 처리를 해주고 싶은 경우에는 아래처럼 추가해줄 수 있다.

     

    long exp =  (long) claims.getPayload().get("exp");
    long currentSeconds = Instant.now().getEpochSecond();
    
    //alg이름이 RS256아닌경우 에러 
    if(!claims.getHeader().get("alg").toString().equals("RS256")) {
        return false;
    }
    
    if(exp > currentSeconds) {
        return true;
    
    }else {
        return false;
    }

     

    5. claim 정보 출력

    public Claims extractAllClaims( PublicKey publicKey,String token) {
    		return Jwts.parser().verifyWith(publicKey).build().parseSignedClaims(token).getPayload();
    	}

     

    반응형

    댓글

Designed by Tistory.