
이 시리즈에서는 JPA 예시 코드를 기반으로, 실제 운영 환경에서 각기 다른 도메인끼리의 통신을 가정한 RESTful API로 디벨롭한다.
* 기본 웹 프로젝트 세팅 완료된 상태에서 시작, 테스트는 POSTMAN 활용디벨롭 단계
- Interceptor 생성 및 적용
- API Key 검증 로직 구현
- JWT 발급, 인증/인가 로직 구현
- 클라이언트 용 인증 로직 구현
- 서버용 JWT 발급 로직 구현
- 서버 인터셉터 내 검증 구현
- JWT의 형식 검증
- JWT의 서명(Signature) 검증
- 클레임(예: iss, exp, scope) 검증.
- OAuth 2.0 기반 인증/인가 로직 구현
- 클라이언트 용 Access Token 발급 로직 구현
- 서버 인터셉터 내 검증 구현
- Access Token의 형식 검증
- 인증 서버에 검증 요청(옵션, 원격 검증)
- Access Token의 클레임(예: scope, exp) 검증
HTTP 인증(Authorization)은 ID/PW 인증 방식처럼 HTTP 인증으로 서버에 접근하는 클라이언트가 권한을 갖고 있는지를 확인한다.
HTTP 통신은 클라이언트-서버 간에 요청(Request)과 응답(Response)으로 HTML 문서 및 웹 리소스를 호출하며 발생한다. 클라이언트(ex: 웹브라우저)가 서버에 요청을 보내면, 서버는 요청한 리소스를 돌려준다.
HTTP 통신 시 보호된 서버 리소스를 접근하는 클라이언트에는 인증 정보를 확인한다. HTTP 인증 프레임워크는 요청 시 아래와 같은 인증 헤더를 사용한다.
Authorization: <type> <credentials>
대표적으로 응답 받는 형태는 다음과 같다.
인증 정보는 인증 방식(Type)에 따라 달라진다.
Basic 인증 방식은 말 그대로 가장 기본적인 인증 방식이다. 인증 정보로 사용자 ID와 비밀번호를 사용하는데, base64로 인코딩한 “사용자ID:비밀번호” 문자열을 Basic과 함께 인증 헤더에 입력한다.
Authorization: Basic base64({USERNAME}:{PASSWORD})
Bearer 인증 방식은 OAuth 2.0 프레임워크에서 사용하는 토큰 인증 방식이다. Bearer 와 토큰을 인증 헤더에 입력한다.
Authorization: Bearer <token>
OAuth 프레임워크는 다양한 서비스 또는 서버 사이에 안전하게 데이터를 전송할 수 있게 해주며, 제 3자의 클라이언트에게 보호된 리소스를 제한적으로 접근하게 해준다.
OAuth 프레임워크에는 리소스 소유자, 클라이언트, 인증 서버, 리소스 서버가 있다.
Bearer 토큰은 OAuth 프레임워크에서 액세스 토큰으로 사용하는 토큰의 유형이다.
Bearer 토큰은 불투명(Opaque)한 문자열로, 토큰의 형태는 인증 서버에서 정의하기 나름이다. 16진수의 문자열이나 JSON Web Token(JWT)을 사용하기도 한다. 어떠한 형태를 사용하든, Bearer 토큰은 클라이언트가 해석할 수 없는 형태여야 하며 사용자의 정보를 전달하면 안된다. 또한 서버에서 클라이언트의 권한을 확인할 수 있는 메타데이터가 토큰에 인코딩되어 있어야 하며, 충분히 복잡한 알고리즘을 사용하여 발급해야 한다.
우선 인터셉터에서 간단하게 헤더에 있는 API key 값을 검증하는 로직만 구현하였다. 여기서 사용 될 토큰 값과 관련된 로직은 다음 포스팅에서 구현한다.
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
// 컨트롤러 호출 전에 실행
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 예: 인증 확인
String authToken = request.getHeader("Authorization");
if (authToken == null || !authToken.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 Unauthorized
response.getWriter().write("Unauthorized");
return false; // 요청 처리 중단
}
String token = authToken.substring(7); // "Bearer " 이후의 토큰 부분
try {
// 토큰 검증 로직
if (isValidToken(token)) {
return true; // 검증 성공 시 요청 계속 진행
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403 Forbidden
response.getWriter().write("Invalid token");
return false;
}
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // 500 Error
response.getWriter().write("Error validating token");
return false;
}
}
...
// 토큰 유효성 검증 메서드
private boolean isValidToken(String token) {
try {
if (token.equals("J3iHLt7...")) { //보안키로 포스팅에는 전체 기재하지 않음
return true;
}
return true; // 실제 검증 로직 추가 후 결과 리턴
} catch (Exception e) {
return false;
}
}
}
