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