스프링 부트 프로젝트에서
JWT 토큰을 생성하기
dependencies
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2'
// 다른 종속성들...
}
yml
token:
expiration_time: 86400000
secret: user_token
86400000 = 24시간
Filter에서 JWT 생성해주는 메서드
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
String userName = ((User)authResult.getPrincipal()).getUsername();
UserDto userDetails = userService.getUserDetailsByEmail(userName);
String token = Jwts.builder()
.setSubject(userDetails.getUserId())
.setExpiration(new Date(System.currentTimeMillis() +
Long.parseLong(env.getProperty("token.expiration_time"))))
.signWith(SignatureAlgorithm.HS512, env.getProperty("token.secret"))
.compact();
response.addHeader("token", token);
response.addHeader("userId", userDetails.getUserId());
}
토큰 방식이 필요한 이유
기존의 전통적인 인증 시스템은 username, password등의 정보를 요청하면 서버에서 이에 대한 인증을 처리하고 인증 성공시 session과 cookie를 설정하였다.
그런데 문제는 자바에서 발급하는 인증 세션이 타 기종에서는 호환이 안된다는 것이다.
모바일 어플리케이션 같은 경우 html 페이지가 아닌 Json이나 다른 포맷이 사용되는 것이 예이다.
또한 다른 언어로의 호환시에도 공유 불가하다는 어려움이 있다.
따라서 토큰으로서의 공통적인 인증 공유 방식이 필요하며, 이로서 토큰 기반 인증 시스템이 등장하였다.
JWT = Json Web Token
- 인증 헤더 내에서 사용되는 토큰 포맷
- 두 개의 시스템끼리 안전한 통신 가능
장점
- 클라이언트 독립적인 서비스 (stateless)
- CDN
- No Cookie-Session (No csrf)
- 지속적인 토큰 저장
API Gateway에서 JWT 토큰 검증의 필요성
일반적인 회원가입과 로그인 시에는 post 요청으로 진행이 되며 아직 토큰이 없는 상태이기 때문에 별도의 작업이 필요하지 않다.
즉, 인증 로직에서는 토큰 검증이 필요하지 않다.
그러나 인가 로직에서, 즉 로그인한 유저에 대한 검증 이후 인증된 유저에 대한 정보를 반환해야 할 때는 필요하다.
따라서 yml 파일에서 다음과 같이 filter를 추가해주고 이를 구현해준다.
인증된 유저에 대한 GET 요청에 대한 검증을 위한 필터 추가
- id: user-service
uri: lb://USER-SERVICE
predicates:
- Path=/user-service/**
- Method=GET
filters:
- RemoveRequestHeader=Cookie
- RewritePath=/user-service/(?<segment>.*), /$\{segment}
- AuthorizationHeaderFilter
API Gateway에서 jwt를 검증하는 필터 클래스 구현하기
@Component
@Slf4j
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
Environment env;
public AuthorizationHeaderFilter(Environment env) {
super(Config.class);
this.env = env;
}
public static class Config {
// Put configuration properties here
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
return onError(exchange, "No authorization header", HttpStatus.UNAUTHORIZED);
}
String authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0);
String jwt = authorizationHeader.replace("Bearer", "");
if (!isJwtValid(jwt)) {
return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED);
}
return chain.filter(exchange);
};
}
// mono, flux -> webflux : 단위 값이면 mono로 반환
private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(httpStatus);
log.error(err);
return response.setComplete();
}
private boolean isJwtValid(String jwt) {
boolean returnValue = true;
String subject = null;
try {
subject = Jwts.parser().setSigningKey(env.getProperty("token.secret"))
.parseClaimsJws(jwt).getBody()
.getSubject();
} catch (Exception ex) {
returnValue = false;
}
if (subject == null || subject.isEmpty()) {
returnValue = false;
}
return returnValue;
}
}
'Programming > Java, Spring' 카테고리의 다른 글
[Spring Cloud] bus, rabbitmq를 이용해 서버 연결하고 actuator를 이용한 갱신 집합화하기 (0) | 2023.06.01 |
---|---|
[Spring Cloud] Config yml 설정, actuator를 이용한 자동 반영 (0) | 2023.06.01 |
[Spring Cloud] API Gateway Routing 시 마이크로서비스 식별 주소값을 제외하고 실용적인 uri를 전달하는 방식 (0) | 2023.06.01 |
[Spring Cloud] Users MicroService를 Api Gateway에 등록하기 (0) | 2023.05.31 |
[Spring Cloud] API Gateway Service / routing, filter (0) | 2023.05.31 |