Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 의존성 연결 및 로그인 뼈대코드 구현 #6

Merged
merged 9 commits into from
Aug 1, 2024
2 changes: 2 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ plugins {
id 'org.springframework.boot' version '3.3.2'
id 'io.spring.dependency-management' version '1.1.6'
id "org.asciidoctor.jvm.convert" version "4.0.2"
id 'java-test-fixtures'
}

bootJar.enabled = false
Expand All @@ -21,6 +22,7 @@ subprojects {
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.asciidoctor.jvm.convert'
apply plugin: 'java-test-fixtures' // fixtures-module 사용

configurations {
compileOnly {
Expand Down
4 changes: 4 additions & 0 deletions backend/pcloud-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ dependencies {
implementation project(":pcloud-domain")
implementation project(":pcloud-infrastructure")

// testFixtures import
testImplementation(testFixtures(project(":pcloud-domain")))
testImplementation(testFixtures(project(":pcloud-common")))

// spring web
implementation 'org.springframework.boot:spring-boot-starter-web'

Expand Down
10 changes: 10 additions & 0 deletions backend/pcloud-api/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ifndef::snippets[]
:snippets: ./build/generated-snippets
endif::[]
= e-market
:doctype: book
:toc: left
:source-highlighter: highlightjs
:sectlinks:

include::member.adoc[]
30 changes: 30 additions & 0 deletions backend/pcloud-api/docs/asciidoc/member.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
= Auth API 문서
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 3

== 회원가입을 진행한다 (POST /api/signup)

=== Request

include::{snippets}/member-controller-test/do_signup/request-fields.adoc[]
include::{snippets}/member-controller-test/do_signup/http-request.adoc[]

=== Response

include::{snippets}/member-controller-test/do_signup/response-fields.adoc[]
include::{snippets}/member-controller-test/do_signup/http-response.adoc[]

== 로그인을 진행한다 (POST /api/login)

=== Request

include::{snippets}/member-controller-test/do_login/request-fields.adoc[]
include::{snippets}/member-controller-test/do_login/http-request.adoc[]

=== Response

include::{snippets}/member-controller-test/do_login/response-fields.adoc[]
include::{snippets}/member-controller-test/do_login/http-response.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.api.global.config;

import com.api.global.config.interceptor.auth.LoginValidCheckerInterceptor;
import com.api.global.config.interceptor.auth.ParseMemberIdFromTokenInterceptor;
import com.api.global.config.interceptor.auth.PathMatcherInterceptor;
import com.api.global.config.resolver.AuthArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

import static com.api.global.config.interceptor.auth.support.HttpMethod.DELETE;
import static com.api.global.config.interceptor.auth.support.HttpMethod.GET;
import static com.api.global.config.interceptor.auth.support.HttpMethod.OPTIONS;
import static com.api.global.config.interceptor.auth.support.HttpMethod.PATCH;
import static com.api.global.config.interceptor.auth.support.HttpMethod.POST;

@RequiredArgsConstructor
@Configuration
public class AuthConfig implements WebMvcConfigurer {

private final AuthArgumentResolver authArgumentResolver;
private final ParseMemberIdFromTokenInterceptor parseMemberIdFromTokenInterceptor;
private final LoginValidCheckerInterceptor loginValidCheckerInterceptor;

@Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(parseMemberIdFromTokenInterceptor());
registry.addInterceptor(loginValidCheckerInterceptor());
}

private HandlerInterceptor parseMemberIdFromTokenInterceptor() {
return new PathMatcherInterceptor(parseMemberIdFromTokenInterceptor)
.excludePathPattern("/**", OPTIONS);
}

private HandlerInterceptor loginValidCheckerInterceptor() {
return new PathMatcherInterceptor(loginValidCheckerInterceptor)
.excludePathPattern("/**", OPTIONS)
.addPathPatterns("/members/test", GET, POST, PATCH, DELETE);
}

@Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(authArgumentResolver);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.api.config;
package com.api.global.config;

import com.api.config.filter.CorsCustomFilter;
import com.api.global.config.filter.CorsCustomFilter;
import jakarta.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.api.config.filter;
package com.api.global.config.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.api.global.config.interceptor.auth;

import com.api.global.config.interceptor.auth.support.AuthenticationContext;
import com.api.global.config.interceptor.auth.support.AuthenticationExtractor;
import com.common.auth.TokenProvider;
import com.common.exception.AuthException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import static com.common.exception.AuthExceptionType.SIGNATURE_INVALID_EXCEPTION;

@RequiredArgsConstructor
@Component
public class LoginValidCheckerInterceptor implements HandlerInterceptor {

private final TokenProvider tokenProvider;
private final AuthenticationContext authenticationContext;

@Override
public boolean preHandle(final HttpServletRequest request,
final HttpServletResponse response,
final Object handler) throws Exception {
String token = AuthenticationExtractor.extract(request)
.orElseThrow(() -> new AuthException(SIGNATURE_INVALID_EXCEPTION));

Long memberId = tokenProvider.extract(token);
authenticationContext.setAuthentication(memberId);

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.api.global.config.interceptor.auth;

import com.api.global.config.interceptor.auth.support.AuthenticationContext;
import com.api.global.config.interceptor.auth.support.AuthenticationExtractor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@RequiredArgsConstructor
@Component
public class ParseMemberIdFromTokenInterceptor implements HandlerInterceptor {

private final LoginValidCheckerInterceptor loginValidCheckerInterceptor;
private final AuthenticationContext authenticationContext;

@Override
public boolean preHandle(final HttpServletRequest request,
final HttpServletResponse response,
final Object handler) throws Exception {
if (AuthenticationExtractor.extract(request).isEmpty()) {
authenticationContext.setAnonymous();
return true;
}

return loginValidCheckerInterceptor.preHandle(request, response, handler);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.api.global.config.interceptor.auth;

import com.api.global.config.interceptor.auth.support.HttpMethod;
import com.api.global.config.interceptor.auth.support.PathContainer;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;

public class PathMatcherInterceptor implements HandlerInterceptor {

private final HandlerInterceptor handlerInterceptor;
private final PathContainer pathContainer;

public PathMatcherInterceptor(final HandlerInterceptor handlerInterceptor) {
this.handlerInterceptor = handlerInterceptor;
this.pathContainer = new PathContainer();
}

@Override
public boolean preHandle(final HttpServletRequest request,
final HttpServletResponse response,
final Object handler) throws Exception {
if (pathContainer.isNotIncludedPath(request.getServletPath(), request.getMethod())) {
return true;
}
return handlerInterceptor.preHandle(request, response, handler);
}

public PathMatcherInterceptor addPathPatterns(final String pathPattern, final HttpMethod... httpMethod) {
pathContainer.addIncludePatterns(pathPattern, httpMethod);
return this;
}

public PathMatcherInterceptor excludePathPattern(final String pathPattern, final HttpMethod... pathMethod) {
pathContainer.addExcludePatterns(pathPattern, pathMethod);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.api.global.config.interceptor.auth.support;

import com.common.exception.AuthException;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;

import java.util.Objects;

import static com.common.exception.AuthExceptionType.LOGIN_INVALID_EXCEPTION;

@RequestScope
@Component
public class AuthenticationContext {

private static final Long ANONYMOUS_MEMBER = -1L;

private Long memberId;

public void setAuthentication(final Long memberId) {
this.memberId = memberId;
}

public Long getPrincipal() {
if (Objects.isNull(this.memberId)) {
throw new AuthException(LOGIN_INVALID_EXCEPTION);
}

return memberId;
}

public void setAnonymous() {
this.memberId = ANONYMOUS_MEMBER;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.api.global.config.interceptor.auth.support;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;

import java.util.Optional;

public class AuthenticationExtractor {

private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER = "Bearer";
private static final String HEADER_SPLIT_DELIMITER = " ";
private static final int TOKEN_TYPE_INDEX = 0;
private static final int TOKEN_VALUE_INDEX = 1;
private static final int VALID_HEADER_SPLIT_LENGTH = 2;

public static Optional<String> extract(final HttpServletRequest request) {
String header = request.getHeader(AUTHORIZATION_HEADER);

if (!StringUtils.hasText(header)) {
return Optional.empty();
}

return extractFromHeader(header.split(HEADER_SPLIT_DELIMITER));
}

public static Optional<String> extractFromHeader(final String[] headerParts) {
if (headerParts.length == VALID_HEADER_SPLIT_LENGTH &&
headerParts[TOKEN_TYPE_INDEX].equals(BEARER)) {
return Optional.ofNullable(headerParts[TOKEN_VALUE_INDEX]);
}

return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.api.global.config.interceptor.auth.support;

public enum HttpMethod {

GET,
POST,
PUT,
PATCH,
DELETE,
OPTIONS,
HEAD,
TRACE,
CONNECT,
ANY;

public boolean matches(final String pathMethod) {
return this == ANY ||
this.name().equalsIgnoreCase(pathMethod);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.api.global.config.interceptor.auth.support;

import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import java.util.ArrayList;
import java.util.List;

public class PathContainer {

private final PathMatcher pathMatcher;
private final List<PathRequest> includePatterns;
private final List<PathRequest> excludePatterns;

public PathContainer() {
this.pathMatcher = new AntPathMatcher();
this.includePatterns = new ArrayList<>();
this.excludePatterns = new ArrayList<>();
}

public boolean isNotIncludedPath(final String targetPath, final String pathMethod) {
boolean isExcludePattern = excludePatterns.stream()
.anyMatch(pathPattern -> pathPattern.matches(pathMatcher, targetPath, pathMethod));

boolean isNotIncludePattern = includePatterns.stream()
.noneMatch(pathPattern -> pathPattern.matches(pathMatcher, targetPath, pathMethod));

return isExcludePattern || isNotIncludePattern;
}

public void addIncludePatterns(final String path, final HttpMethod... method) {
for (HttpMethod httpMethod : method) {
includePatterns.add(new PathRequest(path, httpMethod));
}
}

public void addExcludePatterns(final String path, final HttpMethod... method) {
for (HttpMethod httpMethod : method) {
excludePatterns.add(new PathRequest(path, httpMethod));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.api.global.config.interceptor.auth.support;

import org.springframework.util.PathMatcher;

public class PathRequest {

private final String path;
private final HttpMethod httpMethod;

public PathRequest(final String path, final HttpMethod httpMethod) {
this.path = path;
this.httpMethod = httpMethod;
}

public boolean matches(final PathMatcher pathMatcher,
final String targetPath,
final String pathMethod) {
return pathMatcher.match(path, targetPath) &&
httpMethod.matches(pathMethod);
}
}
Loading
Loading