Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .idea/checkstyle-idea.xml

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

1 change: 1 addition & 0 deletions .idea/vcs.xml

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

86 changes: 86 additions & 0 deletions week10/AuthController.java
Original file line number Diff line number Diff line change
@@ -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<String, String> 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"
));
}
}
}
28 changes: 28 additions & 0 deletions week10/BasicAuthLoggingFilter.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
77 changes: 77 additions & 0 deletions week10/BasicAuthSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
27 changes: 27 additions & 0 deletions week10/CustomUserDetailsService.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
74 changes: 74 additions & 0 deletions week10/JwtAuthenticationFilter.java
Original file line number Diff line number Diff line change
@@ -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<GrantedAuthority> 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;
}
}
}
Loading