diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml
new file mode 100644
index 0000000..6373743
--- /dev/null
+++ b/.idea/checkstyle-idea.xml
@@ -0,0 +1,16 @@
+
+
+
+ 12.1.2
+ JavaOnly
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 35eb1dd..bb38a43 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,5 +2,6 @@
+
\ No newline at end of file
diff --git a/week10/AuthController.java b/week10/AuthController.java
new file mode 100644
index 0000000..9535163
--- /dev/null
+++ b/week10/AuthController.java
@@ -0,0 +1,86 @@
+package io.api.oauth2;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/api/v1/auth")
+@RequiredArgsConstructor
+public class AuthController {
+
+ private final JwtProvider jwtProvider;
+ private final UserRepository userRepository;
+ private final PasswordEncoder passwordEncoder;
+
+ // 회원가입
+ @PostMapping("/register")
+ public ResponseEntity> register(@RequestBody Map userInfo) {
+ String email = userInfo.get("email");
+ String password = userInfo.get("password");
+ String userName = userInfo.get("userName");
+
+ // 이메일 중복 검사
+ if (userRepository.findByUserEmail(email).isPresent()) {
+ return ResponseEntity.badRequest().body(Map.of("error", "Email already exists"));
+ }
+
+ // 사용자 생성
+ UserEntity newUser = new UserEntity(
+ UUID.randomUUID().toString(), // userId
+ userName, // userName
+ email, // userEmail
+ passwordEncoder.encode(password), // password
+ "USER" // userRole - 기본값 USER
+ );
+
+ userRepository.save(newUser);
+
+ return ResponseEntity.ok(Map.of(
+ "message", "User registered successfully",
+ "email", email
+ ));
+ }
+
+ // 로그인 (Basic 인증 헤더로 요청 → Bearer 토큰 발급)
+ @PostMapping("/login")
+ public ResponseEntity> login() {
+ // Spring Security가 Basic 인증을 이미 처리했으므로
+ // SecurityContextHolder에서 인증된 사용자 정보를 가져옴
+ Authentication authentication =
+ org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication();
+
+ if (authentication == null || !authentication.isAuthenticated()) {
+ return ResponseEntity.status(401).body(Map.of(
+ "error", "Invalid credentials"
+ ));
+ }
+
+ String email = authentication.getName();
+
+ try {
+ // 사용자 조회
+ UserEntity user = userRepository.findByUserEmail(email)
+ .orElseThrow(() -> new RuntimeException("User not found"));
+
+ // JWT 토큰 생성
+ String token = jwtProvider.createToken(user.getUserId());
+
+ return ResponseEntity.ok(Map.of(
+ "token", token,
+ "tokenType", "Bearer",
+ "userId", user.getUserId(),
+ "email", user.getUserEmail()
+ ));
+ } catch (Exception e) {
+ return ResponseEntity.status(401).body(Map.of(
+ "error", "Invalid credentials"
+ ));
+ }
+ }
+}
\ No newline at end of file
diff --git a/week10/BasicAuthLoggingFilter.java b/week10/BasicAuthLoggingFilter.java
new file mode 100644
index 0000000..f2b4f40
--- /dev/null
+++ b/week10/BasicAuthLoggingFilter.java
@@ -0,0 +1,28 @@
+package io.api.oauth2;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@Component
+public class BasicAuthLoggingFilter extends OncePerRequestFilter {
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain)
+ throws ServletException, IOException {
+
+ String authHeader = request.getHeader("Authorization");
+ if (authHeader != null && authHeader.startsWith("Basic ")) {
+ System.out.println("🔐 Basic Authorization Header: " + authHeader);
+ }
+
+ filterChain.doFilter(request, response);
+ }
+}
diff --git a/week10/BasicAuthSecurityConfig.java b/week10/BasicAuthSecurityConfig.java
new file mode 100644
index 0000000..9255801
--- /dev/null
+++ b/week10/BasicAuthSecurityConfig.java
@@ -0,0 +1,77 @@
+package io.api.oauth2;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.Customizer;
+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.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+@Configuration
+@EnableWebSecurity
+@RequiredArgsConstructor
+public class BasicAuthSecurityConfig {
+
+ private final JwtAuthenticationFilter jwtAuthenticationFilter;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .cors(cors -> cors.configurationSource(corsConfigurationSource()))
+ .csrf(AbstractHttpConfigurer::disable)
+ .httpBasic(Customizer.withDefaults()) // Basic 인증 활성화
+ .sessionManagement(session -> session
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .authorizeHttpRequests(authorize -> authorize
+ .requestMatchers("/api/v1/auth/register").permitAll() // 회원가입은 인증 불필요
+ .requestMatchers("/api/v1/auth/login").authenticated() // 로그인은 Basic 인증 필요
+ .requestMatchers("/api/v1/user/**").authenticated() // 프로필 조회는 인증 필요
+ .anyRequest().authenticated()
+ )
+ // JWT 필터 추가 (Bearer 토큰 처리)
+ .addFilterBefore(basicAuthLoggingFilter(), BasicAuthenticationFilter.class)
+ .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
+ return config.getAuthenticationManager();
+ }
+
+ @Bean
+ public BasicAuthLoggingFilter basicAuthLoggingFilter() {
+ return new BasicAuthLoggingFilter();
+ }
+
+ @Bean
+ protected CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration configuration = new CorsConfiguration();
+ configuration.addAllowedOrigin("*");
+ configuration.addAllowedHeader("*");
+ configuration.addAllowedMethod("*");
+
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", configuration);
+
+ return source;
+ }
+}
diff --git a/week10/CustomUserDetailsService.java b/week10/CustomUserDetailsService.java
new file mode 100644
index 0000000..1c6967d
--- /dev/null
+++ b/week10/CustomUserDetailsService.java
@@ -0,0 +1,27 @@
+package io.api.oauth2;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class CustomUserDetailsService implements UserDetailsService {
+
+ private final UserRepository userRepository;
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ UserEntity userEntity = userRepository.findByUserEmail(username)
+ .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
+
+ return User.builder()
+ .username(userEntity.getUserEmail())
+ .password(userEntity.getPassword()) // 암호화된 비밀번호가 저장되어 있어야 함
+ .roles(userEntity.getUserRole()) // DB에서 role 가져오기
+ .build();
+ }
+}
diff --git a/week10/JwtAuthenticationFilter.java b/week10/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..e99dc25
--- /dev/null
+++ b/week10/JwtAuthenticationFilter.java
@@ -0,0 +1,74 @@
+package io.api.oauth2;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+@RequiredArgsConstructor
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+ private final JwtProvider jwtProvider;
+ private final UserRepository userRepository;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+ try {
+ String token = parseToken(request);
+ if (token != null) {
+ String userId = jwtProvider.validate(token);
+
+ if(userId == null) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+ //fintByUserId로 User의 ID를 가져옴
+ UserEntity userEntity = userRepository.findByUserId(userId);
+ // User 의 권한을 가져와야함
+ String role = userEntity.getUserRole();
+ //ROLE_USER, ROLE_ADMIN, ROLE_DEV
+ List authorities = new ArrayList<>();
+ authorities.add(new SimpleGrantedAuthority(role));
+
+ SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
+ AbstractAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userId, null, authorities);
+ authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ //context에 토큰 값을 담아줌
+ securityContext.setAuthentication(authenticationToken);
+ //만든 context 등록
+ SecurityContextHolder.setContext(securityContext);
+ }else { //authorization이 없거나 토큰이 없으면 바로 다음 필터 진행
+ filterChain.doFilter(request, response);
+ return;
+ }
+ }catch (Exception e) {
+ e.printStackTrace();
+ }
+ filterChain.doFilter(request, response);
+ }
+
+ private String parseToken(HttpServletRequest request) {
+ //헤더에 있는 Authrization을 가져옴
+ String bearerToken = request.getHeader("Authorization");
+ //가져온 토큰이 널이아니고 Bearer로 시작하면 bearer문자열 값 짜르고 리턴
+ if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
+ return bearerToken.substring(7);
+ }else {
+ return null;
+ }
+ }
+}
diff --git a/week10/JwtProvider.java b/week10/JwtProvider.java
new file mode 100644
index 0000000..26cb0da
--- /dev/null
+++ b/week10/JwtProvider.java
@@ -0,0 +1,76 @@
+package io.api.oauth2;
+
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+import java.security.Key;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+
+@Component
+public class JwtProvider {
+ // 롬복의 value아닌 빈스 팩토리의 벨류
+// 이게 프로퍼티에 있는 시크릿키를 가져오는 어노테이션
+ @Value("${secret-key}")
+ private String secretKey;
+ public String createToken(String userId){
+ Date expiredDate = Date.from(Instant.now().plus(1, ChronoUnit.HOURS));
+ //Key 설정을 해줘야함
+ //jwt는 헤더, 페이로드, 시그니처로 구성되어있고
+ // 헤더는 시그니쳐랑 페이로드를 조합해서 알아서 생성된다고 했음
+ // 그래서 헤더는? 만들필요가없다~
+ // 그럼 만들어야될거?
+ // payload에 실어 보낼 값과 시그니쳐를 만들면됨
+ // secretKey를 바탕으로 HMAC-SHA 키 -> 시그니쳐 생성.
+ Key key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
+ //jwt 만들고 반환.
+ return Jwts.builder()
+ //JWT 생성
+ //자 시작은 무슨 키로했는지 내가 쓴 알고리즘이 뭔지
+ // 이걸로 signWith로 시작하는게 규칙
+ .signWith(key, SignatureAlgorithm.HS256)
+ // 다음 내가 jwt토큰에 실어서 보낼 정보 입력.
+ // jwt토큰은 탈취당하는 위험을
+ // 토큰의 파기. 리프레시로 결정한다.
+ // 일단 setExpiration을 쓰고(아까 만들어논 expiredDate를 설정.
+ // 일단 userId를 실어 보냄
+ .setSubject(userId)
+ .setIssuedAt(new Date())
+ .setExpiration(expiredDate)
+ .compact();
+ }
+ // 여기까지 jwt를 만들었음
+ // 검증을 해야되지 -> 검증 메서드를 만들자.
+ // 검증은 보통 벨리데이트 라고
+ // jwt 검증 메서드
+ public String validate(String jwt){
+ String subject;
+ //Signature 생성
+ Key key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
+ try {
+ subject = Jwts.parserBuilder()
+ .setSigningKey(key)
+ .build()
+ // parseClaimsJwt와 Jws가 있는데
+ // 여기보면 jwt는 받아오는 인자가 object랑 key
+ // jws는 key만
+ // 우리는 파라미터 인자가 jwt하나
+ // 그니까 jws를 사용해야함
+ .parseClaimsJws(jwt)
+ .getBody()
+ .getSubject();
+ }catch (Exception e){
+ // 로그관리해주는 어노테이션 추가해서 로그관리
+ // @Slf4j 추가 그리고 getMesage()로 바꿈
+ // 로그의 레벨설정
+ e.printStackTrace();
+ return null;
+ }
+ return subject;
+ }
+}
\ No newline at end of file
diff --git a/week10/OAuth2Application.java b/week10/OAuth2Application.java
new file mode 100644
index 0000000..47479fd
--- /dev/null
+++ b/week10/OAuth2Application.java
@@ -0,0 +1,11 @@
+package io.api.oauth2;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class OAuth2Application {
+ public static void main(String[] args) {
+ SpringApplication.run(OAuth2Application.class, args);
+ }
+}
diff --git a/week10/UserEntity.java b/week10/UserEntity.java
new file mode 100644
index 0000000..e4835e3
--- /dev/null
+++ b/week10/UserEntity.java
@@ -0,0 +1,32 @@
+package io.api.oauth2;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity(name= "user")
+@Table(name = "user")
+public class UserEntity {
+ @Id
+ @Column(name="user_id")
+ private String userId;
+
+ @Column(name = "user_name")
+ private String userName;
+
+ @Column(name = "user_email")
+ private String userEmail;
+
+ @Column(name = "password")
+ private String password;
+
+ @Column(name = "user_role")
+ private String userRole;
+}
\ No newline at end of file
diff --git a/week10/UserProfileController.java b/week10/UserProfileController.java
new file mode 100644
index 0000000..2731c2b
--- /dev/null
+++ b/week10/UserProfileController.java
@@ -0,0 +1,38 @@
+package io.api.oauth2;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/v1/user")
+@RequiredArgsConstructor
+public class UserProfileController {
+
+ private final UserRepository userRepository;
+
+ // Bearer 토큰으로만 접근 가능한 프로필 조회
+ @GetMapping("/profile")
+ public ResponseEntity> getProfile(Authentication authentication) {
+ // JWT 토큰에서 userId 추출 (JwtAuthenticationFilter에서 설정됨)
+ String userId = authentication.getName();
+
+ UserEntity user = userRepository.findByUserId(userId);
+
+ if (user == null) {
+ return ResponseEntity.notFound().build();
+ }
+
+ return ResponseEntity.ok(Map.of(
+ "userId", user.getUserId(),
+ "email", user.getUserEmail(),
+ "userName", user.getUserName(),
+ "role", user.getUserRole()
+ ));
+ }
+}
\ No newline at end of file
diff --git a/week10/UserRepository.java b/week10/UserRepository.java
new file mode 100644
index 0000000..363980d
--- /dev/null
+++ b/week10/UserRepository.java
@@ -0,0 +1,12 @@
+package io.api.oauth2;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface UserRepository extends JpaRepository {
+ UserEntity findByUserId(String userId);
+ Optional findByUserEmail(String userEmail);
+}