[Spring] JWT

2024. 10. 16. 23:22·🌱 Spring/Spring Boot

토큰 기반 인증이란? 🤔

토큰 기반 인증은 서버가 클라이언트에게 고유한 토큰을 발급하여 사용자를 구분하는 방식입니다. 클라이언트는 서버로부터 받은 토큰을 저장하고, 이후 요청 시 해당 토큰을 함께 전송하여 인증을 받습니다.

토큰을 전달하고 인증받는 과정

  1. 클라이언트가 ID와 비밀번호를 서버에 전달하며 인증을 요청합니다.
  2. 서버는 ID와 비밀번호를 확인하여 유효한 사용자라면 Access Token과 Refresh Token을 생성하여 클라이언트에게 응답합니다.
  3. 클라이언트는 서버에서 받은 토큰을 저장합니다.
  4. 인증이 필요한 API 요청 시 Access Token을 함께 전송합니다.
  5. 서버는 전달받은 토큰이 유효한지 검증하고, 유효하다면 요청을 처리합니다.
  6. Access Token이 만료된 경우 Refresh Token을 사용해 새로운 Access Token을 발급받을 수 있습니다.

JWT (JSON Web Token)

JWT는 토큰 기반 인증에서 많이 사용되는 형식입니다. JWT는 헤더(Header), 내용(Payload), 서명(Signature) 세 부분으로 이루어져 있으며, 각각의 부분은 점(.)으로 구분됩니다.

  • 헤더(Header): 토큰의 타입과 해싱 알고리즘 정보를 담고 있습니다.
  • 내용(Payload): 토큰과 관련된 정보(클레임, Claim)를 담고 있으며, 등록된 클레임, 공개 클레임, 비공개 클레임으로 구분됩니다.
    • 등록된 클레임: 토큰 발급자(iss), 토큰 제목(sub), 만료 시간(exp) 등의 정보를 담고 있습니다.
    • 공개 클레임: 공개 가능한 정보를 담고 있으며, 주로 URI 형식으로 이름을 정합니다.
    • 비공개 클레임: 서버와 클라이언트 간에만 공유되는 비공개 정보를 담고 있습니다.
  • 서명(Signature): 토큰이 조작되지 않았음을 확인하기 위한 서명입니다.

리프레시 토큰 (Refresh Token)

리프레시 토큰은 Access Token이 만료되었을 때 새로운 Access Token을 발급받기 위해 사용되는 토큰입니다. 리프레시 토큰은 서버와 클라이언트가 각각 저장하며, Access Token보다 더 긴 유효기간을 가집니다.

토큰 기반 인증의 특징

  • 무상태성: 서버는 클라이언트의 상태를 유지하지 않으므로 확장성이 높습니다.
  • 확장성: 서버가 여러 대여도 클라이언트는 동일한 토큰을 사용하여 인증을 받을 수 있습니다.
  • 무결성: 서명(Signature)을 통해 토큰이 조작되지 않았음을 보장할 수 있습니다.

토큰 기반 인증의 활용 🧐

  • 사용자 인증 (Authentication): 로그인 후 서버는 사용자 정보를 기반으로 토큰을 발급하며, 이후 클라이언트는 해당 토큰을 요청 헤더에 포함시켜 서버에 요청을 보냅니다.
  • 권한 부여 (Authorization): JWT는 사용자의 권한 정보를 포함할 수 있어 특정 리소스에 대한 접근 권한을 검증할 수 있습니다.
  • 정보 교환: 서버 간 신뢰할 수 있는 정보 교환을 위해 사용되며, 데이터의 무결성을 보장합니다.

토큰 기반 인증 구현 예시

1. 의존성 추가 (build.gradle)

// jwt 설정하기 위한 dependencies
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-gson:0.11.5'

2. 토큰 제공자 추가하기

application.properties 설정:

jwt.issuer=kkongdo@gmail.com
jwt.secret_key=study-springboot

JwtProperties 클래스:

@Getter
@Setter
@Component
@ConfigurationProperties("jwt")
public class JwtProperties {
    private String issuer;
    private String secretKey;
}

3. 토큰 생성 및 검증 (TokenProvider 클래스)

@Service
@RequiredArgsConstructor
public class TokenProvider {
    private final JwtProperties jwtProperties;

