[프로젝트 구조 세팅]
This commit is contained in:
@@ -1,26 +1,17 @@
|
||||
package com.bio.bio_backend.domain.user.member.controller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
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.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 lombok.RequiredArgsConstructor;
|
||||
@@ -42,20 +33,20 @@ public class MemberController {
|
||||
}
|
||||
|
||||
@PostMapping("/join")
|
||||
public ResponseEntity<CreateMemberResponseDto> createMember(@RequestBody @Valid CreateMemberRequestDTO requestDto) {
|
||||
public ResponseEntity<Long> createMember(@RequestBody @Valid CreateMemberRequestDto requestDto) {
|
||||
|
||||
// RequestMember를 MemberDTO로 변환
|
||||
MemberDTO member = new MemberDTO();
|
||||
MemberDto member = new MemberDto();
|
||||
member.setId(requestDto.getUserId());
|
||||
member.setPw(requestDto.getPassword());
|
||||
|
||||
int oid = memberService.createMember(member);
|
||||
long oid = memberService.createMember(member);
|
||||
|
||||
// 생성된 회원 정보를 조회하여 응답
|
||||
MemberDTO createdMember = memberService.selectMember(oid);
|
||||
CreateMemberResponseDto responseDto = mapper.map(createdMember, CreateMemberResponseDto.class);
|
||||
//MemberDto createdMember = memberService.selectMember(oid);
|
||||
//CreateMemberResponseDto responseDto = mapper.map(createdMember, CreateMemberResponseDto.class);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(responseDto);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(oid);
|
||||
}
|
||||
|
||||
// @PostMapping("/member/list")
|
||||
|
@@ -6,7 +6,7 @@ import lombok.Data;
|
||||
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class CreateMemberRequestDTO {
|
||||
public class CreateMemberRequestDto {
|
||||
|
||||
@NotBlank(message = "사용자 ID는 필수입니다")
|
||||
private String userId;
|
@@ -0,0 +1,15 @@
|
||||
package com.bio.bio_backend.domain.user.member.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LoginRequestDto {
|
||||
@NotNull(message = "ID cannot be null")
|
||||
private String id;
|
||||
|
||||
@NotNull(message = "Password cannot be null")
|
||||
private String pw;
|
||||
|
||||
private int loginLogFlag;
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
package com.bio.bio_backend.domain.user.member.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class LoginResponseDto {
|
||||
private String id;
|
||||
|
||||
private String role;
|
||||
|
||||
private String status;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
|
||||
private Date regAt;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
|
||||
private Timestamp lastLoginAt;
|
||||
}
|
@@ -16,7 +16,7 @@ import lombok.Data;
|
||||
/**
|
||||
* 회원
|
||||
*/
|
||||
public class MemberDTO implements UserDetails {
|
||||
public class MemberDto implements UserDetails {
|
||||
/**
|
||||
* 시퀀스 (PK)
|
||||
*/
|
@@ -5,23 +5,23 @@ import java.util.Map;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import com.bio.bio_backend.domain.user.member.dto.MemberDTO;
|
||||
import com.bio.bio_backend.domain.user.member.dto.MemberDto;
|
||||
|
||||
@Mapper
|
||||
public interface MemberMapper {
|
||||
int createMember(MemberDTO memberDTO);
|
||||
int createMember(MemberDto memberDTO);
|
||||
|
||||
MemberDTO loadUserByUsername(String id);
|
||||
MemberDto loadUserByUsername(String id);
|
||||
|
||||
void updateRefreshToken(MemberDTO memberDTO);
|
||||
void updateRefreshToken(MemberDto memberDTO);
|
||||
|
||||
String getRefreshToken(String id);
|
||||
|
||||
int deleteRefreshToken(String id);
|
||||
|
||||
List<MemberDTO> selectMemberList(Map<String, String> params);
|
||||
List<MemberDto> selectMemberList(Map<String, String> params);
|
||||
|
||||
MemberDTO selectMemberBySeq(int seq);
|
||||
MemberDto selectMemberBySeq(long seq);
|
||||
|
||||
int updateMember(MemberDTO member);
|
||||
int updateMember(MemberDto member);
|
||||
}
|
||||
|
@@ -6,25 +6,25 @@ 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.user.member.dto.MemberDto;
|
||||
|
||||
public interface MemberService extends UserDetailsService {
|
||||
|
||||
UserDetails loadUserByUsername(String id);
|
||||
|
||||
int createMember(MemberDTO memberDTO);
|
||||
long createMember(MemberDto memberDTO);
|
||||
|
||||
void updateRefreshToken(MemberDTO memberDTO);
|
||||
void updateRefreshToken(MemberDto memberDTO);
|
||||
|
||||
String getRefreshToken(String id);
|
||||
|
||||
int deleteRefreshToken(String id);
|
||||
|
||||
List<MemberDTO> selectMemberList(Map<String, String> params);
|
||||
List<MemberDto> selectMemberList(Map<String, String> params);
|
||||
|
||||
MemberDTO selectMember(int seq);
|
||||
MemberDto selectMember(long seq);
|
||||
|
||||
int updateMember(MemberDTO member);
|
||||
int updateMember(MemberDto member);
|
||||
|
||||
int deleteMember(MemberDTO member);
|
||||
int deleteMember(MemberDto member);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package com.bio.bio_backend.domain.user.member.service;
|
||||
|
||||
import com.bio.bio_backend.domain.user.member.dto.MemberDTO;
|
||||
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.mapper.MemberMapper;
|
||||
import com.bio.bio_backend.domain.user.member.repository.MemberRepository;
|
||||
@@ -27,7 +27,7 @@ public class MemberServiceImpl implements MemberService {
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
|
||||
|
||||
MemberDTO member = memberMapper.loadUserByUsername(id);
|
||||
MemberDto member = memberMapper.loadUserByUsername(id);
|
||||
|
||||
if (member == null) {
|
||||
throw new UsernameNotFoundException("User not found with id : " + id);
|
||||
@@ -37,7 +37,7 @@ public class MemberServiceImpl implements MemberService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int createMember(MemberDTO memberDTO) {
|
||||
public long createMember(MemberDto memberDTO) {
|
||||
// JPA Entity를 사용하여 회원 생성
|
||||
Member member = Member.builder()
|
||||
.userId(memberDTO.getId())
|
||||
@@ -50,11 +50,11 @@ public class MemberServiceImpl implements MemberService {
|
||||
Member savedMember = memberRepository.save(member);
|
||||
|
||||
// 저장된 회원의 oid를 반환
|
||||
return savedMember.getOid().intValue();
|
||||
return savedMember.getOid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRefreshToken(MemberDTO memberDTO) {
|
||||
public void updateRefreshToken(MemberDto memberDTO) {
|
||||
memberMapper.updateRefreshToken(memberDTO);
|
||||
}
|
||||
|
||||
@@ -69,22 +69,40 @@ public class MemberServiceImpl implements MemberService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MemberDTO> selectMemberList(Map<String, String> params) {
|
||||
public List<MemberDto> selectMemberList(Map<String, String> params) {
|
||||
return memberMapper.selectMemberList(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemberDTO selectMember(int seq) {
|
||||
return memberMapper.selectMemberBySeq(seq);
|
||||
public MemberDto selectMember(long seq) {
|
||||
// JPA 레파지토리를 사용하여 회원 조회
|
||||
Member member = memberRepository.findById(seq)
|
||||
.orElseThrow(() -> new RuntimeException("회원을 찾을 수 없습니다. seq: " + seq));
|
||||
|
||||
// Member 엔티티를 MemberDto로 변환하여 반환
|
||||
MemberDto memberDto = new MemberDto();
|
||||
memberDto.setSeq(member.getOid().intValue()); // Long을 int로 안전하게 변환
|
||||
memberDto.setId(member.getUserId());
|
||||
memberDto.setPw(member.getPassword());
|
||||
memberDto.setRole(member.getRole());
|
||||
memberDto.setStatus(member.getStatus());
|
||||
memberDto.setRegAt(java.sql.Timestamp.valueOf(member.getCreatedAt())); // LocalDateTime을 Timestamp로 변환
|
||||
memberDto.setUdtAt(java.sql.Timestamp.valueOf(member.getUpdatedAt()));
|
||||
if (member.getLastLoginAt() != null) {
|
||||
memberDto.setLastLoginAt(java.sql.Timestamp.valueOf(member.getLastLoginAt()));
|
||||
}
|
||||
memberDto.setRefreshToken(member.getRefreshToken());
|
||||
|
||||
return memberDto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int updateMember(MemberDTO member) {
|
||||
public int updateMember(MemberDto member) {
|
||||
return memberMapper.updateMember(member);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteMember(MemberDTO member) {
|
||||
public int deleteMember(MemberDto member) {
|
||||
|
||||
member.setStatus(MemberConstants.MEMBER_INACTIVE);
|
||||
|
||||
|
@@ -0,0 +1,26 @@
|
||||
package com.bio.bio_backend.global.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
@Configuration
|
||||
public class CorsConfig {
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.addAllowedOriginPattern("*");
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
config.setAllowCredentials(true);
|
||||
|
||||
config.addExposedHeader("Authorization");
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
|
||||
return new CorsFilter(source);
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.bio.bio_backend.global.dto;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
import com.bio.bio_backend.global.utils.ApiResponseCode;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
//공통 response Class
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class CustomApiResponse<T> {
|
||||
|
||||
private int code;
|
||||
private String message;
|
||||
private String description;
|
||||
private T data;
|
||||
|
||||
private static final int SUCCESS = 200;
|
||||
|
||||
private CustomApiResponse(int code, String message, String description, T data){
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.description = description;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static <T> CustomApiResponse<T> success(ApiResponseCode responseCode, T data) {
|
||||
return new CustomApiResponse<T>(SUCCESS, responseCode.name(), responseCode.getDescription(), data);
|
||||
}
|
||||
|
||||
public static <T> CustomApiResponse<T> fail(ApiResponseCode responseCode, T data) {
|
||||
return new CustomApiResponse<T>(responseCode.getStatusCode(), responseCode.name(), responseCode.getDescription(), data);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package com.bio.bio_backend.global.entity;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
@@ -24,7 +25,11 @@ public abstract class BaseEntity {
|
||||
* 자동 증가하는 Long 타입으로 설정
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@GeneratedValue(generator = "customOidGenerator")
|
||||
@GenericGenerator(
|
||||
name = "customOidGenerator",
|
||||
type = com.bio.bio_backend.global.utils.CustomIdGenerator.class
|
||||
)
|
||||
@Column(name = "oid", nullable = false)
|
||||
private Long oid;
|
||||
|
||||
|
@@ -0,0 +1,53 @@
|
||||
package com.bio.bio_backend.global.exception;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import com.bio.bio_backend.global.dto.CustomApiResponse;
|
||||
import com.bio.bio_backend.global.utils.ApiResponseCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
|
||||
//Spring Security login -> filter 수행 -> ExceptionHandler 적용불가로 별도 FailureHandler 를 통하여 Error 응답 처리
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@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());
|
||||
|
||||
CustomApiResponse<String> apiResponse;
|
||||
if (exception instanceof UsernameNotFoundException) {
|
||||
apiResponse = CustomApiResponse.fail(ApiResponseCode.USER_NOT_FOUND, null);
|
||||
} else if (exception instanceof BadCredentialsException) {
|
||||
apiResponse = CustomApiResponse.fail(ApiResponseCode.AUTHENTICATION_FAILED, null);
|
||||
} else {
|
||||
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||
apiResponse = CustomApiResponse.fail(ApiResponseCode.INTERNAL_SERVER_ERROR, null);
|
||||
}
|
||||
|
||||
String jsonResponse = objectMapper.writeValueAsString(apiResponse);
|
||||
response.getWriter().write(jsonResponse);
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
package com.bio.bio_backend.global.exception;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.security.SignatureException;
|
||||
import com.bio.bio_backend.global.dto.CustomApiResponse;
|
||||
import com.bio.bio_backend.global.utils.ApiResponseCode;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public CustomApiResponse<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
|
||||
if (Objects.requireNonNull(e.getMessage()).contains("JSON parse error")) {
|
||||
return CustomApiResponse.fail(ApiResponseCode.COMMON_FORMAT_WRONG, null);
|
||||
}
|
||||
return CustomApiResponse.fail(ApiResponseCode.COMMON_BAD_REQUEST, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public CustomApiResponse<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
|
||||
return CustomApiResponse.fail(ApiResponseCode.COMMON_METHOD_NOT_ALLOWED, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(NoResourceFoundException.class)
|
||||
public CustomApiResponse<Void> handleNoResourceFoundException(NoResourceFoundException e){
|
||||
return CustomApiResponse.fail(ApiResponseCode.COMMON_NOT_FOUND, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public CustomApiResponse<Void> handleIllegalArgumentException(IllegalArgumentException e){
|
||||
return CustomApiResponse.fail(ApiResponseCode.ARGUMENT_NOT_VALID, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(IndexOutOfBoundsException.class)
|
||||
public CustomApiResponse<Void> handleIndexOutOfBoundsException(IndexOutOfBoundsException e){
|
||||
return CustomApiResponse.fail(ApiResponseCode.INDEX_OUT_OF_BOUND, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(SignatureException.class)
|
||||
public CustomApiResponse<Void> handleSignatureException(SignatureException e) {
|
||||
return CustomApiResponse.fail(ApiResponseCode.JWT_SIGNATURE_MISMATCH, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MalformedJwtException.class)
|
||||
public CustomApiResponse<Void> handleMalformedJwtException(MalformedJwtException e) {
|
||||
return CustomApiResponse.fail(ApiResponseCode.JWT_SIGNATURE_MISMATCH, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(JwtException.class)
|
||||
public CustomApiResponse<Void> handleJwtExceptionException(JwtException e) {
|
||||
return CustomApiResponse.fail(ApiResponseCode.JWT_TOKEN_NULL, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ExpiredJwtException.class)
|
||||
public CustomApiResponse<Void> handleExpiredJwtException(ExpiredJwtException e) {
|
||||
return CustomApiResponse.fail(ApiResponseCode.JWT_TOKEN_EXPIRED, null);
|
||||
}
|
||||
|
||||
@ExceptionHandler(JsonProcessingException.class)
|
||||
public CustomApiResponse<Void> handleExpiredJwtException(JsonProcessingException e) {
|
||||
return CustomApiResponse.fail(ApiResponseCode.JSON_PROCESSING_EXCEPTION, null);
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
package com.bio.bio_backend.global.exception;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import com.bio.bio_backend.global.dto.CustomApiResponse;
|
||||
import com.bio.bio_backend.global.utils.ApiResponseCode;
|
||||
|
||||
@Component
|
||||
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
|
||||
private final HandlerExceptionResolver resolver;
|
||||
|
||||
public JwtAccessDeniedHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||
|
||||
Exception exception = (Exception) request.getAttribute("exception");
|
||||
|
||||
if (exception != null) {
|
||||
resolver.resolveException(request, response, null, exception);
|
||||
} else {
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
new ObjectMapper().writeValue(response.getWriter(),
|
||||
CustomApiResponse.fail(ApiResponseCode.COMMON_FORBIDDEN, null));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.bio.bio_backend.global.exception;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
private final HandlerExceptionResolver resolver;
|
||||
|
||||
public JwtAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||||
|
||||
Exception exception = (Exception) request.getAttribute("exception");
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
|
||||
if (exception == null) {
|
||||
return;
|
||||
}
|
||||
resolver.resolveException(request, response, null, exception);
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
//package com.bio.bio_backend.filter;
|
||||
//
|
||||
//import java.io.IOException;
|
||||
//import java.sql.Timestamp;
|
||||
//
|
||||
//import org.springframework.security.core.Authentication;
|
||||
//import org.springframework.security.core.context.SecurityContextHolder;
|
||||
//import org.springframework.web.filter.OncePerRequestFilter;
|
||||
//
|
||||
//import jakarta.servlet.FilterChain;
|
||||
//import jakarta.servlet.ServletException;
|
||||
//import jakarta.servlet.http.HttpServletRequest;
|
||||
//import jakarta.servlet.http.HttpServletResponse;
|
||||
//import com.bio.bio_backend.domain.common.dto.AccessLogDTO;
|
||||
//import com.bio.bio_backend.domain.common.service.AccessLogService;
|
||||
//import com.bio.bio_backend.domain.user.member.dto.MemberDTO;
|
||||
//import com.bio.bio_backend.global.utils.HttpUtils;
|
||||
//
|
||||
//public class HttpLoggingFilter extends OncePerRequestFilter {
|
||||
//
|
||||
//// private AccessLogService accessLogService;
|
||||
// private HttpUtils httpUtils;
|
||||
//
|
||||
// public HttpLoggingFilter(AccessLogService accessLogService, HttpUtils httpUtils) {
|
||||
// this.accessLogService = accessLogService;
|
||||
// this.httpUtils = httpUtils;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
// throws ServletException, IOException {
|
||||
// // Request 요청 시간
|
||||
// Long startedAt = System.currentTimeMillis();
|
||||
// filterChain.doFilter(request, response);
|
||||
// Long finishedAt = System.currentTimeMillis();
|
||||
//
|
||||
// Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
// MemberDTO member = null;
|
||||
// if(auth != null && auth.getPrincipal() instanceof MemberDTO) {
|
||||
// member = (MemberDTO) auth.getPrincipal();
|
||||
// }
|
||||
//
|
||||
// AccessLogDTO log = new AccessLogDTO();
|
||||
// log.setMbrSeq(member == null ? -1 : member.getSeq());
|
||||
// log.setType(httpUtils.getResponseType(response.getContentType()));
|
||||
// log.setMethod(request.getMethod());
|
||||
// log.setIp(httpUtils.getClientIp());
|
||||
// log.setUri(request.getRequestURI());
|
||||
// log.setReqAt(new Timestamp(startedAt));
|
||||
// log.setResAt(new Timestamp(finishedAt));
|
||||
// log.setElapsedTime(finishedAt - startedAt);
|
||||
// log.setResStatus(String.valueOf(response.getStatus()));
|
||||
//
|
||||
// accessLogService.createAccessLog(log);
|
||||
// }
|
||||
//
|
||||
//}
|
@@ -0,0 +1,120 @@
|
||||
package com.bio.bio_backend.global.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import com.bio.bio_backend.global.dto.CustomApiResponse;
|
||||
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.global.utils.ApiResponseCode;
|
||||
import com.bio.bio_backend.global.utils.JwtUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final MemberService memberService;
|
||||
private final ModelMapper modelMapper;
|
||||
private final JwtUtils jwtUtils;
|
||||
private final Environment env;
|
||||
|
||||
// 사용자 login 인증 처리
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||
throws AuthenticationException {
|
||||
|
||||
LoginRequestDto req = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
|
||||
|
||||
// UsernamePasswordAuthenticationToken authToken;
|
||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(req.getId(), req.getPw());
|
||||
|
||||
/*
|
||||
if (req.getLoginLogFlag() == 1) {
|
||||
authToken = new UsernamePasswordAuthenticationToken("admin2", "test123!"); // 비밀번호는 실제 비밀번호로 설정해야 함
|
||||
} else {
|
||||
throw new AuthenticationServiceException("login fail");
|
||||
}
|
||||
*/
|
||||
return authenticationManager.authenticate(authToken);
|
||||
}
|
||||
|
||||
// 사용자 인증 성공 후 token 발급
|
||||
@Override
|
||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
|
||||
FilterChain chain, Authentication authResult) throws IOException, ServletException {
|
||||
|
||||
UserDetails userDetails = (UserDetails) authResult.getPrincipal();
|
||||
|
||||
MemberDto member = (MemberDto) userDetails;
|
||||
|
||||
String accessToken = jwtUtils.generateToken(userDetails.getUsername(), member.getRole(),
|
||||
Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_access"))));
|
||||
|
||||
String refreshToken = jwtUtils.generateToken(userDetails.getUsername(), member.getRole(),
|
||||
Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time_refresh"))));
|
||||
|
||||
|
||||
member.setRefreshToken(refreshToken);
|
||||
member.setLastLoginAt(Timestamp.valueOf(LocalDateTime.now()));
|
||||
|
||||
memberService.updateRefreshToken(member);
|
||||
|
||||
// Refresh 토큰 쿠키 저장
|
||||
Cookie refreshTokenCookie = new Cookie("RefreshToken", refreshToken);
|
||||
refreshTokenCookie.setHttpOnly(true);
|
||||
refreshTokenCookie.setSecure(false);
|
||||
refreshTokenCookie.setPath("/");
|
||||
refreshTokenCookie.setMaxAge(Integer.parseInt(env.getProperty("token.expiration_time_refresh")));
|
||||
|
||||
// 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(),
|
||||
refreshTokenCookie.getValue(),
|
||||
refreshTokenCookie.getMaxAge()));
|
||||
|
||||
SecurityContextHolderStrategy contextHolder = SecurityContextHolder.getContextHolderStrategy();
|
||||
SecurityContext context = contextHolder.createEmptyContext();
|
||||
context.setAuthentication(authResult);
|
||||
contextHolder.setContext(context);
|
||||
|
||||
LoginResponseDto memberData = modelMapper.map(member, LoginResponseDto.class);
|
||||
|
||||
// login 성공 메시지 전송
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
new ObjectMapper().writeValue(response.getWriter(),
|
||||
CustomApiResponse.success(ApiResponseCode.LOGIN_SUCCESSFUL, memberData));
|
||||
}
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
package com.bio.bio_backend.global.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
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.CustomApiResponse;
|
||||
import com.bio.bio_backend.domain.user.member.service.MemberService;
|
||||
import com.bio.bio_backend.global.utils.ApiResponseCode;
|
||||
import com.bio.bio_backend.global.utils.JwtUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class JwtTokenFilter 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,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
String accessToken = jwtUtils.extractAccessJwtFromRequest(request);
|
||||
String refreshToken = jwtUtils.extractRefreshJwtFromCookie(request);
|
||||
|
||||
if(accessToken == null){
|
||||
sendJsonResponse(response, CustomApiResponse.fail(ApiResponseCode.JWT_TOKEN_NULL, null));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 토큰 유효성 검사
|
||||
try {
|
||||
if (jwtUtils.validateAccessToken(accessToken)) {
|
||||
String username = jwtUtils.extractUsername(accessToken);
|
||||
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;
|
||||
}
|
||||
}
|
||||
} catch (ExpiredJwtException ignored) {
|
||||
// Access Token이 만료된 경우에만 ignored >> Refresh Token 검증 수행
|
||||
}
|
||||
// Refresh Token 유효성 검사
|
||||
if (refreshToken != null && jwtUtils.validateRefreshToken(refreshToken)) {
|
||||
String username = jwtUtils.extractUsername(refreshToken);
|
||||
String role = (String) jwtUtils.extractAllClaims(refreshToken).get("role");
|
||||
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, CustomApiResponse.fail(ApiResponseCode.All_TOKEN_INVALID, null));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
request.setAttribute("exception", e);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private void sendJsonResponse(HttpServletResponse response, CustomApiResponse<?> apiResponse) throws IOException {
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setStatus(apiResponse.getCode());
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String jsonResponse = objectMapper.writeValueAsString(apiResponse);
|
||||
response.getWriter().write(jsonResponse);
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package com.bio.bio_backend.global.security;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@Component
|
||||
public class UriAllowFilter {
|
||||
|
||||
@Autowired
|
||||
// ProgramService programService;
|
||||
|
||||
public boolean authExceptionAllow(HttpServletRequest request) {
|
||||
// 임시로 모든 요청 허용
|
||||
return true;
|
||||
|
||||
// String[] allowUrl = programService.getAuthException();
|
||||
// boolean success = Arrays.stream(allowUrl).anyMatch(pattern -> matches(pattern, request.getRequestURI()));
|
||||
// return success;
|
||||
}
|
||||
|
||||
boolean matches(String pattern, String uri) {
|
||||
String regex = pattern.replace("/**", "(/.*)?").replace("*", ".*");
|
||||
return Pattern.matches(regex, uri);
|
||||
}
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
package com.bio.bio_backend.global.security;
|
||||
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
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.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.global.constants.MemberConstants;
|
||||
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;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
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 ModelMapper mapper;
|
||||
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, mapper, jwtUtils, env);
|
||||
filter.setFilterProcessesUrl("/login"); // 로그인 EndPoint
|
||||
filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler(objectMapper));
|
||||
return filter;
|
||||
}
|
||||
|
||||
private Filter getJwtTokenFilter() {
|
||||
return new JwtTokenFilter(jwtUtils, memberService, env, uriAllowFilter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
// AuthenticationManager 설정
|
||||
AuthenticationManagerBuilder authenticationManagerBuilder =
|
||||
http.getSharedObject(AuthenticationManagerBuilder.class);
|
||||
authenticationManagerBuilder.userDetailsService(memberService).passwordEncoder(bCryptPasswordEncoder);
|
||||
|
||||
AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
|
||||
|
||||
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()
|
||||
)
|
||||
.authenticationManager(authenticationManager)
|
||||
.logout(AbstractHttpConfigurer::disable);
|
||||
|
||||
// 예외 처리 핸들링
|
||||
http.exceptionHandling((exceptionConfig) ->
|
||||
exceptionConfig
|
||||
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
|
||||
.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));
|
||||
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package com.bio.bio_backend.global.utils;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/*
|
||||
* API 관련 RESPONSE ENUM
|
||||
*/
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ApiResponseCode {
|
||||
|
||||
/*login & logout*/
|
||||
|
||||
// 200 OK
|
||||
LOGIN_SUCCESSFUL(HttpStatus.OK.value(), "Login successful"),
|
||||
|
||||
LOGOUT_SUCCESSFUL(HttpStatus.OK.value(), "Logout successful"),
|
||||
|
||||
USER_INFO_CHANGE(HttpStatus.OK.value(), "User info update successful"),
|
||||
|
||||
USER_DELETE_SUCCESSFUL(HttpStatus.OK.value(), "User delete is successful"),
|
||||
|
||||
// 401 Unauthorized
|
||||
USER_NOT_FOUND(HttpStatus.UNAUTHORIZED.value(), "User not found. Authentication failed"),
|
||||
|
||||
AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED.value(), "Password is invalid"),
|
||||
|
||||
/*auth*/
|
||||
// 401 Unauthorized
|
||||
JWT_SIGNATURE_MISMATCH(HttpStatus.UNAUTHORIZED.value(), "JWT signature does not match. Authentication failed"),
|
||||
|
||||
JWT_TOKEN_NULL(HttpStatus.UNAUTHORIZED.value(), "JWT token is null"),
|
||||
|
||||
JWT_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "Token is Expired"),
|
||||
|
||||
All_TOKEN_INVALID(HttpStatus.UNAUTHORIZED.value(), "Access and Refresh tokens are expired or invalid"),
|
||||
|
||||
/*공통 Code*/
|
||||
// 400 Bad Request
|
||||
COMMON_BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "Required request body is missing or Error"),
|
||||
|
||||
COMMON_FORMAT_WRONG(HttpStatus.BAD_REQUEST.value(), "Request format is incorrect"),
|
||||
|
||||
// 401 Unauthorized
|
||||
COMMON_UNAUTHORIZED(HttpStatus.UNAUTHORIZED.value(), "Unauthorized"),
|
||||
|
||||
// 403 Forbidden
|
||||
COMMON_FORBIDDEN(HttpStatus.FORBIDDEN.value(), "Access is denied"),
|
||||
|
||||
// 404 Not Found
|
||||
COMMON_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "Resource is not found"),
|
||||
|
||||
// 405 Method Not Allowed
|
||||
COMMON_METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "Method not Allowed"),
|
||||
|
||||
// 500 Internal Server Error
|
||||
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An error occurred on the server"),
|
||||
|
||||
ARGUMENT_NOT_VALID(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Argument is not valid"),
|
||||
|
||||
INDEX_OUT_OF_BOUND(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Index out of bounds for length"),
|
||||
|
||||
JSON_PROCESSING_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Check if it is a valid JSON format");
|
||||
|
||||
private final int statusCode;
|
||||
private final String description;
|
||||
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package com.bio.bio_backend.global.utils;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.id.IdentifierGenerator;
|
||||
|
||||
import com.bio.bio_backend.global.utils.OidUtil;
|
||||
|
||||
public class CustomIdGenerator implements IdentifierGenerator {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Serializable generate(SharedSessionContractImplementor session, Object object) {
|
||||
return OidUtil.generateOid(); // 재사용
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package com.bio.bio_backend.global.utils;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@Component
|
||||
public class HttpUtils {
|
||||
public String getClientIp() {
|
||||
String ip = "";
|
||||
HttpServletRequest request =
|
||||
((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
|
||||
|
||||
ip = request.getHeader("X-Forwarded-For");
|
||||
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Real-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-RealIP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("REMOTE_ADDR");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
HttpServletRequest request =
|
||||
((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
|
||||
|
||||
return request.getRequestURI();
|
||||
}
|
||||
|
||||
public String getResponseType(String contentType) {
|
||||
if(contentType == null) {
|
||||
return "";
|
||||
} else if(contentType.contains("text/html")) {
|
||||
return "PAGE";
|
||||
} else if (contentType.contains("application/json")) {
|
||||
return "API";
|
||||
};
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
90
src/main/java/com/bio/bio_backend/global/utils/JwtUtils.java
Normal file
90
src/main/java/com/bio/bio_backend/global/utils/JwtUtils.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package com.bio.bio_backend.global.utils;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class JwtUtils {
|
||||
|
||||
private final MemberService memberService;
|
||||
|
||||
@Value("${token.secret_key}")
|
||||
private String SECRET_KEY;
|
||||
|
||||
private SecretKey getSigningKey() {
|
||||
byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
|
||||
// Token 생성
|
||||
public String generateToken(String username, String role, long expirationTime) {
|
||||
|
||||
return Jwts.builder()
|
||||
.subject(username)
|
||||
.claim("role", role)
|
||||
.issuedAt(new Date(System.currentTimeMillis()))
|
||||
.expiration(new Date(System.currentTimeMillis() + expirationTime))
|
||||
.signWith(getSigningKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
// Token 검증
|
||||
public Boolean validateAccessToken(String token) {
|
||||
return isTokenExpired(token);
|
||||
}
|
||||
|
||||
public Boolean validateRefreshToken(String token) {
|
||||
String saveToken = memberService.getRefreshToken(extractUsername(token));
|
||||
return (saveToken.equals(token) && isTokenExpired(token));
|
||||
}
|
||||
|
||||
private boolean isTokenExpired(String token) {
|
||||
return !extractAllClaims(token).getExpiration().before(new Date());
|
||||
}
|
||||
|
||||
public String extractUsername(String token) {
|
||||
return extractAllClaims(token).getSubject();
|
||||
}
|
||||
|
||||
public Claims extractAllClaims(String token) {
|
||||
|
||||
return Jwts.parser()
|
||||
.verifyWith(getSigningKey())
|
||||
.build()
|
||||
.parseSignedClaims(token).getPayload();
|
||||
}
|
||||
|
||||
public String extractAccessJwtFromRequest(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7); // "Bearer " 제거
|
||||
}
|
||||
return bearerToken;
|
||||
}
|
||||
|
||||
public String extractRefreshJwtFromCookie(HttpServletRequest request) {
|
||||
if (request.getCookies() != null) {
|
||||
for (Cookie cookie : request.getCookies()) {
|
||||
if ("RefreshToken".equals(cookie.getName())) {
|
||||
return cookie.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
38
src/main/java/com/bio/bio_backend/global/utils/OidUtil.java
Normal file
38
src/main/java/com/bio/bio_backend/global/utils/OidUtil.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package com.bio.bio_backend.global.utils;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class OidUtil {
|
||||
private static final int MAX_SEQUENCE = 999;
|
||||
private static volatile Long lastTimestamp = null;
|
||||
private static AtomicInteger sequence = new AtomicInteger(0);
|
||||
private static final int SERVER_ID = resolveServerId();
|
||||
|
||||
public static synchronized long generateOid() {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (lastTimestamp == null || now != lastTimestamp) {
|
||||
lastTimestamp = now;
|
||||
sequence.set(SERVER_ID);
|
||||
}
|
||||
|
||||
int index = sequence.getAndAdd(2);
|
||||
|
||||
if (index > MAX_SEQUENCE) {
|
||||
now += index / 1000;
|
||||
index = index % 1000;
|
||||
}
|
||||
|
||||
return now * 1000L + index;
|
||||
}
|
||||
|
||||
private static int resolveServerId() {
|
||||
try {
|
||||
// 서버 이중화
|
||||
// String ip = InetAddress.getLocalHost().getHostAddress();
|
||||
// if (ip.contains(".162")) return 1;
|
||||
// if (ip.contains(".163")) return 0;
|
||||
} catch (Exception ignored) {}
|
||||
return 1;
|
||||
}
|
||||
}
|
@@ -45,3 +45,9 @@ spring.output.ansi.enabled=always
|
||||
# HikariCP 연결 풀 크기 설정 (선택사항)
|
||||
# spring.datasource.hikari.maximum-pool-size=10
|
||||
|
||||
##JWT 설정
|
||||
## access : 30분 / refresh : 7일
|
||||
token.expiration_time_access=180000
|
||||
token.expiration_time_refresh=604800000
|
||||
token.secret_key= c3RhbV9qd3Rfc2VjcmV0X3Rva2Vuc3RhbV9qd3Rfc2VjcmV0X3Rva2Vu
|
||||
|
||||
|
Reference in New Issue
Block a user