jwt 소스 추가
This commit is contained in:
@@ -64,6 +64,15 @@ dependencies {
|
|||||||
// crypto
|
// crypto
|
||||||
implementation 'org.mindrot:jbcrypt:0.4'
|
implementation 'org.mindrot:jbcrypt:0.4'
|
||||||
|
|
||||||
|
// security
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
|
|
||||||
|
// JWT (jjwt 0.12.x)
|
||||||
|
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
|
||||||
|
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
|
||||||
|
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' // JSON (Jackson)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
package com.kamco.cd.kamcoback.auth;
|
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto.Basic;
|
|
||||||
import com.kamco.cd.kamcoback.auth.service.AuthService;
|
|
||||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import jakarta.validation.Valid;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
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.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@Tag(name = "관리자 관리", description = "관리자 관리 API")
|
|
||||||
@RestController
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@RequestMapping("/api/auth")
|
|
||||||
public class AuthApiController {
|
|
||||||
|
|
||||||
private final AuthService authService;
|
|
||||||
|
|
||||||
@Operation(summary = "관리자 등록", description = "관리자를 등록 합니다.")
|
|
||||||
@ApiResponses(
|
|
||||||
value = {
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "201",
|
|
||||||
description = "관리자 등록 성공",
|
|
||||||
content =
|
|
||||||
@Content(
|
|
||||||
mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = Long.class))),
|
|
||||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
|
||||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
|
||||||
})
|
|
||||||
@PostMapping("/save")
|
|
||||||
public ApiResponseDto<Long> save(
|
|
||||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
|
||||||
description = "관리자 정보",
|
|
||||||
required = true,
|
|
||||||
content =
|
|
||||||
@Content(
|
|
||||||
mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = AuthDto.SaveReq.class)))
|
|
||||||
@RequestBody
|
|
||||||
@Valid
|
|
||||||
AuthDto.SaveReq saveReq) {
|
|
||||||
return ApiResponseDto.createOK(authService.save(saveReq).getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "관리자 정보 수정", description = "관리자 정보를 수정 합니다.")
|
|
||||||
@ApiResponses(
|
|
||||||
value = {
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "201",
|
|
||||||
description = "관리자 정보 수정 성공",
|
|
||||||
content =
|
|
||||||
@Content(
|
|
||||||
mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = Long.class))),
|
|
||||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
|
||||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
|
||||||
})
|
|
||||||
@PutMapping("/update/{id}")
|
|
||||||
public ApiResponseDto<Long> update(@PathVariable Long id, @RequestBody AuthDto.SaveReq saveReq) {
|
|
||||||
return ApiResponseDto.createOK(authService.update(id, saveReq).getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "관리자 정보 탈퇴처리", description = "관리자 정보를 탈퇴처리 합니다.")
|
|
||||||
@ApiResponses(
|
|
||||||
value = {
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "201",
|
|
||||||
description = "관리자 탈퇴처리 성공",
|
|
||||||
content =
|
|
||||||
@Content(
|
|
||||||
mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = Long.class))),
|
|
||||||
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
|
|
||||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
|
||||||
})
|
|
||||||
@PutMapping("/withdrawal/{id}")
|
|
||||||
public ApiResponseDto<Long> withdrawal(@PathVariable Long id) {
|
|
||||||
return ApiResponseDto.deleteOk(authService.withdrawal(id).getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiResponses(
|
|
||||||
value = {
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "200",
|
|
||||||
description = "조회 성공",
|
|
||||||
content =
|
|
||||||
@Content(
|
|
||||||
mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = AuthDto.Basic.class))),
|
|
||||||
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
|
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
|
||||||
})
|
|
||||||
@Operation(summary = "관리자 상세조회", description = "관리자 정보를 조회 합니다.")
|
|
||||||
@GetMapping("/detail")
|
|
||||||
public ApiResponseDto<AuthDto.Basic> getDetail(
|
|
||||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
|
||||||
description = "관리자 목록 id",
|
|
||||||
required = true)
|
|
||||||
@RequestParam
|
|
||||||
Long id) {
|
|
||||||
return ApiResponseDto.ok(authService.getFindUserById(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Operation(summary = "관리자 목록", description = "관리자 목록 조회")
|
|
||||||
@ApiResponses(
|
|
||||||
value = {
|
|
||||||
@ApiResponse(
|
|
||||||
responseCode = "200",
|
|
||||||
description = "검색 성공",
|
|
||||||
content =
|
|
||||||
@Content(
|
|
||||||
mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = Page.class))),
|
|
||||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
|
||||||
})
|
|
||||||
@GetMapping("/list")
|
|
||||||
public ApiResponseDto<Page<Basic>> getUserList(
|
|
||||||
@Parameter(description = "관리자 이름") @RequestParam(required = false) String userNm,
|
|
||||||
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0")
|
|
||||||
int page,
|
|
||||||
@Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20")
|
|
||||||
int size,
|
|
||||||
@Parameter(description = "정렬 조건 (형식: 필드명,방향)", example = "name,asc")
|
|
||||||
@RequestParam(required = false)
|
|
||||||
String sort) {
|
|
||||||
AuthDto.SearchReq searchReq = new AuthDto.SearchReq(userNm, page, size, sort);
|
|
||||||
Page<AuthDto.Basic> userList = authService.getUserList(searchReq);
|
|
||||||
return ApiResponseDto.ok(userList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.kamco.cd.kamcoback.auth;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||||
|
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.mindrot.jbcrypt.BCrypt;
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CustomAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
|
private final MembersRepository membersRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
|
String username = authentication.getName();
|
||||||
|
String rawPassword = authentication.getCredentials().toString();
|
||||||
|
|
||||||
|
// 1. 유저 조회
|
||||||
|
MemberEntity member =
|
||||||
|
membersRepository
|
||||||
|
.findByEmployeeNo(username)
|
||||||
|
.orElseThrow(() -> new BadCredentialsException("ID 또는 비밀번호가 일치하지 않습니다."));
|
||||||
|
|
||||||
|
// 2. jBCrypt + 커스텀 salt 로 저장된 패스워드 비교
|
||||||
|
if (!BCrypt.checkpw(rawPassword, member.getPassword())) {
|
||||||
|
throw new BadCredentialsException("ID 또는 비밀번호가 일치하지 않습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 인증 성공 → UserDetails 생성
|
||||||
|
CustomUserDetails userDetails = new CustomUserDetails(member);
|
||||||
|
|
||||||
|
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> authentication) {
|
||||||
|
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.kamco.cd.kamcoback.auth;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
public class CustomUserDetails implements UserDetails {
|
||||||
|
|
||||||
|
private final MemberEntity member;
|
||||||
|
|
||||||
|
public CustomUserDetails(MemberEntity member) {
|
||||||
|
this.member = member;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
// 권한을 Member에서 가져오는 경우 바꾸면 됩니다 — 일단 기본값
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return member.getPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return String.valueOf(member.getUuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return true; // 추후 상태 필드에 따라 수정 가능
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return member.getStatus().equalsIgnoreCase("ACTIVE");
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemberEntity getMember() {
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.kamco.cd.kamcoback.auth;
|
||||||
|
|
||||||
|
import org.mindrot.jbcrypt.BCrypt;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
public class JBCryptPasswordEncoder implements PasswordEncoder {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encode(CharSequence rawPassword) {
|
||||||
|
throw new UnsupportedOperationException("custom salt 사용");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||||
|
if (encodedPassword == null || encodedPassword.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.kamco.cd.kamcoback.auth;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
private final UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
|
||||||
|
String token = resolveToken(request);
|
||||||
|
|
||||||
|
if (token != null && jwtTokenProvider.isValidToken(token)) {
|
||||||
|
String username = jwtTokenProvider.getSubject(token);
|
||||||
|
|
||||||
|
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||||
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
|
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveToken(HttpServletRequest request) {
|
||||||
|
String bearer = request.getHeader("Authorization");
|
||||||
|
if (bearer != null && bearer.startsWith("Bearer ")) {
|
||||||
|
return bearer.substring(7);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package com.kamco.cd.kamcoback.auth;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jws;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Date;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtTokenProvider {
|
||||||
|
|
||||||
|
@Value("${jwt.secret}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@Value("${jwt.access-token-validity-in-ms}")
|
||||||
|
private long accessTokenValidityInMs;
|
||||||
|
|
||||||
|
@Value("${jwt.refresh-token-validity-in-ms}")
|
||||||
|
private long refreshTokenValidityInMs;
|
||||||
|
|
||||||
|
private SecretKey key;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
// HS256용 SecretKey
|
||||||
|
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String createAccessToken(String subject) {
|
||||||
|
return createToken(subject, accessTokenValidityInMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String createRefreshToken(String subject) {
|
||||||
|
return createToken(subject, refreshTokenValidityInMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createToken(String subject, long validityInMs) {
|
||||||
|
Date now = new Date();
|
||||||
|
Date expiry = new Date(now.getTime() + validityInMs);
|
||||||
|
return Jwts.builder().subject(subject).issuedAt(now).expiration(expiry).signWith(key).compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubject(String token) {
|
||||||
|
var claims = parseClaims(token).getPayload();
|
||||||
|
return claims.getSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidToken(String token) {
|
||||||
|
try {
|
||||||
|
Jws<Claims> claims = parseClaims(token);
|
||||||
|
return !claims.getPayload().getExpiration().before(new Date());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Jws<Claims> parseClaims(String token) {
|
||||||
|
return Jwts.parser()
|
||||||
|
.verifyWith(key) // SecretKey 타입
|
||||||
|
.build()
|
||||||
|
.parseSignedClaims(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRefreshTokenValidityInMs() {
|
||||||
|
return refreshTokenValidityInMs;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.kamco.cd.kamcoback.auth;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.ValueOperations;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RefreshTokenService {
|
||||||
|
|
||||||
|
private final StringRedisTemplate redisTemplate;
|
||||||
|
private static final String PREFIX = "RT:";
|
||||||
|
|
||||||
|
public void save(String username, String refreshToken, long ttlMillis) {
|
||||||
|
ValueOperations<String, String> ops = redisTemplate.opsForValue();
|
||||||
|
ops.set(PREFIX + username, refreshToken, Duration.ofMillis(ttlMillis));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validate(String username, String refreshToken) {
|
||||||
|
String stored = redisTemplate.opsForValue().get(PREFIX + username);
|
||||||
|
return stored != null && stored.equals(refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(String username) {
|
||||||
|
redisTemplate.delete(PREFIX + username);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
package com.kamco.cd.kamcoback.auth.dto;
|
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.data.domain.Sort;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AuthDto {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public static class Basic {
|
|
||||||
|
|
||||||
private Long id;
|
|
||||||
private String userAuth;
|
|
||||||
private String userNm;
|
|
||||||
private String userId;
|
|
||||||
private String empId;
|
|
||||||
private String userEmail;
|
|
||||||
@JsonFormatDttm private ZonedDateTime createdDttm;
|
|
||||||
|
|
||||||
public Basic(
|
|
||||||
Long id,
|
|
||||||
String userAuth,
|
|
||||||
String userNm,
|
|
||||||
String userId,
|
|
||||||
String empId,
|
|
||||||
String userEmail,
|
|
||||||
ZonedDateTime createdDttm) {
|
|
||||||
this.id = id;
|
|
||||||
this.userAuth = userAuth;
|
|
||||||
this.userNm = userNm;
|
|
||||||
this.userId = userId;
|
|
||||||
this.empId = empId;
|
|
||||||
this.userEmail = userEmail;
|
|
||||||
this.createdDttm = createdDttm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Schema(name = "save request", description = "사용자 등록 정보")
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public static class SaveReq {
|
|
||||||
|
|
||||||
@Schema(description = "구분", example = "관리자/라벨러/검수자 중 하나")
|
|
||||||
@NotBlank
|
|
||||||
private String userAuth;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
@Schema(description = "이름", example = "홍길동")
|
|
||||||
private String userNm;
|
|
||||||
|
|
||||||
@Schema(description = "ID", example = "gildong")
|
|
||||||
@NotBlank
|
|
||||||
private String userId;
|
|
||||||
|
|
||||||
@Schema(description = "PW", example = "password")
|
|
||||||
@NotBlank
|
|
||||||
private String userPw;
|
|
||||||
|
|
||||||
@Schema(description = "사번", example = "사번")
|
|
||||||
@NotBlank
|
|
||||||
private String empId;
|
|
||||||
|
|
||||||
@Schema(description = "이메일", example = "gildong@naver.com")
|
|
||||||
@NotBlank
|
|
||||||
private String userEmail;
|
|
||||||
|
|
||||||
public SaveReq(
|
|
||||||
String userAuth,
|
|
||||||
String userNm,
|
|
||||||
String userId,
|
|
||||||
String userPw,
|
|
||||||
String empId,
|
|
||||||
String userEmail) {
|
|
||||||
this.userAuth = userAuth;
|
|
||||||
this.userNm = userNm;
|
|
||||||
this.userId = userId;
|
|
||||||
this.userPw = userPw;
|
|
||||||
this.empId = empId;
|
|
||||||
this.userEmail = userEmail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Schema(name = "update request", description = "사용자 수정 정보")
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public static class UpdateReq {
|
|
||||||
|
|
||||||
@Schema(description = "id", example = "1")
|
|
||||||
@NotBlank
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Schema(description = "구분", example = "관리자/라벨러/검수자 중 하나")
|
|
||||||
@NotBlank
|
|
||||||
private String userAuth;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
@Schema(description = "이름", example = "홍길동")
|
|
||||||
private String userNm;
|
|
||||||
|
|
||||||
@Schema(description = "ID", example = "gildong")
|
|
||||||
@NotBlank
|
|
||||||
private String userId;
|
|
||||||
|
|
||||||
@Schema(description = "PW", example = "password")
|
|
||||||
@NotBlank
|
|
||||||
private String userPw;
|
|
||||||
|
|
||||||
@Schema(description = "사번", example = "사번")
|
|
||||||
@NotBlank
|
|
||||||
private String empId;
|
|
||||||
|
|
||||||
@Schema(description = "이메일", example = "gildong@naver.com")
|
|
||||||
@NotBlank
|
|
||||||
private String userEmail;
|
|
||||||
|
|
||||||
public UpdateReq(
|
|
||||||
Long id,
|
|
||||||
String userAuth,
|
|
||||||
String userNm,
|
|
||||||
String userId,
|
|
||||||
String userPw,
|
|
||||||
String empId,
|
|
||||||
String userEmail) {
|
|
||||||
this.id = id;
|
|
||||||
this.userAuth = userAuth;
|
|
||||||
this.userNm = userNm;
|
|
||||||
this.userId = userId;
|
|
||||||
this.userPw = userPw;
|
|
||||||
this.empId = empId;
|
|
||||||
this.userEmail = userEmail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public static class User {
|
|
||||||
|
|
||||||
String userId;
|
|
||||||
String userPw;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Schema(name = "UserSearchReq", description = "관리자 목록 요청 정보")
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public static class SearchReq {
|
|
||||||
|
|
||||||
// 검색 조건
|
|
||||||
private String userNm;
|
|
||||||
|
|
||||||
// 페이징 파라미터
|
|
||||||
private int page = 0;
|
|
||||||
private int size = 20;
|
|
||||||
private String sort;
|
|
||||||
|
|
||||||
public Pageable toPageable() {
|
|
||||||
if (sort != null && !sort.isEmpty()) {
|
|
||||||
String[] sortParams = sort.split(",");
|
|
||||||
String property = sortParams[0];
|
|
||||||
Sort.Direction direction =
|
|
||||||
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
|
|
||||||
return PageRequest.of(page, size, Sort.by(direction, property));
|
|
||||||
}
|
|
||||||
return PageRequest.of(page, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package com.kamco.cd.kamcoback.auth.service;
|
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto.Basic;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.core.AuthCoreService;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.UserEntity;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AuthService {
|
|
||||||
|
|
||||||
private final AuthCoreService authCoreService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 등록
|
|
||||||
*
|
|
||||||
* @param saveReq
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Transactional
|
|
||||||
public UserEntity save(AuthDto.SaveReq saveReq) {
|
|
||||||
return authCoreService.save(saveReq);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 정보 수정
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param saveReq
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public UserEntity update(Long id, AuthDto.SaveReq saveReq) {
|
|
||||||
if (saveReq.getUserPw() != null) {}
|
|
||||||
return authCoreService.update(id, saveReq);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 삭제
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public UserEntity withdrawal(Long id) {
|
|
||||||
return authCoreService.withdrawal(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 시퀀스 id로 관리자 조회
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public AuthDto.Basic getFindUserById(Long id) {
|
|
||||||
return authCoreService.findUserById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 목록 조회
|
|
||||||
*
|
|
||||||
* @param searchReq
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Page<Basic> getUserList(AuthDto.SearchReq searchReq) {
|
|
||||||
return authCoreService.getUserList(searchReq);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.kamco.cd.kamcoback.members.dto;
|
package com.kamco.cd.kamcoback.common.enums;
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.config.enums.EnumType;
|
import com.kamco.cd.kamcoback.config.enums.EnumType;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.kamco.cd.kamcoback.common.exception;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class CustomApiException extends RuntimeException {
|
||||||
|
|
||||||
|
private final String codeName; // ApiResponseCode enum name과 맞추는 용도 (예: "UNPROCESSABLE_ENTITY")
|
||||||
|
private final HttpStatus status; // 응답으로 내려줄 HttpStatus
|
||||||
|
|
||||||
|
public CustomApiException(String codeName, HttpStatus status, String message) {
|
||||||
|
super(message);
|
||||||
|
this.codeName = codeName;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomApiException(String codeName, HttpStatus status) {
|
||||||
|
this.codeName = codeName;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.kamco.cd.kamcoback.config;
|
package com.kamco.cd.kamcoback.config;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
|
||||||
import com.kamco.cd.kamcoback.config.api.ApiLogFunction;
|
import com.kamco.cd.kamcoback.config.api.ApiLogFunction;
|
||||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
|
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
|
||||||
@@ -19,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
@@ -366,4 +368,25 @@ public class GlobalExceptionHandler {
|
|||||||
|
|
||||||
return errorLogRepository.save(errorLogEntity);
|
return errorLogRepository.save(errorLogEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(CustomApiException.class)
|
||||||
|
public ResponseEntity<ApiResponseDto<String>> handleCustomApiException(
|
||||||
|
CustomApiException e, HttpServletRequest request) {
|
||||||
|
log.warn("[CustomApiException] resource : {}", e.getMessage());
|
||||||
|
|
||||||
|
String codeName = e.getCodeName();
|
||||||
|
HttpStatus status = e.getStatus();
|
||||||
|
String message = e.getMessage() == null ? ApiResponseCode.getMessage(codeName) : e.getMessage();
|
||||||
|
|
||||||
|
ApiResponseCode apiCode = ApiResponseCode.getCode(codeName);
|
||||||
|
|
||||||
|
ErrorLogEntity errorLog =
|
||||||
|
saveErrerLogData(
|
||||||
|
request, apiCode, status, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace());
|
||||||
|
|
||||||
|
ApiResponseDto<String> body =
|
||||||
|
ApiResponseDto.createException(apiCode, message, status, errorLog.getId());
|
||||||
|
|
||||||
|
return new ResponseEntity<>(body, status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.kamco.cd.kamcoback.config;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.auth.CustomAuthenticationProvider;
|
||||||
|
import com.kamco.cd.kamcoback.auth.JwtAuthenticationFilter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||||
|
private final CustomAuthenticationProvider customAuthenticationProvider;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.csrf(csrf -> csrf.disable())
|
||||||
|
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.formLogin(form -> form.disable())
|
||||||
|
.httpBasic(basic -> basic.disable())
|
||||||
|
.logout(logout -> logout.disable())
|
||||||
|
// 🔥 여기서 우리가 만든 CustomAuthenticationProvider 하나만 등록
|
||||||
|
.authenticationProvider(customAuthenticationProvider)
|
||||||
|
.authorizeHttpRequests(
|
||||||
|
auth ->
|
||||||
|
auth.requestMatchers(
|
||||||
|
"/api/auth/signin",
|
||||||
|
"/api/auth/refresh",
|
||||||
|
"/swagger-ui/**",
|
||||||
|
"/v3/api-docs/**")
|
||||||
|
.permitAll()
|
||||||
|
.anyRequest()
|
||||||
|
.authenticated())
|
||||||
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration)
|
||||||
|
throws Exception {
|
||||||
|
return configuration.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -134,7 +134,7 @@ public class ApiResponseDto<T> {
|
|||||||
FAIL_VERIFICATION("인증에 실패하였습니다."),
|
FAIL_VERIFICATION("인증에 실패하였습니다."),
|
||||||
INVALID_EMAIL("잘못된 형식의 이메일입니다."),
|
INVALID_EMAIL("잘못된 형식의 이메일입니다."),
|
||||||
REQUIRED_EMAIL("이메일은 필수 항목입니다."),
|
REQUIRED_EMAIL("이메일은 필수 항목입니다."),
|
||||||
WRONG_PASSWORD("잘못된 패스워드입니다.."),
|
WRONG_PASSWORD("잘못된 패스워드입니다."),
|
||||||
DUPLICATE_EMAIL("이미 가입된 이메일입니다."),
|
DUPLICATE_EMAIL("이미 가입된 이메일입니다."),
|
||||||
DUPLICATE_DATA("이미 등록되어 있습니다."),
|
DUPLICATE_DATA("이미 등록되어 있습니다."),
|
||||||
DATA_INTEGRITY_ERROR("데이터 무결성이 위반되어 요청을 처리할수 없습니다."),
|
DATA_INTEGRITY_ERROR("데이터 무결성이 위반되어 요청을 처리할수 없습니다."),
|
||||||
|
|||||||
171
src/main/java/com/kamco/cd/kamcoback/members/AuthController.java
Normal file
171
src/main/java/com/kamco/cd/kamcoback/members/AuthController.java
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package com.kamco.cd.kamcoback.members;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.auth.JwtTokenProvider;
|
||||||
|
import com.kamco.cd.kamcoback.auth.RefreshTokenService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseCookie;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.web.ErrorResponse;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Tag(name = "인증(Auth)", description = "로그인, 토큰 재발급, 로그아웃 API")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
private final AuthenticationManager authenticationManager;
|
||||||
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
|
private final RefreshTokenService refreshTokenService;
|
||||||
|
|
||||||
|
@Value("${token.refresh-cookie-name}")
|
||||||
|
private String refreshCookieName;
|
||||||
|
|
||||||
|
@Value("${token.refresh-cookie-secure:true}")
|
||||||
|
private boolean refreshCookieSecure;
|
||||||
|
|
||||||
|
@PostMapping("/signin")
|
||||||
|
@Operation(summary = "로그인", description = "사번 또는 이메일과 비밀번호로 로그인하여 액세스/리프레시 토큰을 발급합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "로그인 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = TokenResponse.class))),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "401",
|
||||||
|
description = "ID 또는 비밀번호 불일치",
|
||||||
|
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||||
|
})
|
||||||
|
public ResponseEntity<TokenResponse> signin(
|
||||||
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
|
description = "로그인 요청 정보",
|
||||||
|
required = true)
|
||||||
|
@RequestBody
|
||||||
|
SignInRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
Authentication authentication =
|
||||||
|
authenticationManager.authenticate(
|
||||||
|
new UsernamePasswordAuthenticationToken(request.username(), request.password()));
|
||||||
|
|
||||||
|
String username = authentication.getName(); // UserDetailsService 에서 사용한 username
|
||||||
|
|
||||||
|
String accessToken = jwtTokenProvider.createAccessToken(username);
|
||||||
|
String refreshToken = jwtTokenProvider.createRefreshToken(username);
|
||||||
|
|
||||||
|
// Redis에 RefreshToken 저장 (TTL = 7일)
|
||||||
|
refreshTokenService.save(
|
||||||
|
username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());
|
||||||
|
|
||||||
|
// HttpOnly + Secure 쿠키에 RefreshToken 저장
|
||||||
|
ResponseCookie cookie =
|
||||||
|
ResponseCookie.from(refreshCookieName, refreshToken)
|
||||||
|
.httpOnly(true)
|
||||||
|
.secure(refreshCookieSecure) // 로컬 개발에서 http만 쓰면 false 로 바꿔야 할 수도 있음
|
||||||
|
.path("/")
|
||||||
|
.maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
|
||||||
|
.sameSite("Strict")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(new TokenResponse(accessToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/refresh")
|
||||||
|
@Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 새로운 액세스/리프레시 토큰을 재발급합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "재발급 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = TokenResponse.class))),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "401",
|
||||||
|
description = "만료되었거나 유효하지 않은 리프레시 토큰",
|
||||||
|
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
|
||||||
|
})
|
||||||
|
public ResponseEntity<TokenResponse> refresh(String refreshToken, HttpServletResponse response) {
|
||||||
|
if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) {
|
||||||
|
return ResponseEntity.status(401).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
String username = jwtTokenProvider.getSubject(refreshToken);
|
||||||
|
|
||||||
|
// Redis에 저장된 RefreshToken과 일치하는지 확인
|
||||||
|
if (!refreshTokenService.validate(username, refreshToken)) {
|
||||||
|
return ResponseEntity.status(401).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 새 토큰 발급
|
||||||
|
String newAccessToken = jwtTokenProvider.createAccessToken(username);
|
||||||
|
String newRefreshToken = jwtTokenProvider.createRefreshToken(username);
|
||||||
|
|
||||||
|
// Redis 갱신
|
||||||
|
refreshTokenService.save(
|
||||||
|
username, newRefreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());
|
||||||
|
|
||||||
|
// 쿠키 갱신
|
||||||
|
ResponseCookie cookie =
|
||||||
|
ResponseCookie.from(refreshCookieName, newRefreshToken)
|
||||||
|
.httpOnly(true)
|
||||||
|
.secure(refreshCookieSecure)
|
||||||
|
.path("/")
|
||||||
|
.maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
|
||||||
|
.sameSite("Strict")
|
||||||
|
.build();
|
||||||
|
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(new TokenResponse(newAccessToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/logout")
|
||||||
|
@Operation(summary = "로그아웃", description = "현재 사용자의 토큰을 무효화(블랙리스트 처리 또는 리프레시 토큰 삭제)합니다.")
|
||||||
|
@ApiResponses({
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "로그아웃 성공",
|
||||||
|
content = @Content(schema = @Schema(implementation = Void.class)))
|
||||||
|
})
|
||||||
|
public ResponseEntity<Void> logout(Authentication authentication, HttpServletResponse response) {
|
||||||
|
if (authentication != null) {
|
||||||
|
String username = authentication.getName();
|
||||||
|
// Redis에서 RefreshToken 삭제
|
||||||
|
refreshTokenService.delete(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 쿠키 삭제 (Max-Age=0)
|
||||||
|
ResponseCookie cookie =
|
||||||
|
ResponseCookie.from(refreshCookieName, "")
|
||||||
|
.httpOnly(true)
|
||||||
|
.secure(refreshCookieSecure)
|
||||||
|
.path("/")
|
||||||
|
.maxAge(0)
|
||||||
|
.sameSite("Strict")
|
||||||
|
.build();
|
||||||
|
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
|
||||||
|
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Schema(description = "로그인 요청 DTO")
|
||||||
|
public record SignInRequest(
|
||||||
|
@Schema(description = "사번", example = "11111") String username,
|
||||||
|
@Schema(description = "비밀번호", example = "kamco1234!") String password) {}
|
||||||
|
|
||||||
|
public record TokenResponse(String accessToken) {}
|
||||||
|
}
|
||||||
@@ -12,7 +12,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springdoc.core.annotations.ParameterObject;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -40,8 +42,9 @@ public class MembersApiController {
|
|||||||
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
|
||||||
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
|
||||||
})
|
})
|
||||||
@PostMapping("/list")
|
@GetMapping
|
||||||
public ApiResponseDto<Page<Basic>> getMemberList(@RequestBody MembersDto.SearchReq searchReq) {
|
public ApiResponseDto<Page<Basic>> getMemberList(
|
||||||
|
@ParameterObject MembersDto.SearchReq searchReq) {
|
||||||
return ApiResponseDto.ok(membersService.findByMembers(searchReq));
|
return ApiResponseDto.ok(membersService.findByMembers(searchReq));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.kamco.cd.kamcoback.members.dto;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MemberDetails implements UserDetails {
|
||||||
|
|
||||||
|
private final MemberEntity member;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
// TODO: tb_member_role 에서 역할 꺼내서 권한으로 변환하고 싶으면 여기 구현
|
||||||
|
// 예시 (나중에 MemberRoleEntity 보고 수정):
|
||||||
|
// return member.getTbMemberRoles().stream()
|
||||||
|
// .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getRoleName()))
|
||||||
|
// .toList();
|
||||||
|
|
||||||
|
return List.of(); // 일단 빈 권한 리스트
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return member.getPassword(); // 암호화된 비밀번호
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
// 로그인 ID 로 무엇을 쓸지 선택
|
||||||
|
// 1) 이메일 로그인:
|
||||||
|
return member.getEmail();
|
||||||
|
|
||||||
|
// 2) 사번으로 로그인하고 싶으면:
|
||||||
|
// return member.getEmployeeNo();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
// status 가 ACTIVE 일 때만 로그인 허용
|
||||||
|
return "ACTIVE".equalsIgnoreCase(member.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemberEntity getMember() {
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.kamco.cd.kamcoback.members.dto;
|
package com.kamco.cd.kamcoback.members.dto;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.common.enums.RoleType;
|
||||||
import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid;
|
import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid;
|
||||||
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.kamco.cd.kamcoback.members.service;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.auth.CustomUserDetails;
|
||||||
|
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||||
|
import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MemberDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
|
private final MembersRepository membersRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
MemberEntity member =
|
||||||
|
membersRepository
|
||||||
|
.findByEmployeeNo(username)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("USER NOT FOUND"));
|
||||||
|
|
||||||
|
return new CustomUserDetails(member);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
package com.kamco.cd.kamcoback.members.service;
|
package com.kamco.cd.kamcoback.members.service;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
|
||||||
import com.kamco.cd.kamcoback.config.BCryptSaltGenerator;
|
import com.kamco.cd.kamcoback.config.BCryptSaltGenerator;
|
||||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||||
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
|
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
|
||||||
import com.kamco.cd.kamcoback.postgres.core.MembersCoreService;
|
import com.kamco.cd.kamcoback.postgres.core.MembersCoreService;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.mindrot.jbcrypt.BCrypt;
|
import org.mindrot.jbcrypt.BCrypt;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -40,8 +42,12 @@ public class MembersService {
|
|||||||
|
|
||||||
if (StringUtils.isNotBlank(updateReq.getPassword())) {
|
if (StringUtils.isNotBlank(updateReq.getPassword())) {
|
||||||
|
|
||||||
|
if (!this.isValidPassword(updateReq.getPassword())) {
|
||||||
|
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
if (StringUtils.isBlank(updateReq.getEmployeeNo())) {
|
if (StringUtils.isBlank(updateReq.getEmployeeNo())) {
|
||||||
throw new HttpMessageNotReadableException("패스워드 변경시 사번은 필수 값입니다.");
|
throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
// salt 생성, 사번이 salt
|
// salt 생성, 사번이 salt
|
||||||
@@ -55,4 +61,15 @@ public class MembersService {
|
|||||||
|
|
||||||
membersCoreService.updateMembers(uuid, updateReq);
|
membersCoreService.updateMembers(uuid, updateReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 대문자 1개 이상 소문자 1개 이상 숫자 1개 이상 특수문자(!@#$) 1개 이상
|
||||||
|
*
|
||||||
|
* @param password
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean isValidPassword(String password) {
|
||||||
|
String regex = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[!@#$]).{8,20}$";
|
||||||
|
return Pattern.matches(regex, password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
package com.kamco.cd.kamcoback.postgres.core;
|
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto.SaveReq;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.UserEntity;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.repository.auth.AuthRepository;
|
|
||||||
import jakarta.persistence.EntityNotFoundException;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AuthCoreService {
|
|
||||||
|
|
||||||
private final AuthRepository authRepository;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 등록
|
|
||||||
*
|
|
||||||
* @param saveReq
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public UserEntity save(SaveReq saveReq) {
|
|
||||||
if (authRepository.findByUserId(saveReq.getUserId()).isPresent()) {
|
|
||||||
new EntityNotFoundException("중복된 아이디가 있습니다. " + saveReq.getUserId());
|
|
||||||
}
|
|
||||||
|
|
||||||
UserEntity userEntity =
|
|
||||||
new UserEntity(
|
|
||||||
null,
|
|
||||||
saveReq.getUserAuth(),
|
|
||||||
saveReq.getUserNm(),
|
|
||||||
saveReq.getUserId(),
|
|
||||||
saveReq.getEmpId(),
|
|
||||||
saveReq.getUserEmail(),
|
|
||||||
saveReq.getUserPw());
|
|
||||||
|
|
||||||
return authRepository.save(userEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 정보 수정
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param saveReq
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public UserEntity update(Long id, AuthDto.SaveReq saveReq) {
|
|
||||||
UserEntity userEntity =
|
|
||||||
authRepository.findById(id).orElseThrow(() -> new RuntimeException("유저가 존재하지 않습니다."));
|
|
||||||
|
|
||||||
if (saveReq.getUserAuth() != null) {
|
|
||||||
userEntity.setUserAuth(saveReq.getUserAuth());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveReq.getUserNm() != null) {
|
|
||||||
userEntity.setUserNm(saveReq.getUserNm());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveReq.getUserId() != null) {
|
|
||||||
userEntity.setUserId(saveReq.getUserId());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveReq.getEmpId() != null) {
|
|
||||||
userEntity.setEmpId(saveReq.getEmpId());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveReq.getUserEmail() != null) {
|
|
||||||
userEntity.setUserEmail(saveReq.getUserEmail());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveReq.getUserPw() != null) {
|
|
||||||
userEntity.setUserPw(saveReq.getUserPw());
|
|
||||||
}
|
|
||||||
|
|
||||||
return authRepository.save(userEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 삭제
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public UserEntity withdrawal(Long id) {
|
|
||||||
UserEntity userEntity =
|
|
||||||
authRepository.findById(id).orElseThrow(() -> new RuntimeException("유저가 존재하지 않습니다."));
|
|
||||||
|
|
||||||
userEntity.setId(id);
|
|
||||||
userEntity.setDateWithdrawal(ZonedDateTime.now());
|
|
||||||
userEntity.setState("WITHDRAWAL");
|
|
||||||
|
|
||||||
return authRepository.save(userEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 시퀀스 id로 관리자 조회
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public AuthDto.Basic findUserById(Long id) {
|
|
||||||
UserEntity entity =
|
|
||||||
authRepository
|
|
||||||
.findUserById(id)
|
|
||||||
.orElseThrow(() -> new EntityNotFoundException("관리자를 찾을 수 없습니다. " + id));
|
|
||||||
return entity.toDto();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 목록 조회
|
|
||||||
*
|
|
||||||
* @param searchReq
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Page<AuthDto.Basic> getUserList(AuthDto.SearchReq searchReq) {
|
|
||||||
return authRepository.getUserList(searchReq);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,6 +14,7 @@ import com.kamco.cd.kamcoback.postgres.repository.members.MembersRoleRepository;
|
|||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.mindrot.jbcrypt.BCrypt;
|
import org.mindrot.jbcrypt.BCrypt;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
@@ -37,12 +38,12 @@ public class MembersCoreService {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Long saveMembers(MembersDto.AddReq addReq) {
|
public Long saveMembers(MembersDto.AddReq addReq) {
|
||||||
if (membersRepository.findByEmployeeNo(addReq.getEmployeeNo())) {
|
if (membersRepository.existsByEmployeeNo(addReq.getEmployeeNo())) {
|
||||||
throw new MemberException.DuplicateMemberException(
|
throw new MemberException.DuplicateMemberException(
|
||||||
MemberException.DuplicateMemberException.Field.EMPLOYEE_NO, addReq.getEmployeeNo());
|
MemberException.DuplicateMemberException.Field.EMPLOYEE_NO, addReq.getEmployeeNo());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (membersRepository.findByEmail(addReq.getEmail())) {
|
if (membersRepository.existsByEmail(addReq.getEmail())) {
|
||||||
throw new MemberException.DuplicateMemberException(
|
throw new MemberException.DuplicateMemberException(
|
||||||
MemberException.DuplicateMemberException.Field.EMAIL, addReq.getEmail());
|
MemberException.DuplicateMemberException.Field.EMAIL, addReq.getEmail());
|
||||||
}
|
}
|
||||||
@@ -66,16 +67,17 @@ public class MembersCoreService {
|
|||||||
MemberEntity memberEntity =
|
MemberEntity memberEntity =
|
||||||
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
|
||||||
|
|
||||||
if (updateReq.getEmployeeNo() != null && !memberEntity.getEmployeeNo().isEmpty()) {
|
if (StringUtils.isNotBlank(memberEntity.getEmployeeNo())) {
|
||||||
memberEntity.setEmployeeNo(updateReq.getEmployeeNo());
|
memberEntity.setEmployeeNo(updateReq.getEmployeeNo());
|
||||||
}
|
}
|
||||||
if (updateReq.getName() != null && !updateReq.getName().isEmpty()) {
|
|
||||||
|
if (StringUtils.isNotBlank(updateReq.getName())) {
|
||||||
memberEntity.setName(updateReq.getName());
|
memberEntity.setName(updateReq.getName());
|
||||||
}
|
}
|
||||||
if (updateReq.getPassword() != null && !updateReq.getPassword().isEmpty()) {
|
if (StringUtils.isNotBlank(updateReq.getPassword())) {
|
||||||
memberEntity.setPassword(updateReq.getPassword());
|
memberEntity.setPassword(updateReq.getPassword());
|
||||||
}
|
}
|
||||||
if (updateReq.getEmail() != null && !updateReq.getEmail().isEmpty()) {
|
if (StringUtils.isNotBlank(updateReq.getEmail())) {
|
||||||
memberEntity.setEmail(updateReq.getEmail());
|
memberEntity.setEmail(updateReq.getEmail());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.kamco.cd.kamcoback.postgres.entity;
|
package com.kamco.cd.kamcoback.postgres.entity;
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
@@ -104,15 +103,4 @@ public class UserEntity {
|
|||||||
this.userEmail = userEmail;
|
this.userEmail = userEmail;
|
||||||
this.userPw = userPw;
|
this.userPw = userPw;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthDto.Basic toDto() {
|
|
||||||
return new AuthDto.Basic(
|
|
||||||
this.id,
|
|
||||||
this.userAuth,
|
|
||||||
this.userNm,
|
|
||||||
this.userId,
|
|
||||||
this.empId,
|
|
||||||
this.userEmail,
|
|
||||||
this.createdDttm);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.kamco.cd.kamcoback.postgres.repository.auth;
|
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.UserEntity;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
public interface AuthRepository extends JpaRepository<UserEntity, Long>, AuthRepositoryCustom {}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.kamco.cd.kamcoback.postgres.repository.auth;
|
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto.Basic;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.UserEntity;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
|
|
||||||
public interface AuthRepositoryCustom {
|
|
||||||
|
|
||||||
Optional<UserEntity> findByUserId(String userId);
|
|
||||||
|
|
||||||
Optional<UserEntity> findUserById(Long id);
|
|
||||||
|
|
||||||
Page<Basic> getUserList(AuthDto.SearchReq searchReq);
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package com.kamco.cd.kamcoback.postgres.repository.auth;
|
|
||||||
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto;
|
|
||||||
import com.kamco.cd.kamcoback.auth.dto.AuthDto.Basic;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.QUserEntity;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.UserEntity;
|
|
||||||
import com.querydsl.core.BooleanBuilder;
|
|
||||||
import com.querydsl.core.types.Projections;
|
|
||||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.PageImpl;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class AuthRepositoryImpl implements AuthRepositoryCustom {
|
|
||||||
|
|
||||||
private final JPAQueryFactory queryFactory;
|
|
||||||
private final QUserEntity userEntity = QUserEntity.userEntity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 유저 아이디로 조회
|
|
||||||
*
|
|
||||||
* @param userId
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Optional<UserEntity> findByUserId(String userId) {
|
|
||||||
return Optional.ofNullable(
|
|
||||||
queryFactory.selectFrom(userEntity).where(userEntity.userId.eq(userId)).fetchOne());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 유저 시퀀스 id로 조회
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Optional<UserEntity> findUserById(Long id) {
|
|
||||||
return Optional.ofNullable(
|
|
||||||
queryFactory.selectFrom(userEntity).where(userEntity.id.eq(id)).fetchOne());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 관리자 목록 조회
|
|
||||||
*
|
|
||||||
* @param searchReq
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Page<Basic> getUserList(AuthDto.SearchReq searchReq) {
|
|
||||||
Pageable pageable = searchReq.toPageable();
|
|
||||||
BooleanBuilder builder = new BooleanBuilder();
|
|
||||||
if (searchReq.getUserNm() != null && !searchReq.getUserNm().isEmpty()) {
|
|
||||||
builder.and(likeName(userEntity, searchReq.getUserNm()));
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Basic> content =
|
|
||||||
queryFactory
|
|
||||||
.select(
|
|
||||||
Projections.constructor(
|
|
||||||
AuthDto.Basic.class,
|
|
||||||
userEntity.id,
|
|
||||||
userEntity.userAuth,
|
|
||||||
userEntity.userNm,
|
|
||||||
userEntity.userId,
|
|
||||||
userEntity.empId,
|
|
||||||
userEntity.userEmail,
|
|
||||||
userEntity.createdDttm))
|
|
||||||
.from(userEntity)
|
|
||||||
.where(builder)
|
|
||||||
.offset(pageable.getOffset())
|
|
||||||
.limit(pageable.getPageSize())
|
|
||||||
.orderBy(userEntity.userId.asc())
|
|
||||||
.fetch();
|
|
||||||
|
|
||||||
long total = queryFactory.select(userEntity.id).from(userEntity).where(builder).fetchCount();
|
|
||||||
return new PageImpl<>(content, pageable, total);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BooleanExpression likeName(QUserEntity entity, String nameStr) {
|
|
||||||
if (nameStr == null || nameStr.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// ex) WHERE LOWER(name) LIKE LOWER('%입력값%')
|
|
||||||
return entity.userNm.containsIgnoreCase(nameStr.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,11 +9,13 @@ import org.springframework.data.domain.Page;
|
|||||||
|
|
||||||
public interface MembersRepositoryCustom {
|
public interface MembersRepositoryCustom {
|
||||||
|
|
||||||
boolean findByEmployeeNo(String employeeNo);
|
boolean existsByEmployeeNo(String employeeNo);
|
||||||
|
|
||||||
boolean findByEmail(String email);
|
boolean existsByEmail(String email);
|
||||||
|
|
||||||
Page<Basic> findByMembers(MembersDto.SearchReq searchReq);
|
Page<Basic> findByMembers(MembersDto.SearchReq searchReq);
|
||||||
|
|
||||||
Optional<MemberEntity> findByUUID(UUID uuid);
|
Optional<MemberEntity> findByUUID(UUID uuid);
|
||||||
|
|
||||||
|
Optional<MemberEntity> findByEmployeeNo(String employeeNo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.kamco.cd.kamcoback.postgres.repository.members;
|
package com.kamco.cd.kamcoback.postgres.repository.members;
|
||||||
|
|
||||||
|
import com.kamco.cd.kamcoback.common.enums.RoleType;
|
||||||
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
import com.kamco.cd.kamcoback.members.dto.MembersDto;
|
||||||
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
|
import com.kamco.cd.kamcoback.members.dto.MembersDto.Basic;
|
||||||
import com.kamco.cd.kamcoback.members.dto.RoleType;
|
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
import com.kamco.cd.kamcoback.postgres.entity.MemberEntity;
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.QMemberEntity;
|
import com.kamco.cd.kamcoback.postgres.entity.QMemberEntity;
|
||||||
import com.kamco.cd.kamcoback.postgres.entity.QMemberRoleEntity;
|
import com.kamco.cd.kamcoback.postgres.entity.QMemberRoleEntity;
|
||||||
@@ -14,6 +14,7 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageImpl;
|
import org.springframework.data.domain.PageImpl;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
@@ -34,7 +35,7 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean findByEmployeeNo(String employeeNo) {
|
public boolean existsByEmployeeNo(String employeeNo) {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectOne()
|
.selectOne()
|
||||||
.from(memberEntity)
|
.from(memberEntity)
|
||||||
@@ -50,7 +51,7 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean findByEmail(String email) {
|
public boolean existsByEmail(String email) {
|
||||||
return queryFactory
|
return queryFactory
|
||||||
.selectOne()
|
.selectOne()
|
||||||
.from(memberEntity)
|
.from(memberEntity)
|
||||||
@@ -71,7 +72,7 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
|||||||
BooleanBuilder builder = new BooleanBuilder();
|
BooleanBuilder builder = new BooleanBuilder();
|
||||||
BooleanBuilder leftBuilder = new BooleanBuilder();
|
BooleanBuilder leftBuilder = new BooleanBuilder();
|
||||||
|
|
||||||
if (searchReq.getField() != null && !searchReq.getField().isEmpty()) {
|
if (StringUtils.isNotBlank(searchReq.getField())) {
|
||||||
switch (searchReq.getField()) {
|
switch (searchReq.getField()) {
|
||||||
case "name" ->
|
case "name" ->
|
||||||
builder.and(memberEntity.name.containsIgnoreCase(searchReq.getKeyword().trim()));
|
builder.and(memberEntity.name.containsIgnoreCase(searchReq.getKeyword().trim()));
|
||||||
@@ -142,4 +143,13 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom {
|
|||||||
return Optional.ofNullable(
|
return Optional.ofNullable(
|
||||||
queryFactory.selectFrom(memberEntity).where(memberEntity.uuid.eq(uuid)).fetchOne());
|
queryFactory.selectFrom(memberEntity).where(memberEntity.uuid.eq(uuid)).fetchOne());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<MemberEntity> findByEmployeeNo(String employeeNo) {
|
||||||
|
return Optional.ofNullable(
|
||||||
|
queryFactory
|
||||||
|
.selectFrom(memberEntity)
|
||||||
|
.where(memberEntity.employeeNo.eq(employeeNo))
|
||||||
|
.fetchOne());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,5 +35,15 @@ spring:
|
|||||||
port: 6379
|
port: 6379
|
||||||
password: kamco
|
password: kamco
|
||||||
|
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b"
|
||||||
|
access-token-validity-in-ms: 86400000 # 1일
|
||||||
|
refresh-token-validity-in-ms: 604800000 # 7일
|
||||||
|
|
||||||
|
token:
|
||||||
|
refresh-cookie-name: kamco-dev # 개발용 쿠키 이름
|
||||||
|
refresh-cookie-secure: false # 로컬 http 테스트면 false
|
||||||
|
|
||||||
member:
|
member:
|
||||||
init_password: kamco1234!
|
init_password: kamco1234!
|
||||||
|
|||||||
@@ -25,9 +25,18 @@ spring:
|
|||||||
|
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: 192.168.2.109
|
host: localhost
|
||||||
port: 6379
|
port: 6379
|
||||||
password: kamco
|
password: 1234
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b"
|
||||||
|
access-token-validity-in-ms: 86400000 # 1일
|
||||||
|
refresh-token-validity-in-ms: 604800000 # 7일
|
||||||
|
|
||||||
|
token:
|
||||||
|
refresh-cookie-name: kamco-local # 개발용 쿠키 이름
|
||||||
|
refresh-cookie-secure: false # 로컬 http 테스트면 false
|
||||||
|
|
||||||
member:
|
member:
|
||||||
init_password: kamco1234!
|
init_password: kamco1234!
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ spring:
|
|||||||
minimum-idle: 10
|
minimum-idle: 10
|
||||||
maximum-pool-size: 20
|
maximum-pool-size: 20
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b"
|
||||||
|
access-token-validity-in-ms: 86400000 # 1일
|
||||||
|
refresh-token-validity-in-ms: 604800000 # 7일
|
||||||
|
|
||||||
|
token:
|
||||||
|
refresh-cookie-name: kamco # 개발용 쿠키 이름
|
||||||
|
refresh-cookie-secure: true # 로컬 http 테스트면 false
|
||||||
|
|
||||||
member:
|
member:
|
||||||
init_password: kamco1234!
|
init_password: kamco1234!
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user