Skip to content

Commit

Permalink
feat: OAuth + jwt 인증 구현 및 s3 multiUpload 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
oxdjww committed Jul 4, 2024
1 parent d7ce8a6 commit ffbb871
Show file tree
Hide file tree
Showing 23 changed files with 637 additions and 21 deletions.
9 changes: 6 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,9 @@ dependencies {
// core
implementation 'org.springframework.boot:spring-boot-starter-web'

// validation
implementation 'org.springframework.boot:spring-boot-starter-validation'

// jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
runtimeOnly 'com.mysql:mysql-connector-j'

// lombok
Expand All @@ -48,6 +46,11 @@ dependencies {

// S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

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

tasks.named('test') {
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/hackathon/TimeLapse/config/CorsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.hackathon.TimeLapse.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

public class CorsConfig implements WebMvcConfigurer {

public static CorsConfigurationSource apiConfigurationSource() {

CorsConfiguration configuration = new CorsConfiguration();

ArrayList<String> allowedOriginPatterns = new ArrayList<>();
allowedOriginPatterns.add("http://localhost:8080");
allowedOriginPatterns.add("http://localhost:3000");
// allowedOriginPatterns.add("{프론트 배포 URL}");

ArrayList<String> allowedHttpMethods = new ArrayList<>(List.of("GET", "POST"));

configuration.setAllowedOrigins(allowedOriginPatterns);
configuration.setAllowedMethods(allowedHttpMethods);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);

return source;
}
}
63 changes: 63 additions & 0 deletions src/main/java/com/hackathon/TimeLapse/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.hackathon.TimeLapse.config;

import java.util.Arrays;
import java.util.stream.Stream;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.hackathon.TimeLapse.oauth.JwtAuthenticationFilter;
import com.hackathon.TimeLapse.oauth.JwtTokenProvider;

import lombok.RequiredArgsConstructor;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtTokenProvider jwtTokenProvider;

private final String[] swaggerUrls = {"/","/swagger-ui/**", "/v3/api-docs/**"};
private final String[] authUrls = {"/kakao/callback/**", "/api/auth/kakao", "/api/files/**"};
private final String[] allowedUrls = Stream.concat(Arrays.stream(swaggerUrls), Arrays.stream(authUrls))
.toArray(String[]::new);

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

http
.cors((cors) -> cors
.configurationSource(CorsConfig.apiConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.authorizeHttpRequests((auth) -> auth
.requestMatchers(allowedUrls).permitAll()
.requestMatchers("/test").permitAll()
.anyRequest().authenticated()
);

http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);

return http.build();
}
}
55 changes: 38 additions & 17 deletions src/main/java/com/hackathon/TimeLapse/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,54 @@
package com.hackathon.TimeLapse.domain;

import com.hackathon.TimeLapse.domain.common.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@DynamicUpdate
@DynamicInsert
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
import com.hackathon.TimeLapse.domain.common.BaseEntity;
import com.hackathon.TimeLapse.oauth.OAuthProvider;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Entity
public class Member extends BaseEntity {
@Id
@GeneratedValue(
strategy = GenerationType.IDENTITY
strategy = GenerationType.IDENTITY
)
private Long id;

private String email;

private String nickname;

@Enumerated(EnumType.STRING)
private OAuthProvider oAuthProvider;

@OneToMany(
mappedBy = "member",
cascade = {CascadeType.ALL}
mappedBy = "member",
cascade = {CascadeType.ALL},
fetch = FetchType.EAGER
)
@Builder.Default
private List<Article> articleList = new ArrayList<>();

@Builder
public Member(String email, String nickname, OAuthProvider oAuthProvider) {
this.email = email;
this.nickname = nickname;
this.oAuthProvider = oAuthProvider;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.hackathon.TimeLapse.member;

import com.hackathon.TimeLapse.domain.Member;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.hackathon.TimeLapse.domain.Member;

public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
}
21 changes: 21 additions & 0 deletions src/main/java/com/hackathon/TimeLapse/oauth/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.hackathon.TimeLapse.oauth;

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;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/auth")
public class AuthController {
private final OAuthLoginService oAuthLoginService;

@PostMapping("/kakao")
public ResponseEntity<AuthTokens> loginKakao(@RequestBody KakaoLoginParams params) {
return ResponseEntity.ok(oAuthLoginService.login(params));
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/hackathon/TimeLapse/oauth/AuthTokens.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.hackathon.TimeLapse.oauth;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AuthTokens {
private String accessToken;
private String refreshToken;
private String grantType;
private Long expiresIn;

public static AuthTokens of(String accessToken, String refreshToken, String grantType, Long expiresIn) {
return new AuthTokens(accessToken, refreshToken, grantType, expiresIn);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.hackathon.TimeLapse.oauth;

import java.util.Date;

import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class AuthTokensGenerator {
private static final String BEARER_TYPE = "Bearer";
private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; // 30분
private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 7; // 7일

private final JwtTokenProvider jwtTokenProvider;

public AuthTokens generate(Long memberId) {
long now = (new Date()).getTime();
Date accessTokenExpiredAt = new Date(now + ACCESS_TOKEN_EXPIRE_TIME);
Date refreshTokenExpiredAt = new Date(now + REFRESH_TOKEN_EXPIRE_TIME);

String subject = memberId.toString();
String accessToken = jwtTokenProvider.generate(subject, accessTokenExpiredAt);
String refreshToken = jwtTokenProvider.generate(subject, refreshTokenExpiredAt);

return AuthTokens.of(accessToken, refreshToken, BEARER_TYPE, ACCESS_TOKEN_EXPIRE_TIME / 1000L);
}

public Long extractMemberId(String accessToken) {
return Long.valueOf(jwtTokenProvider.extractSubject(accessToken));
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/hackathon/TimeLapse/oauth/ClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.hackathon.TimeLapse.oauth;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ClientConfig {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.hackathon.TimeLapse.oauth;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@AllArgsConstructor
@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenProvider jwtTokenProvider;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("[*] Jwt Filter");
String token = resolveToken(request);
log.info("[*] Jwt token >>>>> " + token);
if (token != null) {
Authentication authentication = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication); //SecurityContextHolder에 담기
}
chain.doFilter(request, response);
}
private Authentication getAuthentication(String token) {
log.info("[*] subject: "+jwtTokenProvider.extractSubject(token));
return new UsernamePasswordAuthenticationToken(jwtTokenProvider.extractSubject(token), null,
Collections.singleton(new SimpleGrantedAuthority("ROLE_MEMBER")));
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.split(" ")[1];
}
return null;
}
}
Loading

0 comments on commit ffbb871

Please sign in to comment.