본문 바로가기
프로젝트/LinkBook

토큰(JWT)기반 인증 도입

by 걸어가는 신사 2022. 8. 23.

1. Token 기반 인증

  • 토큰 기반 인증 시스템은 인증받은 사용자들에게 토큰을 발급하고, 서버에 요청을 할 때 헤더에 토큰을 함께 보내도록 하여 유효성 검사를 한다.
  • 이러한 시스템에서는 더 이상 사용자의 인증 정보를 서버나 세션에 유지하지 않고 클라이언트 측에서 들어오는 요청만으로 작업을 처리한다.
    • 상태를 유지하지 않으므로 stateless 한 구조를 갖는다.
    • 서버(세션) 기반 인증의 단점을 극복할 수 있다.

 

2. JWT (Json Web Token)

💡 JWT란 인증에 필요한 정보들을 Token에 담아 암호화시킨 토큰을 의미한다.

JWT 구조

JWT는 .을 구분자로 나누어지는 세 가지 문자열의 조합이다. JWT는 3가지 구조로 나누어진다.

Header

  • alg : 암호화할 해싱 알고리즘, typ : 토큰의 타입

Payload

  • Payload는 토큰에 담을 정보를 가지고 있다.
  • 주로 클라이언트의 고유 ID 값 및 유효기간등이 포함되는 영역이다.
  • Key-Value 형식으로 이루어진 한 쌍의 정보를 Claim이라고 부른다.

Signature

  • Signature는 인코딩된 Header와 Payload를 더한 뒤 비밀키로 해싱하여 생성한다.
  • Header와 Payload는 단순히 인코딩 된 값이기 때문에 제 3자가 복호화 및 조작할 수 있지만, Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없다.
  • Signature는 토큰의 위변조 여부를 확인하는데 사용한다.

 

3. JWT 구현

application-jwt.yaml

jwt:
  access-header : Access-Token
  refresh-header : Refresh-Token
  issuer: prgrms
  client-secret:
  access-token-expiry-seconds: 3600
  refresh-token-expiry-seconds: 1209600
  • access-header, refresh-header : HTTP header 이름
  • issuer : 토큰 발급자
  • client-secret : HS512 알고리즘으로 서명을 수행할 것이기 때문에 키 길이를 64바이트로 해야 한다.
  • AccessToken 만료시간 : 1시간
  • RefreshToken 만료시간 : 2주

JwtConfigure

@Component
@Getter @Setter
@ToString
@ConfigurationProperties(prefix = "jwt")
public class JwtConfigure {

    private String accessHeader;

    private String refreshHeader;

    private int accessTokenExpirySeconds;

    private int RefreshTokenExpirySeconds;

    private String issuer;

    private String clientSecret;

}
  • @ConfigurationProperties(prefix = "jwt")
    • application-jwt.yaml 에 있는 정보를 가져와서 필드에 넣어준다.
  • @Component
    • 해당 클래스를 Bean으로 등록한다.

Jwt

@ToString
public class Jwt {

    private final String issuer;
    private final String clientSecret;
    private final int accessTokenExpirySeconds;
    private final int refreshTokenExpirySeconds;
    private final Algorithm algorithm;
    private final JWTVerifier jwtVerifier;

    public Jwt(String issuer, String clientSecret, int accessTokenExpirySeconds, int refreshTokenExpirySeconds) {
        this.issuer = issuer;
        this.clientSecret = clientSecret;
        this.accessTokenExpirySeconds = accessTokenExpirySeconds;
        this.refreshTokenExpirySeconds = refreshTokenExpirySeconds;
        this.algorithm = Algorithm.HMAC512(clientSecret);
        this.jwtVerifier = com.auth0.jwt.JWT.require(algorithm)
            .withIssuer(issuer)
            .build();
    }

