토큰 기반 인증이란? 🤔
토큰 기반 인증은 서버가 클라이언트에게 고유한 토큰을 발급하여 사용자를 구분하는 방식입니다. 클라이언트는 서버로부터 받은 토큰을 저장하고, 이후 요청 시 해당 토큰을 함께 전송하여 인증을 받습니다.
토큰을 전달하고 인증받는 과정
- 클라이언트가 ID와 비밀번호를 서버에 전달하며 인증을 요청합니다.
- 서버는 ID와 비밀번호를 확인하여 유효한 사용자라면 Access Token과 Refresh Token을 생성하여 클라이언트에게 응답합니다.
- 클라이언트는 서버에서 받은 토큰을 저장합니다.
- 인증이 필요한 API 요청 시 Access Token을 함께 전송합니다.
- 서버는 전달받은 토큰이 유효한지 검증하고, 유효하다면 요청을 처리합니다.
- 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 |
