Skip to content

Commit

Permalink
[ALL] 하루스터디 v1.1.0 배포 🚀
Browse files Browse the repository at this point in the history
[ALL] 하루스터디 v1.1.0 배포 🚀
  • Loading branch information
nlom0218 authored Aug 17, 2023
2 parents b159781 + 246f3a0 commit fc51b7d
Show file tree
Hide file tree
Showing 270 changed files with 6,965 additions and 3,807 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "backend/src/main/resources/submodule"]
path = backend/src/main/resources/submodule
url = https://github.com/haru-study/submodule.git
[submodule "frontend/env-submodule"]
path = frontend/env-submodule
url = https://github.com/haru-study/env-submodule.git
9 changes: 9 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,21 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-webflux'

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'


implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'

implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'

implementation 'mysql:mysql-connector-java:8.0.33'

compileOnly 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package harustudy.backend.auth;

import harustudy.backend.auth.dto.AuthMember;
import harustudy.backend.auth.service.AuthService;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
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
@Component
public class AuthArgumentResolver implements HandlerMethodArgumentResolver {

private static final int ACCESS_TOKEN_LOCATION = 1;

private final AuthService authService;

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Authenticated.class);
}

@Override
public AuthMember resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
String authorizationHeader = webRequest.getHeader(HttpHeaders.AUTHORIZATION);
Objects.requireNonNull(authorizationHeader);
String accessToken = authorizationHeader.split(" ")[ACCESS_TOKEN_LOCATION];
long memberId = Long.parseLong(authService.parseMemberId(accessToken));
return new AuthMember(memberId);
}
}
35 changes: 35 additions & 0 deletions backend/src/main/java/harustudy/backend/auth/AuthInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package harustudy.backend.auth;

import harustudy.backend.auth.exception.InvalidAuthorizationHeaderException;
import harustudy.backend.auth.service.AuthService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@RequiredArgsConstructor
@Component
public class AuthInterceptor implements HandlerInterceptor {

private final AuthService authService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
return true;
}
String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
String[] splitAuthorizationHeader = authorizationHeader.split(" ");
if (splitAuthorizationHeader.length != 2 ||
!splitAuthorizationHeader[0].equals("Bearer")) {
throw new InvalidAuthorizationHeaderException();
}
String accessToken = authorizationHeader.split(" ")[1];
authService.validateAccessToken(accessToken);
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
14 changes: 14 additions & 0 deletions backend/src/main/java/harustudy/backend/auth/Authenticated.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package harustudy.backend.auth;

