Skip to content

Commit

Permalink
feat: 6차 세미나 실습 코드
Browse files Browse the repository at this point in the history
  • Loading branch information
ChaeAg committed Dec 6, 2023
1 parent 7f3760a commit 8817927
Show file tree
Hide file tree
Showing 21 changed files with 656 additions and 45 deletions.
46 changes: 27 additions & 19 deletions ThirdSeminar/build.gradle
Original file line number Diff line number Diff line change
@@ -1,42 +1,50 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.11'
id 'io.spring.dependency-management' version '1.1.3'
id 'java'
id 'org.springframework.boot' version '3.0.11'
id 'io.spring.dependency-management' version '1.1.3'
}

group = 'com.server.dosopt'
version = '0.0.1-SNAPSHOT'

java {
sourceCompatibility = '17'
sourceCompatibility = '17'
}

configurations {
compileOnly {
extendsFrom annotationProcessor
}
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
mavenCentral()
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-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'

// database
// database
// runtimeOnly 'com.h2database:h2'
implementation 'mysql:mysql-connector-java:8.0.32'
implementation 'mysql:mysql-connector-java:8.0.32'

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

// AWS sdk
implementation("software.amazon.awssdk:bom:2.21.0")
implementation("software.amazon.awssdk:s3:2.21.0")

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'
}

tasks.named('test') {
useJUnitPlatform()
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.server.dosopt.seminar.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

@Configuration
public class AWSConfig {

private static final String AWS_ACCESS_KEY_ID = "aws.accessKeyId";
private static final String AWS_SECRET_ACCESS_KEY = "aws.secretAccessKey";

// @Value("${aws-property.access-key}") --> 이렇게도 가능
private final String accessKey;
private final String secretKey;
private final String regionString;

public AWSConfig(@Value("${aws-property.access-key}") final String accessKey,
@Value("${aws-property.secret-key}") final String secretKey,
@Value("${aws-property.aws-region}") final String regionString) {
this.accessKey = accessKey;
this.secretKey = secretKey;
this.regionString = regionString;
}


// 시스템에 환경변수로 키를 등록
// 자격증명을 얻는 여러 방법 중 하나!
@Bean
public SystemPropertyCredentialsProvider systemPropertyCredentialsProvider() {
System.setProperty(AWS_ACCESS_KEY_ID, accessKey);
System.setProperty(AWS_SECRET_ACCESS_KEY, secretKey);
return SystemPropertyCredentialsProvider.create();
}

@Bean
public Region getRegion() {
return Region.of(regionString);
}

// S3Client : S3에 요청을 보내는 객체
// S3Client를 Spring Bean에 등록
@Bean
public S3Client getS3Client() {
return S3Client.builder()
.region(getRegion())
.credentialsProvider(systemPropertyCredentialsProvider())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.server.dosopt.seminar.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class BCryptPasswordConfig {

// salt 할 때 보안 강도를 어느정도로 할지 설정
// 높을수록 세지는데, 어느정도 높아지면 비슷함
// default가 10
private static final int STRENGTH = 10;

@Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder(STRENGTH);
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,57 @@
package com.server.dosopt.seminar.config;

import com.server.dosopt.seminar.config.jwt.CustomAccessDeniedHandler;
import com.server.dosopt.seminar.config.jwt.CustomJwtAuthenticationEntryPoint;
import com.server.dosopt.seminar.config.jwt.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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
@EnableWebSecurity
public class SecurityConfig {

// 들어오는 요청에 대해서 어떻게 처리할지 ?
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint;
private final CustomAccessDeniedHandler customAccessDeniedHandler;

private static final String[] AUTH_WHITELIST = {
"/sign-up",
"/sign-in"
};

public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
this.customJwtAuthenticationEntryPoint = customJwtAuthenticationEntryPoint;
this.customAccessDeniedHandler = customAccessDeniedHandler;
}


@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf().disable()
return http
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
// .sessionManagement()
// .sessionCreationPolicy(STATELESS)
// .and()
.exceptionHandling()
.authenticationEntryPoint(customJwtAuthenticationEntryPoint)
.accessDeniedHandler(customAccessDeniedHandler)
.and()
.authorizeHttpRequests()
.anyRequest().permitAll() // 모든 요청 다 받겠음
.and().build();
.requestMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.server.dosopt.seminar.config.jwt;

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;

import java.io.IOException;

@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,20 @@
package com.server.dosopt.seminar.config.jwt;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class CustomJwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

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

private void setResponse(HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.server.dosopt.seminar.config.jwt;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
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 com.server.dosopt.seminar.domain.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);
// authentication 객체 생성 -> principal에 유저정보를 담는다.
UserAuthentication authentication = new UserAuthentication(memberId.toString(), null, null);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception exception) {
throw new RuntimeException();
}
// 다음 필터로 요청 전달
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,81 @@
package com.server.dosopt.seminar.config.jwt;

import com.server.dosopt.seminar.domain.JwtValidationType;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

private static final String MEMBER_ID = "memberId";

@Value("${jwt.secret}")
private String JWT_SECRET;

@PostConstruct
protected void init() {
//base64 라이브러리에서 encodeToString을 이용해서 byte[] 형식을 String 형식으로 변환
JWT_SECRET = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes(StandardCharsets.UTF_8));
}

public String generateToken(Authentication authentication, Long tokenExpirationTime) {
final Date now = new Date();

final Claims claims = Jwts.claims()
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + tokenExpirationTime)); // 만료 시간

claims.put(MEMBER_ID, authentication.getPrincipal());

return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE) // Header
.setClaims(claims) // Claim
.signWith(getSigningKey()) // Signature
.compact();
}

private SecretKey getSigningKey() {
String encodedKey = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes()); //SecretKey 통해 서명 생성
return Keys.hmacShaKeyFor(encodedKey.getBytes()); //일반적으로 HMAC (Hash-based Message Authentication Code) 알고리즘 사용
}

public JwtValidationType validateToken(String token) {
try {
final Claims claims = getBody(token);
return JwtValidationType.VALID_JWT;
} catch (MalformedJwtException ex) {
return JwtValidationType.INVALID_JWT_TOKEN;
} catch (ExpiredJwtException ex) {
return JwtValidationType.EXPIRED_JWT_TOKEN;
} catch (UnsupportedJwtException ex) {
return JwtValidationType.UNSUPPORTED_JWT_TOKEN;
} catch (IllegalArgumentException ex) {
return JwtValidationType.EMPTY_JWT;
}
}

private Claims getBody(final String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}

public Long getUserFromJwt(String token) {
Claims claims = getBody(token);
return Long.valueOf(claims.get(MEMBER_ID).toString());
}

}
Loading

0 comments on commit 8817927

Please sign in to comment.