Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

로그인 DTO, 컨트롤러 구현 #52

Merged
merged 6 commits into from
Aug 26, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum StatusCode {
NOT_SUPPORTED_STATISTICS_TYPE(HttpStatus.BAD_REQUEST, "type을 다시 설정해주세요. date와 hour이 가능합니다."),
EXCEEDED_STATISTICS_DATE_RANGE(HttpStatus.BAD_REQUEST, "조회 가능 기간을 초과했습니다. 조회 시간을 다시 설정해주세요"),
INVALID_STATISTICS_DATE(HttpStatus.BAD_REQUEST, "조회 시작 일자는 조회 종료 일자보다 이전이어야 합니다."),
FORBIDDEN(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."),

/**
* 500 번대 CODE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import lombok.RequiredArgsConstructor;
import team05.integrated_feed_backend.exception.custom.CustomAccessDeniedHandler;
import team05.integrated_feed_backend.exception.custom.CustomAuthenticationEntryPoint;
import team05.integrated_feed_backend.module.auth.jwt.JwtAuthenticationFilter;
import team05.integrated_feed_backend.module.auth.jwt.JwtManager;

Expand All @@ -25,6 +27,8 @@ public class SecurityConfig {

private final JwtManager jwtManager;
private final UserDetailsService userDetailsService;
private final CustomAccessDeniedHandler customAccessDeniedHandler;
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
Expand All @@ -44,10 +48,13 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
"/api-docs/**",
"/webjars/**"
).permitAll() // Swagger UI 관련 경로에 인증 없이 접근 허용
.requestMatchers("/auth/**").permitAll() // 인증 없이 접근할 수 있는 경로 설정 (회원가입, 로그인 등)
.requestMatchers("/api/auth/**").permitAll() // 인증 없이 접근할 수 있는 경로 설정 (회원가입, 로그인 등)
.requestMatchers(HttpMethod.POST, "/api/members").permitAll()
.anyRequest().authenticated() // 그 외의 모든 요청 인증 필요
)
.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint(customAuthenticationEntryPoint) // 인증 실패 시 처리
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 사용 X
.addFilterBefore(jwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class); // JWT 필터를 UsernamePasswordAuthenticationFilter 앞에 추가
Expand All @@ -57,7 +64,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
return NoOpPasswordEncoder.getInstance(); // 비밀번호 인코딩을 사용하지 않음
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package team05.integrated_feed_backend.exception.custom;

import java.io.IOException;

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import team05.integrated_feed_backend.common.BaseApiResponse;
import team05.integrated_feed_backend.common.code.StatusCode;

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

log.warn("CustomAccessDeniedHandler 호출");

// StatusCode.FORBIDDEN을 사용하여 응답 생성
StatusCode statusCode = StatusCode.FORBIDDEN;
BaseApiResponse<Void> apiResponse = BaseApiResponse.of(statusCode);

ResponseEntity<BaseApiResponse<Void>> entity = new ResponseEntity<>(apiResponse, statusCode.getHttpStatus());

response.setStatus(entity.getStatusCodeValue());
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(entity.getBody()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package team05.integrated_feed_backend.exception.custom;

import java.io.IOException;

import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setContentType("application/json; charset=UTF-8");
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().write("{\"error\": \"Forbidden\", \"message\": \"접근 권한이 없습니다.\"}");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package team05.integrated_feed_backend.module.auth.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
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.extern.slf4j.Slf4j;
import team05.integrated_feed_backend.common.BaseApiResponse;
import team05.integrated_feed_backend.module.auth.dto.JwtResponse;
import team05.integrated_feed_backend.module.auth.dto.LoginRequest;
import team05.integrated_feed_backend.module.auth.jwt.JwtManager;

@Slf4j
@RestController
@RequestMapping("/api/auth")
public class AuthController implements AuthControllerDocs {

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private JwtManager jwtManager;

@PostMapping("/login")
public BaseApiResponse<JwtResponse> login(@RequestBody LoginRequest loginRequest) {

log.info("로그인 시도: {}", loginRequest.getAccount());

Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getAccount(), loginRequest.getPassword()
)
);
log.info("AuthenticationManager 호출 후 - 인증 성공");

// 문제 없을 시 JWT 토큰 생성
String token = jwtManager.generateToken(authentication);
log.info("JWT 생성: {}", token);

log.info("로그인 성공: {}", loginRequest.getAccount());
JwtResponse jwtRes = new JwtResponse(token);
return BaseApiResponse.of(HttpStatus.OK, "로그인에 성공했습니다.", jwtRes);

// 예외 처리 핸들러에 추가
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package team05.integrated_feed_backend.module.auth.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import team05.integrated_feed_backend.common.BaseApiResponse;
import team05.integrated_feed_backend.module.auth.dto.JwtResponse;
import team05.integrated_feed_backend.module.auth.dto.LoginRequest;

@Tag(name = "Auth", description = "로그인 인증 관련 API")
public interface AuthControllerDocs {
@Operation(summary = "로그인", description = "사용자의 계정과 비밀번호로 로그인하여 JWT 토큰을 발급받습니다.")
@ApiResponse(responseCode = "200", description = "로그인이 성공적으로 이루어졌습니다.")
@ApiResponse(responseCode = "401", description = "인증에 실패했습니다.")
@ApiResponse(responseCode = "403", description = "접근 권한이 없습니다.")
BaseApiResponse<JwtResponse> login(LoginRequest loginRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package team05.integrated_feed_backend.module.auth.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class JwtResponse {
private String token;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package team05.integrated_feed_backend.module.auth.dto;

import lombok.Data;

@Data
public class LoginRequest {
private String account;
private String password;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import org.springframework.security.core.userdetails.UserDetails;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import team05.integrated_feed_backend.module.member.entity.Member;

@Slf4j
@Getter
public class CustomUserDetails implements UserDetails {

Expand All @@ -25,6 +27,7 @@ public String getUsername() {

@Override
public String getPassword() {
log.info("CustomUserDetails.getPassword() 호출 - 반환된 비밀번호: {}", member.getPassword());
return member.getPassword();
}

Expand All @@ -48,7 +51,7 @@ public boolean isCredentialsNonExpired() {

@Override
public boolean isEnabled() {
return "verified".equals(member.getStatus()); // 인증된 상태만 활성화
return "VERIFIED".equals(member.getStatus()); // 인증된 상태만 활성화
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public UserDetails loadUserByUsername(String account) throws UsernameNotFoundExc
.orElseThrow(() -> new UsernameNotFoundException(StatusCode.UNAUTHORIZED.name()));

log.info("유저 조회 성공: {}", member.getAccount());
log.info("저장된 비밀번호: {}", member.getPassword());

return new CustomUserDetails(member);
}
}
Loading