    public String generateToken(User user, Duration expiredAt) {
        Date now = new Date();
        return makeToken(new Date(now.getTime() + expiredAt.toMillis()), user);
    }

    private String makeToken(Date expiry, User user) {
        Date now = new Date();
        return Jwts.builder()
                .setHeaderParam(Header.TYPE, Header.JWT_TYPE)
                .setIssuer(jwtProperties.getIssuer())
                .setIssuedAt(now)
                .setExpiration(expiry)
                .setSubject(user.getEmail())
                .claim("id", user.getId())
                .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey())
                .compact();
    }

    public boolean validToken(String token) {
        try {
            Jwts.parser()
                    .setSigningKey(jwtProperties.getSecretKey())
                    .parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public Authentication getAuthentication(String token) {
        Claims claims = getClaims(token);
        Set<SimpleGrantedAuthority> authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
        return new UsernamePasswordAuthenticationToken(new org.springframework.security.core.userdetails.User(
                claims.getSubject(), "", authorities), token, authorities);
    }

    private Claims getClaims(String token) {
        return Jwts.parser()
                .setSigningKey(jwtProperties.getSecretKey())
                .parseClaimsJws(token)
                .getBody();
    }
}

4. 토큰 인증 필터 (TokenAuthenticationFilter 클래스)

@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {
    private final TokenProvider tokenProvider;
    private static final String HEADER_AUTHORIZATION = "Authorization";
    private static final String TOKEN_PREFIX = "Bearer";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION);
        String token = getAccessToken(authorizationHeader);
        if (tokenProvider.validToken(token)) {
            Authentication authentication = tokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }

    private String getAccessToken(String authorizationHeader) {
        if (authorizationHeader != null && authorizationHeader.startsWith(TOKEN_PREFIX)) {
            return authorizationHeader.substring(TOKEN_PREFIX.length());
        }
        return null;
    }
}

5. 토큰 API 구현 (TokenApiController)

@RestController
@RequiredArgsConstructor
public class TokenApiController {
    private final TokenService tokenService;

    @PostMapping("/api/token")
    public ResponseEntity<CreateAccessTokenResponse> createNewAccessToken(@RequestBody CreateAccessTokenRequest request) throws IllegalAccessException {
        String newAccessToken = tokenService.createNewAccessToken(request.getRefreshToken());
        return ResponseEntity.status(HttpStatus.CREATED).body(new CreateAccessTokenResponse(newAccessToken));
    }
}

'🌱 Spring > Spring Boot' 카테고리의 다른 글

[Spring] Spring Security  (1) 2024.10.16
[Spring] Spring Batch - Batch와 Job, Step  (1) 2024.10.16
[Spring] Spring MVC  (0) 2024.10.16
[Spring] @Annotation  (0) 2024.10.16
[Spring] Spring AOP  (0) 2024.10.16
'🌱 Spring/Spring Boot' 카테고리의 다른 글
  • [Spring] Spring Security
  • [Spring] Spring Batch - Batch와 Job, Step
  • [Spring] Spring MVC
  • [Spring] @Annotation
kkongdo
kkongdo
kkongdo 님의 블로그 입니다.
  • kkongdo
    숲을 바라보며 나무를 심는 아이
    kkongdo
  • 전체
    오늘
    어제
    • 분류 전체보기 (32)
      • 🌏 Web (0)
      • ☕ Java (5)
      • 🌱 Spring (9)
        • Spring Boot (7)
        • Spring Data JPA & QueryDSL (2)
      • 🗂️ Database (5)
      • 💻 CS (12)
        • 운영체제 (4)
        • 네트워크 (5)
        • 자료구조 (3)
      • 🗃️Git (1)
      • 🔍 Algorithm (0)
      • 📡 DevOps (0)
        • Docker (0)
      • 🔭 ETC (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • GitHub
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    네트워크
    CS
    SpringMVC
    QueryDSL
    자료구조
    OS
    운영체제
    java
    springbatch
    db
    SpringSecurity
    조인
    데이터베이스
    JPA
    네트워크기기
    복잡도
    스케줄링
    DI
    spring
    @annotation
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
kkongdo
[Spring] JWT
상단으로

티스토리툴바