import io.swagger.v3.oas.annotations.Hidden;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Hidden
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Authenticated {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package harustudy.backend.auth.config;

import java.util.Map;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Setter
@Configuration
@ConfigurationProperties(prefix = "oauth2")
public class OauthProperties {

private Map<String, OauthProperty> oauthProperties;

public OauthProperty get(String providerName) {
return oauthProperties.get(providerName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package harustudy.backend.auth.config;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class OauthProperty {

private String clientId;
private String clientSecret;
private String redirectUri;
private String tokenUri;
private String userInfoUri;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package harustudy.backend.auth.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public record TokenConfig(
@Value("${jwt.secret-key}") String secretKey,
@Value("${jwt.expire-length}") long accessTokenExpireLength,
@Value("${jwt.guest-expire-length}") long guestAccessTokenExpireLength,
@Value("${refresh-token.expire-length}") long refreshTokenExpireLength) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package harustudy.backend.auth.controller;

import harustudy.backend.auth.dto.OauthLoginRequest;
import harustudy.backend.auth.dto.TokenResponse;
import harustudy.backend.auth.exception.RefreshTokenCookieNotExistsException;
import harustudy.backend.auth.service.AuthService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "인증 관련 기능")
@RequiredArgsConstructor
@RequestMapping("api/auth")
@RestController
public class AuthController {

@Value("${refresh-token.expire-length}")
private Long refreshTokenExpireLength;

private final AuthService authService;

@Operation(summary = "소셜 로그인 요청")
@PostMapping("/login")
public ResponseEntity<TokenResponse> oauthLogin(
HttpServletResponse httpServletResponse,
@RequestBody OauthLoginRequest request
) {
TokenResponse tokenResponse = authService.oauthLogin(request);
Cookie cookie = new Cookie("refreshToken", tokenResponse.refreshToken().toString());
cookie.setMaxAge(refreshTokenExpireLength.intValue());
cookie.setPath("/");
httpServletResponse.addCookie(cookie);
return ResponseEntity.ok(tokenResponse);
}

@Operation(summary = "비회원 로그인 요청")
@PostMapping("/guest")
public ResponseEntity<TokenResponse> guestLogin() {
TokenResponse tokenResponse = authService.guestLogin();
return ResponseEntity.ok(tokenResponse);
}

@Operation(summary = "access 토큰, refresh 토큰 갱신")
@PostMapping("/refresh")
public ResponseEntity<TokenResponse> refresh(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse
) {
String refreshToken = extractRefreshTokenFromCookie(httpServletRequest);
TokenResponse tokenResponse = authService.refresh(refreshToken);
Cookie cookie = new Cookie("refreshToken", tokenResponse.refreshToken().toString());
cookie.setMaxAge(refreshTokenExpireLength.intValue());
cookie.setPath("/");
httpServletResponse.addCookie(cookie);
return ResponseEntity.ok(tokenResponse);
}

private String extractRefreshTokenFromCookie(HttpServletRequest httpServletRequest) {
return Arrays.stream(httpServletRequest.getCookies())
.filter(cookie -> cookie.getName().equals("refreshToken"))
.map(Cookie::getValue)
.findAny()
.orElseThrow(RefreshTokenCookieNotExistsException::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package harustudy.backend.auth.domain;

import harustudy.backend.auth.exception.RefreshTokenExpiredException;
import harustudy.backend.common.BaseTimeEntity;
import harustudy.backend.member.domain.Member;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class RefreshToken extends BaseTimeEntity {

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

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

@Column(columnDefinition = "BINARY(16)")
private UUID uuid;

private LocalDateTime expireDateTime;

public RefreshToken(Member member, long expireLength) {
this.member = member;
this.uuid = UUID.randomUUID();
this.expireDateTime = LocalDateTime.now().plus(expireLength, ChronoUnit.MILLIS);
}

public void validateExpired() {
if (expireDateTime.isBefore(LocalDateTime.now())) {
throw new RefreshTokenExpiredException();
}
}

public RefreshToken updateExpireDateTime(long expireLength) {
this.expireDateTime = LocalDateTime.now().plus(expireLength, ChronoUnit.MILLIS);
return this;
}

public void updateUuidAndExpireDateTime(long expireLength) {
this.uuid = UUID.randomUUID();
this.expireDateTime = LocalDateTime.now().plus(expireLength, ChronoUnit.MILLIS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package harustudy.backend.auth.dto;

public record AuthMember(Long id){

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package harustudy.backend.auth.dto;

public record OauthLoginRequest(String oauthProvider, String code) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package harustudy.backend.auth.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;

@Builder
public record OauthTokenResponse(
@JsonProperty("token_type") String tokenType,
@JsonProperty("access_token") String accessToken,
String scope) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package harustudy.backend.auth.dto;

import java.util.UUID;

public record RefreshTokenRequest(UUID refreshTokenUuid) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package harustudy.backend.auth.dto;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import harustudy.backend.auth.domain.RefreshToken;
import java.util.UUID;

@JsonInclude(Include.NON_NULL)
public record TokenResponse(String accessToken, @JsonIgnore UUID refreshToken) {

public static TokenResponse forLoggedIn(String accessToken, RefreshToken refreshToken) {
return new TokenResponse(accessToken, refreshToken.getUuid());
}

public static TokenResponse forGuest(String accessToken) {
return new TokenResponse(accessToken, null);
}
}
13 changes: 13 additions & 0 deletions backend/src/main/java/harustudy/backend/auth/dto/UserInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package harustudy.backend.auth.dto;

import harustudy.backend.member.domain.LoginType;
import harustudy.backend.member.domain.Member;
import lombok.Builder;

@Builder
public record UserInfo(String name, String email, String imageUrl) {

public Member toMember(LoginType loginType) {
return new Member(name, email, imageUrl, loginType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package harustudy.backend.auth.exception;

import harustudy.backend.common.HaruStudyException;

public class AuthorizationException extends HaruStudyException {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package harustudy.backend.auth.exception;

import harustudy.backend.common.HaruStudyException;

public class InvalidAccessTokenException extends
HaruStudyException {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package harustudy.backend.auth.exception;

import harustudy.backend.common.HaruStudyException;

public class InvalidAuthorizationHeaderException extends
HaruStudyException {

}
Loading

0 comments on commit fc51b7d

Please sign in to comment.