
이번 포스팅에서는 스프링 부트 애플리케이션에서 커스텀 인증 필터를 구현하는 방법을 소개하겠습니다.
특히, 인증 로직을 데이터베이스와 연동하여 처리하는 방법에 대해 다룹니다.
목차
- 시작하며
- 프로젝트 셋업
- 커스텀 인증 필터 구현
- 암호화 로직 구현
- 필터 등록 및 설정
- 마치며
1. 시작하며
API 서버를 운영하면서 특정 요청에 대해 인증을 처리해야 할 때가 많습니다.
이번 포스팅에서는 간단하게 커스텀 인증 필터를 통해 요청을 검증하고, 데이터베이스에 저장된 정보를 바탕으로
인증을 처리하는 방법을 알아보겠습니다.
2. 프로젝트 셋업
먼저, 프로젝트의 build.gradle 파일에 필요한 의존성을 추가합니다.
스프링 부트, Lombok, MySQL 드라이버 등이 필요합니다.
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.myproject'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// MySQL Connector
runtimeOnly 'com.mysql:mysql-connector-j'
}
3. 커스텀 인증 필터 구현
이제 실제로 커스텀 인증 필터를 구현해보겠습니다. AuthFilter 클래스를 생성합니다.
@RequiredArgsConstructor
@Slf4j
public class AuthFilter extends HttpFilter {
private final BlablaDao blablaDao;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);
}
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException {
String method = request.getMethod();
String url = request.getRequestURI();
String timestamp = request.getHeader("x-blabla-timestamp");
String receivedAccessId = request.getHeader("x-blabla-access-id");
String receivedSignature = request.getHeader("x-blabla-signature");
String nowYm = String.valueOf(System.currentTimeMillis() / 1000);
try {
if (!StringUtils.hasText(timestamp) || !StringUtils.hasText(receivedAccessId) || !StringUtils.hasText(receivedSignature)) {
throw new AuthenticationException("인증 헤더 정보가 누락되었습니다!");
}
int calcTm = Integer.parseInt(nowYm) - Integer.parseInt(timestamp);
if(calcTm < -5 || calcTm > 60) {
log.error("[AuthFilter] 인증시간 만료 (1분) 감지");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(CommonResponseFail.responseFail(ExceptionEnum.REQUEST_TIMEOUT.getCode(),ExceptionEnum.REQUEST_TIMEOUT.getMessage())));
return;
}
EteamVO eteamVO = blablaDao.selectEteamByJwtKey(receivedAccessId);
if(eteamVO == null) {
throw new AuthenticationException("해당 access-id에 맞는 팀 정보가 존재하지 않습니다!");
}
String calculatedSignature = SignatureUtil.makeSignature(method, url, timestamp, receivedAccessId, eteamVO.t_jwtkey());
if (!receivedSignature.equals(calculatedSignature)) {
throw new AuthenticationException("인증 암호키가 올바르지 않습니다!");
}
chain.doFilter(request, response);
} catch (AuthenticationException e) {
log.error("[AuthFilter] 인증 로직 중 인증실패 감지 error:", e);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(CommonResponseFail.responseFail(ExceptionEnum.NO_PERMISSION.getCode(),ExceptionEnum.NO_PERMISSION.getMessage())));
} catch (Exception e) {
log.error("[AuthFilter] 인증 로직 중 인증에러 발생 error:", e);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(CommonResponseFail.responseFail(ExceptionEnum.NO_PERMISSION.getCode(), ExceptionEnum.NO_PERMISSION.getMessage())));
}
}
}
4. 서명 로직 구현
메소드, url, 타임스탬프, 액세스키 등 값들을 가지고 서명 로직을 구현해보겠습니다.
public class SignatureUtil {
public static String makeSignature(String method, String url, String timestamp, String accessId, String accessKey) throws Exception {
String space = " ";
String newLine = "\n";
String message = method +
space +
url +
newLine +
timestamp +
newLine +
accessId;
SecretKeySpec signingKey = new SecretKeySpec(accessKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(rawHmac);
}
}
5. 필터 등록 및 설정
마지막으로, 필터를 등록하고 설정해보겠습니다. WebConfig 클래스를 생성하여 필터를 등록합니다.
@Configuration
public class WebConfig {
private final BlablaDao blablaDao;
public WebConfig(BlablaDao blablaDao) {
this.blablaDao = blablaDao;
}
@Bean
public FilterRegistrationBean<AuthFilter> authFilterRegistration() {
FilterRegistrationBean<AuthFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new AuthFilter(blablaDao));
registrationBean.addUrlPatterns("/v1/api/*"); // 필터를 적용할 URL 패턴
return registrationBean;
}
}
이제 모든 설정이 완료되었습니다. 커스텀 인증 필터가 성공적으로 적용되었습니다!
해당 필터를 통해 인증을 완료해야만 해당 컨트롤러로 이동할 수 있습니다.
즉, 지정한 URL 패턴의 HTTP 요청이 들어올 때마다 이 필터를 거쳐야만 컨트롤러로의 접근이 가능합니다.
이를 통해 간단한 인증 로직을 구현할 수 있습니다.
이 포스팅이 도움이 되셨으면 좋겠습니다
질문이나 더 좋은 방법이 있다면 언제든지 댓글로 남겨주세요. Happy coding! 🐸
'개발 > Spring' 카테고리의 다른 글
서킷브레이커는 또 무엇일까... (1) | 2024.11.28 |
---|---|
Spring과 Kafka를 활용한 트랜잭셔널 아웃박스 패턴 구현 (0) | 2024.11.21 |
MSA 아키텍처로의 전환을 고려한 트랜잭션 처리 및 이벤트 기반 설계 (3) | 2024.11.15 |
Junit & Mock 기반 테스트 코드 도입기 (4) | 2024.09.23 |
Spring 애플리케이션에서 로깅 구현하기 (feat. SLF4J) (0) | 2024.07.12 |
깨굴딱지의 코드연못입니다
올챙이가 개구리로 거듭나듯, 끊임없는 노력으로 진화하는 개발자의 길을 걷습니다. 🐸