    public String createAccessToken (Claims claims) {
        Date now = new Date();
        JWTCreator.Builder builder = com.auth0.jwt.JWT.create();
        builder.withIssuer(issuer);
        builder.withIssuedAt(now);
        if (accessTokenExpirySeconds > 0) {
            builder.withExpiresAt(new Date(now.getTime() + accessTokenExpirySeconds * 1_000L));
        }
        builder.withClaim("email", claims.email);
        builder.withArrayClaim("roles", claims.roles);
        return builder.sign(algorithm);
    }

    public String createRefreshToken (Claims claims) {
        Date now = new Date();
        JWTCreator.Builder builder = com.auth0.jwt.JWT.create();
        builder.withIssuer(issuer);
        builder.withIssuedAt(now);
        if (refreshTokenExpirySeconds > 0) {
            builder.withExpiresAt(new Date(now.getTime() + refreshTokenExpirySeconds * 1_000L));
        }
        builder.withClaim("email", claims.email);
        builder.withArrayClaim("roles", claims.roles);
        return builder.sign(algorithm);
    }

    public Claims verify(String token) {
        return new Claims(jwtVerifier.verify(token));
    }

    public Boolean isExpiredToken(String accessToken) {
        DecodedJWT decodedJWT = com.auth0.jwt.JWT.decode(accessToken);
        return decodedJWT.getExpiresAt().after(new Date());
    }

    static public class Claims {
        private String email;
        private String[] roles;
        private Date iat;
        private Date exp;

        private Claims() {}

        Claims(DecodedJWT decodedJWT) {
            Claim email = decodedJWT.getClaim("email");
            if(!email.isNull()) {
                this.email = email.asString();
            }
            Claim roles = decodedJWT.getClaim("roles");
            if(!roles.isNull()) {
                this.roles = roles.asArray(String.class);
            }
            this.iat = decodedJWT.getIssuedAt();
            this.exp = decodedJWT.getExpiresAt();
        }

        public static Claims from(String email, String[] roles) {
            Claims claims = new Claims();
            claims.email = email;
            claims.roles = roles;
            return claims;
        }

        public Map<String, Object> asMap() {
            Map<String, Object> map = new HashMap<>();
            map.put("email", email);
            map.put("roles", roles);
            map.put("iat", iat());
            map.put("exp", exp());
            return map;
        }

        public long iat() {
            return iat != null ? iat.getTime() : -1;
        }

        public long exp() {
            return exp != null ? exp.getTime() : -1;
        }

        public String getEmail() {
            return email;
        }

        public String[] getRoles() {
            return roles;
        }

        public Date getIat() {
            return iat;
        }

        public Date getExp() {
            return exp;
        }
    }

}

  • Jwt 클래스
    • 해당 클래스는 토큰의 생성과 검증 역할과 책임이 있다.
    • JWT 생성을 위한 sign 메서드
    • JWT 검증을 위한 verify 메서드
  • Claims Inner 클래스
    • JWT의 payload 부분에 해당한다.
    • email : 사용자 이메일
    • roles : 사용자 권한
    • iat : 토큰이 발급된 시간
    • exp : 토큰 만료시간

WebSecurityConfigure

public class WebSecurityConfigure {

    private final JwtConfigure jwtConfigure;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public Jwt jwt() {
        return new Jwt(
            jwtConfigure.getIssuer(),
            jwtConfigure.getClientSecret(),
            jwtConfigure.getAccessTokenExpirySeconds(),
            jwtConfigure.getRefreshTokenExpirySeconds()
        );
    }
  • PasswordEncoder, Jwt를 Bean으로 등록한다.

 

 

GitHub - prgrms-web-devcourse/Team-03-LinkBook-BE: 강력3팀 북마크 공유 프로젝트(백엔드)

강력3팀 북마크 공유 프로젝트(백엔드). Contribute to prgrms-web-devcourse/Team-03-LinkBook-BE development by creating an account on GitHub.

github.com

자세한 코드 위 링크에서 보실 수 있습니다.

 

Reference

https://tecoble.techcourse.co.kr/post/2021-05-22-cookie-session-jwt/

반응형

댓글