Skip to content

Commit

Permalink
#13 [ADD] : add files of week 6 seminar
Browse files Browse the repository at this point in the history
  • Loading branch information
bbabbi committed Jun 2, 2024
1 parent e33167c commit 464dc5d
Show file tree
Hide file tree
Showing 39 changed files with 1,142 additions and 0 deletions.
8 changes: 8 additions & 0 deletions week6/.idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions week6/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.4'
id 'io.spring.dependency-management' version '1.1.4'
}

group = 'org.sopt'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '17'
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation group: 'org.postgresql', name: 'postgresql', version: '42.7.3'
testImplementation 'io.rest-assured:rest-assured'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

//JWT
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'

//Multipart file
implementation("software.amazon.awssdk:bom:2.21.0")
implementation("software.amazon.awssdk:s3:2.21.0")

//Security
implementation 'org.springframework.boot:spring-boot-starter-security'

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.3.1.RELEASE'
}

tasks.named('test') {
useJUnitPlatform()
}
13 changes: 13 additions & 0 deletions week6/src/main/java/org/sopt/spring/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.sopt.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}
26 changes: 26 additions & 0 deletions week6/src/main/java/org/sopt/spring/auth/PrincipalHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.sopt.spring.auth;

import org.sopt.spring.common.dto.ErrorMessage;
import org.sopt.spring.exception.UnauthorizedException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

@Component
public class PrincipalHandler {

private static final String ANONYMOUS_USER = "anonymousUser";

public Long getUserIdFromPrincipal() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
isPrincipalNull(principal);
return Long.valueOf(principal.toString());
}

public void isPrincipalNull(
final Object principal
) {
if (principal.toString().equals(ANONYMOUS_USER)) {
throw new UnauthorizedException(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION);
}
}
}
48 changes: 48 additions & 0 deletions week6/src/main/java/org/sopt/spring/auth/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.sopt.spring.auth;

import lombok.RequiredArgsConstructor;
import org.sopt.spring.auth.filter.CustomAccessDeniedHandler;
import org.sopt.spring.auth.filter.CustomJwtAuthenticationEntryPoint;
import org.sopt.spring.auth.filter.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@RequiredArgsConstructor
@EnableWebSecurity //web Security를 사용할 수 있게
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint;
private final CustomAccessDeniedHandler customAccessDeniedHandler;


private static final String[] AUTH_WHITE_LIST = {"/api/v1/member"};

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.requestCache(RequestCacheConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.exceptionHandling(exception ->
{
exception.authenticationEntryPoint(customJwtAuthenticationEntryPoint);
exception.accessDeniedHandler(customAccessDeniedHandler);
});


http.authorizeHttpRequests(auth -> {
auth.requestMatchers(AUTH_WHITE_LIST).permitAll();
auth.anyRequest().authenticated();
})
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
}
}
17 changes: 17 additions & 0 deletions week6/src/main/java/org/sopt/spring/auth/UserAuthentication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.sopt.spring.auth;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

public class UserAuthentication extends UsernamePasswordAuthenticationToken {

public UserAuthentication(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}

public static UserAuthentication createUserAuthentication(Long userId) {
return new UserAuthentication(userId, null, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.sopt.spring.auth.filter;

import io.jsonwebtoken.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
setResponse(response);
}

private void setResponse(HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.sopt.spring.auth.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.sopt.spring.common.dto.ErrorMessage;
import org.sopt.spring.common.dto.ErrorResponse;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
@RequiredArgsConstructor
public class CustomJwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

private final ObjectMapper objectMapper;

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
setResponse(response);
}

private void setResponse(HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter()
.write(objectMapper.writeValueAsString(
ErrorResponse.of(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION.getStatus(),
ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION.getMessage())));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.sopt.spring.auth.filter;

import io.micrometer.common.lang.NonNull;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.sopt.spring.auth.UserAuthentication;
import org.sopt.spring.common.dto.ErrorMessage;
import org.sopt.spring.common.jwt.JwtTokenProvider;
import org.sopt.spring.exception.UnauthorizedException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

import static org.sopt.spring.common.jwt.JwtValidationType.VALID_JWT;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenProvider jwtTokenProvider;

@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
try {
final String token = getJwtFromRequest(request);
if (jwtTokenProvider.validateToken(token) == VALID_JWT) {
Long memberId = jwtTokenProvider.getUserFromJwt(token);
UserAuthentication authentication = UserAuthentication.createUserAuthentication(memberId);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception exception) {
throw new UnauthorizedException(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION);
}
filterChain.doFilter(request, response);
}

private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring("Bearer ".length());
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.sopt.spring.common;


import jakarta.persistence.EntityNotFoundException;
import org.sopt.spring.common.dto.ErrorMessage;
import org.sopt.spring.exception.NotFoundException;
import org.sopt.spring.exception.UnauthorizedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.sopt.spring.common.dto.ErrorResponse;

import java.util.Objects;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage()));
}
@ExceptionHandler(NotFoundException.class)
protected ResponseEntity<ErrorResponse> handleNotFoundException(NotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ErrorResponse.of(e.getErrorMessage()));
}
@ExceptionHandler(UnauthorizedException.class)
protected ResponseEntity<ErrorResponse> handlerUnauthorizedException(UnauthorizedException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ErrorResponse.of(e.getErrorMessage().getStatus(), e.getErrorMessage().getMessage()));
}
}
17 changes: 17 additions & 0 deletions week6/src/main/java/org/sopt/spring/common/dto/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.sopt.spring.common.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum ErrorMessage {
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "ID에 해당하는 사용자가 존재하지 않습니다."),
BLOG_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "ID에 해당하는 블로그가 없습니다."),
JWT_UNAUTHORIZED_EXCEPTION(HttpStatus.UNAUTHORIZED.value(), "사용자의 로그인 검증을 실패했습니다."),
REFRESH_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 유저의 리프레시 토큰이 존재하지 않습니다."),
;
private final int status;
private final String message;
}
13 changes: 13 additions & 0 deletions week6/src/main/java/org/sopt/spring/common/dto/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.sopt.spring.common.dto;

public record ErrorResponse(
int status,
String message
) {
public static ErrorResponse of(int status, String message) {
return new ErrorResponse(status, message);
}
public static ErrorResponse of(ErrorMessage errorMessage) {
return new ErrorResponse(errorMessage.getStatus(), errorMessage.getMessage());
}
}
16 changes: 16 additions & 0 deletions week6/src/main/java/org/sopt/spring/common/dto/SuccessMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.sopt.spring.common.dto;


import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum SuccessMessage {

BLOG_CREATE_SUCCESS(HttpStatus.CREATED.value(),"블로그 생성이 완료되었습니다."),
;
private final int status;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.sopt.spring.common.dto;

public record SuccessStatusResponse(
int status,
String message
) {

public static SuccessStatusResponse of(SuccessMessage successMessage) {
return new SuccessStatusResponse(successMessage.getStatus(), successMessage.getMessage());
}

}
Loading

0 comments on commit 464dc5d

Please sign in to comment.