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] v3.0.0 배포 #829

Merged
merged 18 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/backend-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:

- name: Build and push
run: |
docker buildx build --platform linux/arm64 -t \
docker buildx build --platform linux/amd64 -t \
${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_IMAGE_BE_DEV }} --push .

deploy:
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/backend-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: backend-push

on:
push:
branches: [ "main" ]
branches: [ "release" ]
paths:
- 'server/**'
- '.github/workflows/**'
Expand Down Expand Up @@ -112,10 +112,10 @@ jobs:
MAX_ATTEMPTS=30
SLEEP_INTERVAL=2
ATTEMPT=1

while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
HEALTH_STATUS=$(curl -s http://localhost:$NEXT_PORT/actuator/health | sed -n 's/.*"status":"\([^"]*\)".*/\1/p')

if [ "$HEALTH_STATUS" = "UP" ]; then
echo "Health check passed on attempt $ATTEMPT."
break
Expand All @@ -124,7 +124,7 @@ jobs:
ATTEMPT=$((ATTEMPT+1))
sleep $SLEEP_INTERVAL
fi

if [ $ATTEMPT -gt $MAX_ATTEMPTS ]; then
echo "Health check failed after $MAX_ATTEMPTS attempts. Rolling back..."
sudo docker rm -f haengdong-backend-$NEXT_PORT
Expand Down Expand Up @@ -172,7 +172,7 @@ jobs:
}
server {
listen 9100;

location /actuator {
proxy_pass http://haengdong-backend-$NEXT_PORT:8080;
proxy_set_header Host \$host;
Expand Down Expand Up @@ -211,7 +211,7 @@ jobs:
}
server {
listen 9100;

location /actuator {
proxy_pass http://haengdong-backend-$NEXT_PORT:8080;
proxy_set_header Host \$host;
Expand Down
1 change: 1 addition & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {

implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.3.1'
implementation 'com.auth0:java-jwt:4.4.0'

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package server.haengdong;
package haengdong;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
Expand Down
12 changes: 12 additions & 0 deletions server/src/main/java/haengdong/common/auth/Login.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package haengdong.common.auth;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
boolean required() default true;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package server.haengdong.domain;
package haengdong.common.auth;

import java.util.Map;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package haengdong.common.auth.application;


import haengdong.common.auth.TokenProvider;
import haengdong.common.exception.AuthenticationException;
import haengdong.common.exception.HaengdongErrorCode;
import haengdong.event.application.EventService;
import haengdong.user.domain.Role;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AuthService {

private static final String TOKEN_NAME = "accessToken";
private static final String CLAIM_SUB = "sub";
private static final String ROLE = "role";

private final TokenProvider tokenProvider;
private final EventService eventService;

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

public String createGuestToken(Long userId) {
Map<String, Object> payload = Map.of(CLAIM_SUB, userId, ROLE, Role.GUEST);

return tokenProvider.createToken(payload);
}

public String createMemberToken(Long userId) {
Map<String, Object> payload = Map.of(CLAIM_SUB, userId, ROLE, Role.MEMBER);

return tokenProvider.createToken(payload);
}

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

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

public String getTokenName() {
return TOKEN_NAME;
}

public void checkAuth(String eventToken, Long userId) {
boolean hasEvent = eventService.existsByTokenAndUserId(eventToken, userId);

if (!hasEvent) {
log.warn("[행사 접근 불가] Cookie EventId = {}, UserId = {}", eventToken, userId);
throw new AuthenticationException(HaengdongErrorCode.FORBIDDEN);
}
}
}
36 changes: 36 additions & 0 deletions server/src/main/java/haengdong/common/auth/config/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package haengdong.common.auth.config;

import haengdong.common.auth.TokenProvider;
import haengdong.common.auth.application.AuthService;
import haengdong.common.auth.infrastructure.AuthenticationExtractor;
import haengdong.common.auth.infrastructure.JwtTokenProvider;
import haengdong.common.properties.JwtProperties;
import haengdong.event.application.EventService;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@RequiredArgsConstructor
@EnableConfigurationProperties({JwtProperties.class})
@Configuration
public class AuthConfig {

private final JwtProperties jwtProperties;
private final EventService eventService;

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

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

@Bean
public AuthenticationExtractor authenticationExtractor() {
return new AuthenticationExtractor();
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
package server.haengdong.config;
package haengdong.common.auth.infrastructure;

import haengdong.common.auth.application.AuthService;
import haengdong.common.exception.AuthenticationException;
import haengdong.common.exception.HaengdongErrorCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import server.haengdong.application.AuthService;
import server.haengdong.exception.AuthenticationException;
import server.haengdong.exception.HaengdongErrorCode;
import server.haengdong.infrastructure.auth.AuthenticationExtractor;

@Slf4j
@RequiredArgsConstructor
public class AdminInterceptor implements HandlerInterceptor {

public static final String LOGIN_MEMBER_REQUEST = "loginUserId";

private static final String ADMIN_URI_REGEX = "/api/admin/events/([^/]+)";
private static final Pattern ADMIN_URI_PATTERN = Pattern.compile(ADMIN_URI_REGEX);
private static final int EVENT_TOKEN_MATCHER_INDEX = 1;

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) {
HttpMethod method = HttpMethod.valueOf(request.getMethod());
Expand All @@ -38,19 +36,16 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
}

private void validateToken(HttpServletRequest request) {
String token = authenticationExtractor.extract(request, authService.getTokenName());
String tokenEventId = authService.findEventIdByToken(token);
String jwt = authenticationExtractor.extract(request, authService.getTokenName());
Long userId = authService.findUserIdByJWT(jwt);
String uri = request.getRequestURI();

Matcher matcher = ADMIN_URI_PATTERN.matcher(uri);
if (!matcher.find()) {
throw new AuthenticationException(HaengdongErrorCode.FORBIDDEN);
}

String eventToken = matcher.group(EVENT_TOKEN_MATCHER_INDEX);
if (!tokenEventId.equals(eventToken)) {
log.warn("[행사 접근 불가] Cookie EventId = {}, URL EventId = {}", tokenEventId, eventToken);
throw new AuthenticationException(HaengdongErrorCode.FORBIDDEN);
}

authService.checkAuth(eventToken, userId);
request.setAttribute(LOGIN_MEMBER_REQUEST, userId);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package server.haengdong.infrastructure.auth;
package haengdong.common.auth.infrastructure;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import server.haengdong.exception.AuthenticationException;
import server.haengdong.exception.HaengdongErrorCode;
import haengdong.common.exception.AuthenticationException;
import haengdong.common.exception.HaengdongErrorCode;

public class AuthenticationExtractor {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package haengdong.common.auth.infrastructure;

import haengdong.common.auth.Login;
import haengdong.common.auth.application.AuthService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@RequiredArgsConstructor
public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {

private final AuthService authService;
private final AuthenticationExtractor authenticationExtractor;

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Login.class) && parameter.getParameterType().equals(Long.class);
}

@Override
public Long resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String jwt = authenticationExtractor.extract(request, authService.getTokenName());

return authService.findUserIdByJWT(jwt);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package server.haengdong.infrastructure.auth;
package haengdong.common.auth.infrastructure;

import haengdong.common.properties.JwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
Expand All @@ -8,42 +9,42 @@
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import server.haengdong.domain.TokenProvider;
import haengdong.common.auth.TokenProvider;

public class JwtTokenProvider implements TokenProvider {

private final TokenProperties tokenProperties;
private final JwtProperties jwtProperties;

public JwtTokenProvider(TokenProperties tokenProperties) {
this.tokenProperties = tokenProperties;
public JwtTokenProvider(JwtProperties jwtProperties) {
this.jwtProperties = jwtProperties;
}

@Override
public String createToken(Map<String, Object> payload) {
Claims claims = Jwts.claims(new HashMap<>(payload));
Date now = new Date();
Date validity = new Date(now.getTime() + tokenProperties.expireLength());
Date validity = new Date(now.getTime() + jwtProperties.expireLength());

return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, tokenProperties.secretKey())
.signWith(SignatureAlgorithm.HS256, jwtProperties.secretKey())
.compact();
}

@Override
public Map<String, Object> getPayload(String token) {
return Jwts.parser()
.setSigningKey(tokenProperties.secretKey())
.setSigningKey(jwtProperties.secretKey())
.parseClaimsJws(token)
.getBody();
}

@Override
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser().setSigningKey(tokenProperties.secretKey()).parseClaimsJws(token);
Jws<Claims> claims = Jwts.parser().setSigningKey(jwtProperties.secretKey()).parseClaimsJws(token);

return !claims.getBody().getExpiration().before(new Date());
} catch (JwtException | IllegalArgumentException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package server.haengdong.infrastructure;
package haengdong.common.config;

import com.zaxxer.hikari.HikariDataSource;
import jakarta.persistence.EntityManagerFactory;
Expand All @@ -16,6 +16,7 @@
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import haengdong.common.infrastructure.DynamicRoutingDataSource;

@Profile("prod")
@Configuration
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package server.haengdong.config;
package haengdong.common.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
Expand Down
Loading
Loading