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

[BE] 행사 관리자 비밀번호 추가 #213

Merged
merged 10 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,16 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
Expand Down
40 changes: 40 additions & 0 deletions server/src/main/java/server/haengdong/application/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package server.haengdong.application;


import java.util.Map;
import server.haengdong.domain.TokenProvider;
import server.haengdong.exception.AuthenticationException;

public class AuthService {

private static final String TOKEN_NAME = "eventToken";
private static final String CLAIM_SUB = "sub";

private final TokenProvider tokenProvider;

public AuthService(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}

public String createToken(String eventId) {
Map<String, Object> payload = Map.of(CLAIM_SUB, eventId);

return tokenProvider.createToken(payload);
}

public String findEventIdByToken(String token) {
validateToken(token);
Map<String, Object> payload = tokenProvider.getPayload(token);
return (String) payload.get(CLAIM_SUB);
}

private void validateToken(String token) {
if (!tokenProvider.validateToken(token)) {
throw new AuthenticationException();
}
}

public String getTokenName() {
return TOKEN_NAME;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import server.haengdong.application.request.EventAppRequest;
import server.haengdong.application.request.EventLoginAppRequest;
import server.haengdong.application.request.MemberUpdateAppRequest;
import server.haengdong.application.response.ActionAppResponse;
import server.haengdong.application.response.EventAppResponse;
Expand All @@ -19,6 +20,7 @@
import server.haengdong.domain.event.Event;
import server.haengdong.domain.event.EventRepository;
import server.haengdong.domain.event.EventTokenProvider;
import server.haengdong.exception.AuthenticationException;
import server.haengdong.exception.HaengdongErrorCode;
import server.haengdong.exception.HaengdongException;

Expand All @@ -42,15 +44,13 @@ public EventAppResponse saveEvent(EventAppRequest request) {
}

public EventDetailAppResponse findEvent(String token) {
Event event = eventRepository.findByToken(token)
.orElseThrow(() -> new HaengdongException(HaengdongErrorCode.NOT_FOUND_EVENT));
Event event = getEvent(token);

return EventDetailAppResponse.of(event);
}

public List<ActionAppResponse> findActions(String token) {
Event event = eventRepository.findByToken(token)
.orElseThrow(() -> new HaengdongException(HaengdongErrorCode.NOT_FOUND_EVENT));
Event event = getEvent(token);

List<BillAction> billActions = billActionRepository.findByAction_Event(event).stream()
.sorted(Comparator.comparing(BillAction::getSequence)).toList();
Expand Down Expand Up @@ -92,8 +92,7 @@ private List<ActionAppResponse> getActionAppResponses(
}

public MembersAppResponse findAllMembers(String token) {
Event event = eventRepository.findByToken(token)
.orElseThrow(() -> new HaengdongException(HaengdongErrorCode.NOT_FOUND_EVENT));
Event event = getEvent(token);

List<String> memberNames = memberActionRepository.findAllUniqueMemberByEvent(event);

Expand All @@ -102,8 +101,7 @@ public MembersAppResponse findAllMembers(String token) {

@Transactional
public void updateMember(String token, String memberName, MemberUpdateAppRequest request) {
Event event = eventRepository.findByToken(token)
.orElseThrow(() -> new HaengdongException(HaengdongErrorCode.NOT_FOUND_EVENT));
Event event = getEvent(token);
String updatedMemberName = request.name();
validateMemberNameUnique(event, updatedMemberName);

Expand All @@ -117,4 +115,16 @@ private void validateMemberNameUnique(Event event, String updatedMemberName) {
throw new HaengdongException(HaengdongErrorCode.DUPLICATED_MEMBER_NAME);
}
}

public void validatePassword(EventLoginAppRequest request) throws HaengdongException {
Event event = getEvent(request.token());
if (event.isSamePassword(request.password())) {
throw new AuthenticationException();
}
}

private Event getEvent(String token) {
return eventRepository.findByToken(token)
.orElseThrow(() -> new HaengdongException(HaengdongErrorCode.NOT_FOUND_EVENT));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import server.haengdong.domain.event.Event;

public record EventAppRequest(String name) {
public record EventAppRequest(String name, String password) {

public Event toEvent(String token) {
return new Event(name, token);
return new Event(name, password, token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package server.haengdong.application.request;

public record EventLoginAppRequest(String token, String password) {
}
44 changes: 44 additions & 0 deletions server/src/main/java/server/haengdong/config/AdminInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package server.haengdong.config;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import server.haengdong.application.AuthService;
import server.haengdong.exception.AuthenticationException;
import server.haengdong.infrastructure.auth.AuthenticationExtractor;

@Slf4j
public class AdminInterceptor implements HandlerInterceptor {

private final AuthService authService;
private final AuthenticationExtractor authenticationExtractor;

public AdminInterceptor(AuthService authService, AuthenticationExtractor authenticationExtractor) {
this.authService = authService;
this.authenticationExtractor = authenticationExtractor;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.trace("login request = {}", request.getRequestURI());

String method = request.getMethod();
if (method.equals("GET")) {
return true;
}

validateToken(request);

return true;
}

private void validateToken(HttpServletRequest request) {
String token = authenticationExtractor.extract(request, authService.getTokenName());
String tokenEventId = authService.findEventIdByToken(token);
String eventId = request.getRequestURI().split("/")[2];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c: 정규표현식이나 다른 방식으로 해야할 것 같은데 뾰족한 방법이 없는 것 같네요..

if (!tokenEventId.equals(eventId)) {
throw new AuthenticationException();
}
}
}
27 changes: 0 additions & 27 deletions server/src/main/java/server/haengdong/config/WebConfig.java

This file was deleted.

65 changes: 65 additions & 0 deletions server/src/main/java/server/haengdong/config/WebMvcConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package server.haengdong.config;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import server.haengdong.application.AuthService;
import server.haengdong.domain.TokenProvider;
import server.haengdong.infrastructure.auth.AuthenticationExtractor;
import server.haengdong.infrastructure.auth.JwtTokenProvider;
import server.haengdong.infrastructure.auth.TokenProperties;

@RequiredArgsConstructor
@EnableConfigurationProperties(TokenProperties.class)
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

private final TokenProperties tokenProperties;

@Value("${cors.max-age}")
private Long maxAge;

@Value("${cors.allowed-origins}")
private String[] allowedOrigins;

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(allowedOrigins)
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*")
.maxAge(maxAge);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/events");
}

@Bean
public AdminInterceptor adminInterceptor() {
return new AdminInterceptor(authService(), authenticationExtractor());
}

@Bean
public AuthService authService() {
return new AuthService(tokenProvider());
}

@Bean
public TokenProvider tokenProvider() {
return new JwtTokenProvider(tokenProperties);
}

@Bean
public AuthenticationExtractor authenticationExtractor() {
return new AuthenticationExtractor();
}
}
12 changes: 12 additions & 0 deletions server/src/main/java/server/haengdong/domain/TokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package server.haengdong.domain;

import java.util.Map;

public interface TokenProvider {

String createToken(Map<String, Object> payload);

Map<String, Object> getPayload(String token);

boolean validateToken(String token);
}
20 changes: 19 additions & 1 deletion server/src/main/java/server/haengdong/domain/event/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -18,18 +20,23 @@ public class Event {
private static final int MIN_NAME_LENGTH = 2;
private static final int MAX_NAME_LENGTH = 20;
private static final String SPACES = " ";
private static final Pattern PASSWORD_PATTERN = Pattern.compile("^\\d{4}$");

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

private String password;

private String token;

public Event(String name, String token) {
public Event(String name, String password, String token) {
validateName(name);
validatePassword(password);
this.name = name;
this.password = password;
this.token = token;
}

Expand All @@ -48,11 +55,22 @@ private void validateName(String name) {
}
}

private void validatePassword(String password) {
Matcher matcher = PASSWORD_PATTERN.matcher(password);
if (!matcher.matches()) {
throw new HaengdongException(HaengdongErrorCode.BAD_REQUEST, "비밀번호는 4자리 숫자만 가능합니다.");
}
}

private boolean isBlankContinuous(String name) {
return name.contains(SPACES);
}

public boolean isTokenMismatch(String token) {
return !this.token.equals(token);
}

public boolean isSamePassword(String password) {
return this.password.equals(password);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package server.haengdong.exception;

public class AuthenticationException extends RuntimeException {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
Expand All @@ -14,6 +15,12 @@
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<ErrorResponse> authenticationException() {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ErrorResponse.of(HaengdongErrorCode.UNAUTHORIZED));
}

@ExceptionHandler({HttpRequestMethodNotSupportedException.class, NoResourceFoundException.class})
public ResponseEntity<ErrorResponse> noResourceException() {
return ResponseEntity.badRequest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum HaengdongErrorCode {
BAD_REQUEST("R_001", "잘못된 요청입니다."),
NO_RESOURCE_REQUEST("R_002", "잘못된 엔드포인트입니다."),
MESSAGE_NOT_READABLE("R_003", "읽을 수 없는 요청 형식입니다."),
UNAUTHORIZED("A_001", "인증에 실패했습니다."),
INTERNAL_SERVER_ERROR("S_001", "서버 내부에서 에러가 발생했습니다."),
DUPLICATED_MEMBER_NAME("EV_001", "중복된 행사 참여 인원 이름이 존재합니다."),
NOT_FOUND_EVENT("EV_400", "존재하지 않는 행사입니다."),
Expand Down
Loading
Loading