[회원 관리 기능 추가] 회원 등록 및 로그인 API 구현, JWT 토큰 발급 및 검증 필터 추가. Member 관련 DTO, Entity, Mapper, Repository, Service 구현으로 회원 관리 기능을 강화하고, Swagger 설정을 통해 API 문서화 개선.
This commit is contained in:
		@@ -26,6 +26,7 @@ repositories {
 | 
			
		||||
dependencies {
 | 
			
		||||
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
 | 
			
		||||
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
 | 
			
		||||
 | 
			
		||||
	// PostgreSQL JDBC
 | 
			
		||||
    runtimeOnly 'org.postgresql:postgresql'
 | 
			
		||||
	implementation 'org.springframework.boot:spring-boot-starter-web'
 | 
			
		||||
@@ -45,6 +46,8 @@ dependencies {
 | 
			
		||||
 | 
			
		||||
	// jwt
 | 
			
		||||
	implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
 | 
			
		||||
	runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
 | 
			
		||||
	runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
 | 
			
		||||
 | 
			
		||||
	// lombok
 | 
			
		||||
	compileOnly 'org.projectlombok:lombok'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,17 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.controller;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.controller;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.global.dto.ApiResponseDto;
 | 
			
		||||
import io.swagger.v3.oas.annotations.tags.Tag;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.Valid;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.CreateMemberRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.CreateMemberResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.service.MemberService;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.mapper.MemberMapper;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.CreateMemberRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.CreateMemberResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.mapper.MemberMapper;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import io.swagger.v3.oas.annotations.Operation;
 | 
			
		||||
@@ -32,7 +31,6 @@ public class MemberController {
 | 
			
		||||
 | 
			
		||||
    private final MemberService memberService;
 | 
			
		||||
    private final MemberMapper memberMapper;
 | 
			
		||||
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
 | 
			
		||||
 | 
			
		||||
    @LogExecution("회원 등록")
 | 
			
		||||
    @Operation(summary = "회원 등록", description = "새로운 회원을 등록합니다.")
 | 
			
		||||
@@ -51,6 +49,28 @@ public class MemberController {
 | 
			
		||||
        return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @LogExecution("로그아웃")
 | 
			
		||||
    @Operation(summary = "로그아웃", description = "사용자 로그아웃을 처리합니다.")
 | 
			
		||||
    @ApiResponses({
 | 
			
		||||
        @ApiResponse(responseCode = "200", description = "로그아웃 성공"),
 | 
			
		||||
        @ApiResponse(responseCode = "401", description = "인증 실패", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
 | 
			
		||||
    })
 | 
			
		||||
    @PostMapping("/logout")
 | 
			
		||||
    public ResponseEntity<ApiResponseDto<Void>> logout(@RequestHeader("Authorization") String authorization) {
 | 
			
		||||
        try {
 | 
			
		||||
            // Authorization 헤더에서 토큰 추출
 | 
			
		||||
            String token = authorization.replace("Bearer ", "");
 | 
			
		||||
            // Refresh Token 삭제 (실제로는 JWT 블랙리스트나 DB에서 삭제)
 | 
			
		||||
            // memberService.deleteRefreshToken(userId);
 | 
			
		||||
            
 | 
			
		||||
            return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("로그아웃 처리 중 오류 발생: {}", e.getMessage());
 | 
			
		||||
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
 | 
			
		||||
                    .body(ApiResponseDto.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // @PostMapping("/member/list")
 | 
			
		||||
    // public ResponseEntity<List<ResponseMember>> getMemberList(@RequestBody(required = false) Map<String, String> params) {
 | 
			
		||||
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.dto;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.dto;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.dto;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.dto;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.enums.MemberRole;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.enums.MemberRole;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.dto;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.dto;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.dto;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.dto;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.dto;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.dto;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.enums.MemberRole;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.enums.MemberRole;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.entity;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.entity;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.enums.MemberRole;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.enums.MemberRole;
 | 
			
		||||
import com.bio.bio_backend.global.entity.BaseEntity;
 | 
			
		||||
import jakarta.persistence.*;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.enums;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.enums;
 | 
			
		||||
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.mapper;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.mapper;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.CreateMemberRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.CreateMemberResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.entity.Member;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.enums.MemberRole;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.CreateMemberRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.CreateMemberResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.Member;
 | 
			
		||||
import org.mapstruct.Mapper;
 | 
			
		||||
import org.mapstruct.Mapping;
 | 
			
		||||
import org.mapstruct.factory.Mappers;
 | 
			
		||||
@@ -20,7 +19,7 @@ public interface MemberMapper {
 | 
			
		||||
     * 기본값 설정: role = MemberRole.MEMBER, useFlag = true
 | 
			
		||||
     */
 | 
			
		||||
    @Mapping(target = "oid", ignore = true)
 | 
			
		||||
    @Mapping(target = "role", expression = "java(com.bio.bio_backend.domain.user.member.enums.MemberRole.getDefault())")
 | 
			
		||||
    @Mapping(target = "role", expression = "java(com.bio.bio_backend.domain.base.member.enums.MemberRole.getDefault())")
 | 
			
		||||
    @Mapping(target = "useFlag", constant = "true")
 | 
			
		||||
    @Mapping(target = "refreshToken", ignore = true)
 | 
			
		||||
    @Mapping(target = "lastLoginAt", ignore = true)
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.repository;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.repository;
 | 
			
		||||
 | 
			
		||||
import org.springframework.data.jpa.repository.JpaRepository;
 | 
			
		||||
import org.springframework.stereotype.Repository;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.entity.Member;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.Member;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
@Repository
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.repository;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.repository;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.entity.Member;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.Member;
 | 
			
		||||
import org.springframework.data.domain.Page;
 | 
			
		||||
import org.springframework.data.domain.Pageable;
 | 
			
		||||
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.repository;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.repository;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.entity.Member;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.entity.QMember;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.enums.MemberRole;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.Member;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.QMember;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.enums.MemberRole;
 | 
			
		||||
import com.querydsl.core.BooleanBuilder;
 | 
			
		||||
import com.querydsl.core.types.dsl.BooleanExpression;
 | 
			
		||||
import com.querydsl.jpa.impl.JPAQueryFactory;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.data.domain.Page;
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.service;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.service;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
@@ -6,7 +6,7 @@ import java.util.Map;
 | 
			
		||||
import org.springframework.security.core.userdetails.UserDetails;
 | 
			
		||||
import org.springframework.security.core.userdetails.UserDetailsService;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
			
		||||
 | 
			
		||||
public interface MemberService extends UserDetailsService {
 | 
			
		||||
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
package com.bio.bio_backend.domain.user.member.service;
 | 
			
		||||
package com.bio.bio_backend.domain.base.member.service;
 | 
			
		||||
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.entity.Member;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.enums.MemberRole;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.mapper.MemberMapper;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.repository.MemberRepository;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.entity.Member;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.enums.MemberRole;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.mapper.MemberMapper;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.repository.MemberRepository;
 | 
			
		||||
import com.bio.bio_backend.global.exception.ApiException;
 | 
			
		||||
import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
@@ -1,9 +1,16 @@
 | 
			
		||||
package com.bio.bio_backend.global.config;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 | 
			
		||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
 | 
			
		||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.context.annotation.Configuration;
 | 
			
		||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.format.DateTimeFormatter;
 | 
			
		||||
 | 
			
		||||
@Configuration
 | 
			
		||||
public class AppConfig {
 | 
			
		||||
    
 | 
			
		||||
@@ -11,4 +18,21 @@ public class AppConfig {
 | 
			
		||||
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
 | 
			
		||||
        return new BCryptPasswordEncoder();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Bean
 | 
			
		||||
    public ObjectMapper objectMapper() {
 | 
			
		||||
        ObjectMapper mapper = new ObjectMapper();
 | 
			
		||||
        
 | 
			
		||||
        // JavaTimeModule 등록
 | 
			
		||||
        JavaTimeModule javaTimeModule = new JavaTimeModule();
 | 
			
		||||
        
 | 
			
		||||
        // LocalDateTime 직렬화/역직렬화 설정
 | 
			
		||||
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 | 
			
		||||
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
 | 
			
		||||
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
 | 
			
		||||
        
 | 
			
		||||
        mapper.registerModule(javaTimeModule);
 | 
			
		||||
        
 | 
			
		||||
        return mapper;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -29,9 +29,6 @@ public class CustomAuthenticationFailureHandler implements AuthenticationFailure
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
 | 
			
		||||
 | 
			
		||||
        log.info("exception : " + exception.toString());
 | 
			
		||||
 | 
			
		||||
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
 | 
			
		||||
        response.setCharacterEncoding("UTF-8");
 | 
			
		||||
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,10 @@ import jakarta.servlet.http.Cookie;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import com.bio.bio_backend.global.dto.ApiResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.LoginRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.LoginResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.service.MemberService;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.LoginRequestDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.LoginResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
			
		||||
import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
			
		||||
import com.bio.bio_backend.global.utils.JwtUtils;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
@@ -35,12 +35,13 @@ import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
 | 
			
		||||
public class JwtTokenIssuanceFilter extends UsernamePasswordAuthenticationFilter {
 | 
			
		||||
 | 
			
		||||
    private final AuthenticationManager authenticationManager;
 | 
			
		||||
    private final MemberService memberService;
 | 
			
		||||
    private final JwtUtils jwtUtils;
 | 
			
		||||
    private final Environment env;
 | 
			
		||||
    private final ObjectMapper objectMapper;
 | 
			
		||||
 | 
			
		||||
    // 사용자 login 인증 처리
 | 
			
		||||
    @SneakyThrows
 | 
			
		||||
@@ -93,7 +94,6 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte
 | 
			
		||||
 | 
			
		||||
        // JWT 토큰 전달
 | 
			
		||||
        response.setHeader("Authorization", "Bearer " + accessToken);
 | 
			
		||||
//        response.addCookie(refreshTokenCookie);
 | 
			
		||||
        response.addHeader("Set-Cookie", 
 | 
			
		||||
        	    String.format("%s=%s; HttpOnly; Secure; Path=/; Max-Age=%d; SameSite=None", 
 | 
			
		||||
        	    refreshTokenCookie.getName(), 
 | 
			
		||||
@@ -112,8 +112,10 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte
 | 
			
		||||
 | 
			
		||||
        // login 성공 메시지 전송
 | 
			
		||||
        response.setStatus(HttpStatus.OK.value());
 | 
			
		||||
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
 | 
			
		||||
        new ObjectMapper().writeValue(response.getWriter(),
 | 
			
		||||
                ApiResponseDto.success(ApiResponseCode.LOGIN_SUCCESSFUL, memberData));
 | 
			
		||||
        response.setContentType(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
 | 
			
		||||
        objectMapper.writeValue(
 | 
			
		||||
            response.getWriter(),
 | 
			
		||||
            ApiResponseDto.success(ApiResponseCode.LOGIN_SUCCESSFUL, memberData)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,13 +13,12 @@ import org.springframework.web.filter.OncePerRequestFilter;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
 | 
			
		||||
import io.jsonwebtoken.ExpiredJwtException;
 | 
			
		||||
import jakarta.servlet.FilterChain;
 | 
			
		||||
import jakarta.servlet.ServletException;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import com.bio.bio_backend.global.dto.ApiResponseDto;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.service.MemberService;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
			
		||||
import com.bio.bio_backend.global.constants.ApiResponseCode;
 | 
			
		||||
import com.bio.bio_backend.global.utils.JwtUtils;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
@@ -27,18 +26,12 @@ import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class JwtTokenFilter extends OncePerRequestFilter {
 | 
			
		||||
public class JwtTokenValidationFilter extends OncePerRequestFilter {
 | 
			
		||||
 | 
			
		||||
    private final JwtUtils jwtUtils;
 | 
			
		||||
    private final MemberService memberService;
 | 
			
		||||
    private final Environment env;
 | 
			
		||||
 | 
			
		||||
    private final UriAllowFilter uriAllowFilter;
 | 
			
		||||
    
 | 
			
		||||
    @Override
 | 
			
		||||
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
 | 
			
		||||
    	return uriAllowFilter.authExceptionAllow(request);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
 | 
			
		||||
@@ -53,8 +46,7 @@ public class JwtTokenFilter extends OncePerRequestFilter {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            // 토큰 유효성 검사
 | 
			
		||||
            try {
 | 
			
		||||
            // Access Token 유효성 검사
 | 
			
		||||
            if (jwtUtils.validateAccessToken(accessToken)) {
 | 
			
		||||
                String username = jwtUtils.extractUsername(accessToken);
 | 
			
		||||
                UserDetails userDetails = memberService.loadUserByUsername(username);
 | 
			
		||||
@@ -68,26 +60,39 @@ public class JwtTokenFilter extends OncePerRequestFilter {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            } catch (ExpiredJwtException ignored) {
 | 
			
		||||
                // Access Token이 만료된 경우에만 ignored >> Refresh Token 검증 수행
 | 
			
		||||
            }
 | 
			
		||||
            // Refresh Token 유효성 검사
 | 
			
		||||
            
 | 
			
		||||
            // Access Token이 유효하지 않거나 만료된 경우 Refresh Token 검증
 | 
			
		||||
            if (refreshToken != null && jwtUtils.validateRefreshToken(refreshToken)) {
 | 
			
		||||
                String username = jwtUtils.extractUsername(refreshToken);
 | 
			
		||||
                String role = (String) jwtUtils.extractAllClaims(refreshToken).get("role");
 | 
			
		||||
                
 | 
			
		||||
                // 새로운 Access Token 생성
 | 
			
		||||
                String newAccessToken = jwtUtils.generateToken(username, role,
 | 
			
		||||
                        Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
 | 
			
		||||
                
 | 
			
		||||
                // 새로운 Access Token을 응답 헤더에 설정
 | 
			
		||||
                response.setHeader("Authorization", "Bearer " + newAccessToken);
 | 
			
		||||
                filterChain.doFilter(request, response);
 | 
			
		||||
            } else {
 | 
			
		||||
                sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.ALL_TOKEN_INVALID, null));
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            request.setAttribute("exception", e);
 | 
			
		||||
                
 | 
			
		||||
                // 인증 정보 설정
 | 
			
		||||
                UserDetails userDetails = memberService.loadUserByUsername(username);
 | 
			
		||||
                if (userDetails != null) {
 | 
			
		||||
                    UsernamePasswordAuthenticationToken authentication =
 | 
			
		||||
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
 | 
			
		||||
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
 | 
			
		||||
                    SecurityContextHolder.getContext().setAuthentication(authentication);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                filterChain.doFilter(request, response);
 | 
			
		||||
                return;
 | 
			
		||||
            } else {
 | 
			
		||||
                sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.ALL_TOKEN_INVALID));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            request.setAttribute("exception", e);
 | 
			
		||||
            sendJsonResponse(response, ApiResponseDto.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void sendJsonResponse(HttpServletResponse response, ApiResponseDto<?> apiResponse) throws IOException {
 | 
			
		||||
@@ -8,21 +8,18 @@ import org.springframework.security.config.annotation.authentication.builders.Au
 | 
			
		||||
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.web.SecurityFilterChain;
 | 
			
		||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 | 
			
		||||
import org.springframework.web.filter.CorsFilter;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
 | 
			
		||||
import jakarta.servlet.Filter;
 | 
			
		||||
//import com.bio.bio_backend.domain.common.service.AccessLogService;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.service.MemberService;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
			
		||||
import com.bio.bio_backend.global.exception.CustomAuthenticationFailureHandler;
 | 
			
		||||
import com.bio.bio_backend.global.exception.JwtAccessDeniedHandler;
 | 
			
		||||
import com.bio.bio_backend.global.exception.JwtAuthenticationEntryPoint;
 | 
			
		||||
//import com.bio.bio_backend.global.filter.HttpLoggingFilter;
 | 
			
		||||
import com.bio.bio_backend.global.utils.HttpUtils;
 | 
			
		||||
import com.bio.bio_backend.global.utils.JwtUtils;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
 | 
			
		||||
@@ -34,26 +31,20 @@ public class WebSecurity {
 | 
			
		||||
    private final MemberService memberService;
 | 
			
		||||
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
 | 
			
		||||
    private final JwtUtils jwtUtils;
 | 
			
		||||
//    private final AccessLogService accessLogService;
 | 
			
		||||
    private final CorsFilter corsFilter;
 | 
			
		||||
    private final ObjectMapper objectMapper;
 | 
			
		||||
    private final HttpUtils httpUtils;
 | 
			
		||||
    private final Environment env;
 | 
			
		||||
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
 | 
			
		||||
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
 | 
			
		||||
 | 
			
		||||
    private final UriAllowFilter uriAllowFilter;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private JwtAuthenticationFilter getJwtAuthenticationFilter(AuthenticationManager authenticationManager) throws Exception {
 | 
			
		||||
        JwtAuthenticationFilter filter = new JwtAuthenticationFilter(authenticationManager, memberService, jwtUtils, env);
 | 
			
		||||
    private JwtTokenIssuanceFilter getJwtTokenIssuanceFilter(AuthenticationManager authenticationManager) throws Exception {
 | 
			
		||||
        JwtTokenIssuanceFilter filter = new JwtTokenIssuanceFilter(authenticationManager, memberService, jwtUtils, env, objectMapper);
 | 
			
		||||
        filter.setFilterProcessesUrl("/login"); // 로그인 EndPoint
 | 
			
		||||
        filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler(objectMapper));
 | 
			
		||||
        return filter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Filter getJwtTokenFilter() {
 | 
			
		||||
        return new JwtTokenFilter(jwtUtils, memberService, env, uriAllowFilter);
 | 
			
		||||
    private Filter getJwtTokenValidationFilter() {
 | 
			
		||||
        return new JwtTokenValidationFilter(jwtUtils, memberService, env);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Bean
 | 
			
		||||
@@ -69,13 +60,15 @@ public class WebSecurity {
 | 
			
		||||
        http.csrf(AbstractHttpConfigurer::disable)          //csrf 비활성화
 | 
			
		||||
                .authorizeHttpRequests(request ->               //request 허용 설정
 | 
			
		||||
                        request
 | 
			
		||||
                        	.anyRequest().permitAll()  // 모든 요청 허용
 | 
			
		||||
//                    		.requestMatchers("/ws/**").permitAll()
 | 
			
		||||
//                            .requestMatchers("/admin/**", "/join").hasAnyAuthority(MemberConstants.ROLE_ADMIN)
 | 
			
		||||
//                            .requestMatchers("/member/**").hasAnyAuthority(MemberConstants.ROLE_MEMBER)
 | 
			
		||||
//                            .anyRequest().authenticated()
 | 
			
		||||
                            .requestMatchers("/api/auth/**").permitAll()  // 인증 관련 엔드포인트 허용
 | 
			
		||||
                            .requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll()  // Swagger 허용
 | 
			
		||||
                            .requestMatchers("/ws/**").permitAll()  // WebSocket 허용
 | 
			
		||||
                            .anyRequest().authenticated()  // 나머지 요청은 인증 필요
 | 
			
		||||
                )
 | 
			
		||||
                .authenticationManager(authenticationManager)
 | 
			
		||||
                .sessionManagement(session -> 
 | 
			
		||||
                    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)  // 세션 사용 안함
 | 
			
		||||
                )
 | 
			
		||||
                .logout(AbstractHttpConfigurer::disable);
 | 
			
		||||
 | 
			
		||||
        // 예외 처리 핸들링
 | 
			
		||||
@@ -85,16 +78,11 @@ public class WebSecurity {
 | 
			
		||||
                        .accessDeniedHandler(jwtAccessDeniedHandler)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
//        http
 | 
			
		||||
//        		.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
 | 
			
		||||
//                .addFilterBefore(new HttpLoggingFilter(accessLogService, httpUtils), UsernamePasswordAuthenticationFilter.class)
 | 
			
		||||
//                .addFilterBefore(getJwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
 | 
			
		||||
//                .addFilterBefore(getJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//                .sessionManagement(httpSecuritySessionManagementConfigurer ->       //Session 사용 X
 | 
			
		||||
//                        httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
 | 
			
		||||
 | 
			
		||||
        http
 | 
			
		||||
            // 1단계: JWT 토큰 발급 필터 (로그인 요청 처리 및 토큰 발급)
 | 
			
		||||
            .addFilterBefore(getJwtTokenIssuanceFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
 | 
			
		||||
            // 2단계: JWT 토큰 검증 필터 (모든 요청에 대해 토큰 유효성 검증)
 | 
			
		||||
            .addFilterBefore(getJwtTokenValidationFilter(), UsernamePasswordAuthenticationFilter.class);
 | 
			
		||||
 | 
			
		||||
        return http.build();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ import io.jsonwebtoken.io.Decoders;
 | 
			
		||||
import io.jsonwebtoken.security.Keys;
 | 
			
		||||
import jakarta.servlet.http.Cookie;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import com.bio.bio_backend.domain.user.member.service.MemberService;
 | 
			
		||||
import com.bio.bio_backend.domain.base.member.service.MemberService;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
 
 | 
			
		||||
@@ -95,4 +95,3 @@ springdoc.swagger-ui.doc-expansion=none
 | 
			
		||||
springdoc.swagger-ui.disable-swagger-default-url=true
 | 
			
		||||
springdoc.default-produces-media-type=application/json
 | 
			
		||||
springdoc.default-consumes-media-type=application/json
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user