OAuth2 이란?
OAuth는 사용자의 민감한 인증 정보를 제 3자 애플리케이션에 직접 제공하지 않고도, 해당 애플리케이션이 사용자의 권한을 얻어 보호된 리소스에 안전하게 접근할 수 있도록 돕는 인증 및 권한 부여 프로토콜이다.

예를 들면 우리가 캘린더 서비스를 개발했을 때, 사용자가 구글 계정을 이용해 캘린더 서비스에 로그인하려고 하면, 사용자의 인증 및 권한 부여는 구글이 담당한다.
이 과정에서 우리의 캘린더 서비스는 사용자 인증 단계에 관여하지 않는다.
사용자가 구글 계정을 통해 인증에 성공하면, 구글은 캘린더 서비스에 접근 권한(토큰)을 부여한다.
이를 통해 사용자는 구글 계정을 활용해 우리의 캘린더 서비스에 로그인하고 리소스를 안전하게 관리할 수 있다.
즉, OAuth를 사용하면,
- 사용자는 민감한 정보를 캘린더 서비스와 직접 공유할 필요가 없어 보안상의 이점을 가진다.
- 캘린더 서비스는 사용자의 민감한 정보를 관리할 필요가 없어 운영상의 부담과 보안 위험을 줄일 수 있다.
JWT이란?
JWT(JSON Web Token)는 JSON 형식의 자가 포함된 토큰으로, 클레임(claim)을 포함하여 사용자에 대한 정보를 전달한다.
- 헤더, 페이로드, 서명 세 부분으로 구성된다.
- jwt는 암호화를 통해 데이터의 무결성과 출처를 보장한다.
데이터 무결성이란?
토큰의 내용이 전송 중에 변조되지 않았음을 보장하는 개념이다.
토큰의 서명 검증 과정에서 서버는 받은 jwt 토큰의 헤더와 페이로드를 확인한 후, 시크릿 키로 서명을 재생성해보고, 전송된 jwt 토큰 서명과 비교한다. 이때 두 서명이 일치하면, 데이터가 변조되지 않았다는 것이 보장된다.
클라우드 게이트웨이의 Pre 필터에서 JWT 인증 진행하기 실습

1) Auth에서 토큰 발급받기
로그인을 담당하는 Auth 서비스 어플리케이션을 생성하고 설정한다. 로그인을 진행하면 토큰을 발급받고 이 토크을 사용하여 Gateway를 호출하는 역할을 한다.
우리는 이 실습에 jwt를 사용하기 때문에 세션을 사용하지 않는다고 Config에 작성해준다.
// 세션 관리 정책을 정의합니다. 여기서는 세션을 사용하지 않도록 STATELESS로 설정합니다.
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
- AuthController
컨트롤러에서 /auth/signIn 으로 user_id 를 넘겨 로그인하도록 한다. 응답으로는 access_token을 넘겨준다.
@RestController
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@GetMapping("/auth/signIn")
public ResponseEntity<?> createdAuthToken(@RequestParam String user_id) {
return ResponseEntity.ok(new AuthResponse(authService.createAccessToken(user_id)));
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class AuthResponse {
private String access_Token;
}
}
- AuthService
토큰을 생성한다.
@Service
public class AuthService {
@Value("${spring.application.name}")
private String issuer; // 토큰 발급한 어플리케이션 이름
private Long accessExpiration = 3600000L;
private final SecretKey secretKey;
public AuthService(@Value("${service.jwt.secret-key}") String secretKey) {
this.secretKey = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretKey));
}
public String createAccessToken(String user_id) { // 토큰 발급
return Jwts.builder()
.claim("user_id", user_id)
.claim("role", "ADMIN")
.issuer(issuer)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + accessExpiration))
.signWith(secretKey, SignatureAlgorithm.HS512)
.compact();
}
}
실제로 Auth에 user_id를 담아 요청해보면 토큰이 잘 넘어오는 것을 확인할 수 있다.

jwt 사이트에서 이를 번역하면 AuthService에서 넣었던 claims와 같은 내용을 확인할 수 있다.

그럼 이제 게이트웨이가 auth 에서 발급된 토큰을 가지고 인증 인가를 진행할 것이다.
게이트웨이에서 인증 인가 진행하기
- LocalJwtAuthenticationFilter
게이트웨이에서 auth를 호출해 받아온 토큰을 필터에서 검증한다.
@Component
@Slf4j
public class LocalJwtAuthenticationFilter implements GlobalFilter {
@Value("${service.jwt.secret-key}")
private String secretKey;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
if (path.equals("/auth/signIn")) {
return chain.filter(exchange); // 로그인 페이지에 접근할 땐 토큰 인증 x (모두가 접근가능)
}
String token = extractToken(exchange);
if (token == null || !validateToken(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
private String extractToken(ServerWebExchange exchange) {
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
return null; // 토큰이 정확하게 전달되지 않은 경우
}
private boolean validateToken(String token) {
try {
SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64URL.decode(secretKey));
Jws<Claims> claimsJws = Jwts.parser()
.verifyWith(key)
.build().parseSignedClaims(token);
log.info("#####payload :: " + claimsJws.getPayload().toString());
// 추가적인 검증 로직 (예: 토큰 만료 여부 확인 등)을 여기에 추가할 수 있습니다.
return true;
} catch (Exception e) {
return false;
}
}
}
세팅이 끝난 후 이제 게이트웨이 port인 19091에 인증을 요청하면 토큰이 생성되면서 로그인이 잘 되는 것을 볼 수 있다.

나중에 방화벽처리를 해서, 사용자들이 게이트웨이에만 접근할 수 있도록 설정해줘야한다.
그래야 게이트웨이에서 접근하는 사용자들에 대한 인증 인가 처리를 할 수 있다.
*로그인 토큰 없이 product에 요청했을 때

*로그인 토큰을 가지고 product에 요청했을 때


'Backend > MSA' 카테고리의 다른 글
| 241126 분산 추적 (Spring Cloud Sleuth) 및 로깅 (Zipkin) 실습 TIL (0) | 2024.11.26 |
|---|---|
| 241125 컨피그 서버 (Spring Cloud Config) 실습 TIL (2) | 2024.11.26 |
| 241122 API 게이트웨이 실습 TIL (0) | 2024.11.22 |
| 241121 서킷 브레이커 (Resilience4j) 실습 TIL (0) | 2024.11.21 |
| 241121 클라이언트 사이드 로드 밸런싱 (FeignClient) 실습 TIL (2) | 2024.11.21 |