Initial commit

This commit is contained in:
jooho
2021-10-20 17:12:00 +09:00
parent 0c884beff8
commit 8caa4bbc5a
487 changed files with 44198 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
package org.egovframe.cloud.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* org.egovframe.cloud.userservice.UserApplication
* <p>
* 유저 서비스 어플리케이션 클래스
* Eureka Client 로 설정했기 때문에 Eureka Server 가 먼저 기동되어야 한다.
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@ComponentScan({"org.egovframe.cloud.common", "org.egovframe.cloud.servlet", "org.egovframe.cloud.userservice"}) // org.egovframe.cloud.common package 포함하기 위해
@EntityScan({"org.egovframe.cloud.servlet.domain", "org.egovframe.cloud.userservice.domain"})
@EnableDiscoveryClient
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,42 @@
package org.egovframe.cloud.userservice.api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Locale;
/**
* org.egovframe.cloud.userservice.api.MessageSourceApiController
* <p>
* MessageSource 정상 확인을 위한 컨트롤러
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/08/10
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/08/10 jaeyeolkim 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
@RestController
public class MessageSourceApiController {
private final MessageSource messageSource;
@GetMapping("/api/v1/messages/{code}/{lang}")
public String getMessage(@PathVariable String code, @PathVariable String lang) {
Locale locale = "en".equals(lang)? Locale.ENGLISH : Locale.KOREAN;
String message = messageSource.getMessage(code, null, locale);
log.info("code={}, lang={}, message={}", code, lang, message);
return message;
}
}

View File

@@ -0,0 +1,153 @@
package org.egovframe.cloud.userservice.api.role;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.userservice.api.role.dto.AuthorizationListResponseDto;
import org.egovframe.cloud.userservice.api.role.dto.AuthorizationResponseDto;
import org.egovframe.cloud.userservice.api.role.dto.AuthorizationSaveRequestDto;
import org.egovframe.cloud.userservice.api.role.dto.AuthorizationUpdateRequestDto;
import org.egovframe.cloud.userservice.service.role.AuthorizationService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* org.egovframe.cloud.userservice.api.role.AuthorizationApiController
* <p>
* API Gateway 의 RestApiAuthorization.check 메소드에 의해 호출된다.
* 요청 url에 대한 사용자 인가 서비스를 수행하는 클래스
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/07/19
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/19 jaeyeolkim 최초 생성
* </pre>
*/
@Slf4j
@RequiredArgsConstructor
@RestController
public class AuthorizationApiController {
/**
* 인가 서비스
*/
private final AuthorizationService authorizationService;
/**
* 인가 여부 확인
*
* @param httpMethod Http Method
* @param requestPath 요청 경로
* @return Boolean 인가 여부
*/
@GetMapping("/api/v1/authorizations/check")
public Boolean isAuthorization(@RequestParam("httpMethod") String httpMethod, @RequestParam("requestPath") String requestPath) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userId = authentication.getName();
List<String> roles = authentication.getAuthorities().stream().map(GrantedAuthority::toString).collect(Collectors.toList());
// 사용자 아이디로 조회
// return authorizationService.isAuthorization(userId, httpMethod, requestPath);
// 권한으로 조회
Boolean isAuth = authorizationService.isAuthorization(roles, httpMethod, requestPath);
log.info("[isAuthorization={}] authentication.isAuthenticated()={}, userId={}, httpMethod={}, requestPath={}, roleList={}", isAuth, authentication.isAuthenticated(), userId, httpMethod, requestPath, roles);
return isAuth;
}
/**
* 인가 페이지 목록 조회
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<AuthorizationListResponseDto> 페이지 인가 목록 응답 DTO
*/
@GetMapping("/api/v1/authorizations")
public Page<AuthorizationListResponseDto> findPage(RequestDto requestDto,
@PageableDefault(sort = "sort_seq", direction = Sort.Direction.ASC) Pageable pageable) {
return authorizationService.findPage(requestDto, pageable);
}
/**
* 인가 단건 조회
*
* @param authorizationNo 인가 번호
* @return AuthorizationResponseDto 인가 상세 응답 DTO
*/
@GetMapping("/api/v1/authorizations/{authorizationNo}")
public AuthorizationResponseDto findById(@PathVariable Integer authorizationNo) {
return authorizationService.findById(authorizationNo);
}
/**
* 인가 다음 정렬 순서 조회
*
* @return Integer 다음 정렬 순서
*/
@GetMapping("/api/v1/authorizations/sort-seq/next")
public Integer findNextSortSeq() {
return authorizationService.findNextSortSeq();
}
/**
* 인가 등록
*
* @param requestDto 인가 등록 요청 DTO
* @return AuthorizationResponseDto 인가 상세 응답 DTO
*/
@PostMapping("/api/v1/authorizations")
public AuthorizationResponseDto save(@RequestBody @Valid AuthorizationSaveRequestDto requestDto) {
return authorizationService.save(requestDto);
}
/**
* 인가 수정
*
* @param authorizationNo 인가 번호
* @param requestDto 인가 수정 요청 DTO
* @return AuthorizationResponseDto 인가 상세 응답 DTO
*/
@PutMapping("/api/v1/authorizations/{authorizationNo}")
public AuthorizationResponseDto update(@PathVariable Integer authorizationNo, @RequestBody @Valid AuthorizationUpdateRequestDto requestDto) {
return authorizationService.update(authorizationNo, requestDto);
}
/**
* 인가 삭제
*
* @param authorizationNo 인가 번호
*/
@DeleteMapping("/api/v1/authorizations/{authorizationNo}")
public void delete(@PathVariable Integer authorizationNo) {
authorizationService.delete(authorizationNo);
}
}

View File

@@ -0,0 +1,65 @@
package org.egovframe.cloud.userservice.api.role;
import lombok.RequiredArgsConstructor;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleListResponseDto;
import org.egovframe.cloud.userservice.service.role.RoleService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* org.egovframe.cloud.userservice.api.role.RoleApiController
* <p>
* 권한 Rest API 컨트롤러 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/07
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/07 jooho 최초 생성
* </pre>
*/
@RequiredArgsConstructor
@RestController
public class RoleApiController {
/**
* 권한 서비스
*/
private final RoleService roleService;
/**
* 권한 페이지 목록 조회
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<RoleListResponseDto> 페이지 권한 목록 응답 DTO
*/
@GetMapping("/api/v1/roles")
public Page<RoleListResponseDto> findPage(RequestDto requestDto,
@PageableDefault(sort = "sort_seq", direction = Sort.Direction.ASC) Pageable pageable) {
return roleService.findPage(requestDto, pageable);
}
/**
* 권한 정렬 순서 오름차순 전체 목록 조회
*
* @return List<RoleListResponseDto>
*/
@GetMapping("/api/v1/roles/all")
public List<RoleListResponseDto> findAll() {
return roleService.findAllBySort(Sort.by(Sort.Direction.ASC, "sortSeq"));
}
}

View File

@@ -0,0 +1,76 @@
package org.egovframe.cloud.userservice.api.role;
import lombok.RequiredArgsConstructor;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationDeleteRequestDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationListRequestDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationListResponseDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationSaveRequestDto;
import org.egovframe.cloud.userservice.service.role.RoleAuthorizationService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* org.egovframe.cloud.userservice.api.role.RoleAuthorizationApiController
* <p>
* 권한 인가 Rest API 컨트롤러 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/12
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/12 jooho 최초 생성
* </pre>
*/
@RequiredArgsConstructor
@RestController
public class RoleAuthorizationApiController {
/**
* 권한 인가 서비스
*/
private final RoleAuthorizationService roleAuthorizationService;
/**
* 권한 인가 페이지 목록 조회
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<RoleAuthorizationListResponseDto> 페이지 권한 인가 목록 응답 DTO
*/
@GetMapping("/api/v1/role-authorizations")
public Page<RoleAuthorizationListResponseDto> findPageAuthorizationList(@Valid RoleAuthorizationListRequestDto requestDto,
Pageable pageable) {
return roleAuthorizationService.findPageAuthorizationList(requestDto, pageable);
}
/**
* 권한 인가 다건 등록
*
* @param requestDtoList 권한 인가 등록 요청 DTO List
* @return List<RoleAuthorizationListResponseDto> 등록한 권한 인가 목록 응답 DTO
*/
@PostMapping("/api/v1/role-authorizations")
public List<RoleAuthorizationListResponseDto> save(@RequestBody @Valid List<RoleAuthorizationSaveRequestDto> requestDtoList) {
return roleAuthorizationService.save(requestDtoList);
}
/**
* 권한 인가 다건 삭제
*
* @param requestDtoList 권한 인가 삭제 요청 DTO List
*/
@PutMapping("/api/v1/role-authorizations")
public void delete(@RequestBody @Valid List<RoleAuthorizationDeleteRequestDto> requestDtoList) {
roleAuthorizationService.delete(requestDtoList);
}
}

View File

@@ -0,0 +1,78 @@
package org.egovframe.cloud.userservice.api.role.dto;
import com.querydsl.core.annotations.QueryProjection;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* org.egovframe.cloud.userservice.api.role.dto.AuthorizationListResponseDto
* <p>
* 인가 목록 응답 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class AuthorizationListResponseDto implements Serializable {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 7400347728171964946L;
/**
* 인가 번호
*/
private Integer authorizationNo;
/**
* 인가 명
*/
private String authorizationName;
/**
* URL 패턴 값
*/
private String urlPatternValue;
/**
* Http Method 코드
*/
private String httpMethodCode;
/**
* 정렬 순서
*/
private Integer sortSeq;
/**
* 인가 목록 응답 DTO 생성자
*
* @param authorizationNo 인가 번호
* @param authorizationName 인가 명
* @param urlPatternValue URL 패턴 값
* @param httpMethodCode Http Method 코드
* @param sortSeq 정렬 순서
*/
@QueryProjection
public AuthorizationListResponseDto(Integer authorizationNo, String authorizationName, String urlPatternValue, String httpMethodCode, Integer sortSeq) {
this.authorizationNo = authorizationNo;
this.authorizationName = authorizationName;
this.urlPatternValue = urlPatternValue;
this.httpMethodCode = httpMethodCode;
this.sortSeq = sortSeq;
}
}

View File

@@ -0,0 +1,98 @@
package org.egovframe.cloud.userservice.api.role.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.egovframe.cloud.userservice.domain.role.Authorization;
import java.util.List;
import java.util.stream.Collectors;
/**
* org.egovframe.cloud.userservice.api.role.dto.AuthorizationResponseDto
* <p>
* 인가 상세 응답 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class AuthorizationResponseDto {
/**
* 인가 번호
*/
private Integer authorizationNo;
/**
* 인가 명
*/
private String authorizationName;
/**
* URL 패턴 값
*/
private String urlPatternValue;
/**
* Http Method 코드
*/
private String httpMethodCode;
/**
* 정렬 순서
*/
private Integer sortSeq;
/**
* 권한 인가 목록 응답 DTO
*/
private List<RoleAuthorizationListResponseDto> roleAuthorizations;
/**
* 인가 엔티티를 생성자로 주입 받아서 인가 상세 응답 DTO 속성 값 세팅
*
* @param entity 인가 엔티티
*/
public AuthorizationResponseDto(Authorization entity) {
this.authorizationNo = entity.getAuthorizationNo();
this.authorizationName = entity.getAuthorizationName();
this.urlPatternValue = entity.getUrlPatternValue();
this.httpMethodCode = entity.getHttpMethodCode();
this.sortSeq = entity.getSortSeq();
if (entity.getRoleAuthorizations() != null) {
this.roleAuthorizations = entity.getRoleAuthorizations().stream()
// .map(RoleAuthorizationListResponseDto::new)
.map(e -> RoleAuthorizationListResponseDto.builder()
.roleId(e.getRoleAuthorizationId().getRoleId())
.authorizationNo(e.getRoleAuthorizationId().getAuthorizationNo())
.build())
.collect(Collectors.toList());
}
}
/**
* 인가 상세 응답 DTO 속성 값으로 인가 엔티티 빌더를 사용하여 객체 생성
*
* @return Authorization 인가 엔티티
*/
public Authorization toEntity() {
return Authorization.builder()
.authorizationNo(authorizationNo)
.authorizationName(authorizationName)
.urlPatternValue(urlPatternValue)
.httpMethodCode(httpMethodCode)
.sortSeq(sortSeq)
.build();
}
}

View File

@@ -0,0 +1,67 @@
package org.egovframe.cloud.userservice.api.role.dto;
import lombok.Getter;
import org.egovframe.cloud.userservice.domain.role.Authorization;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* org.egovframe.cloud.userservice.api.role.dto.AuthorizationSaveRequestDto
* <p>
* 인가 등록 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jooho 최초 생성
* </pre>
*/
@Getter
public class AuthorizationSaveRequestDto {
/**
* 인가 명
*/
@NotBlank(message = "{authorization.authorizationName} {err.required}")
private String authorizationName;
/**
* URL 패턴 값
*/
@NotBlank(message = "{authorization.urlPatternValue} {err.required}")
private String urlPatternValue;
/**
* Http Method 코드
*/
@NotBlank(message = "{authorization.httpMethodCode} {err.required}")
private String httpMethodCode;
/**
* 정렬 순서
*/
@NotNull(message = "{authorization.sortSeq} {err.required}")
private Integer sortSeq;
/**
* 인가 등록 요청 DTO 속성 값으로 인가 엔티티 빌더를 사용하여 객체 생성
*
* @return Authorization 인가 엔티티
*/
public Authorization toEntity() {
return Authorization.builder()
.authorizationName(authorizationName)
.urlPatternValue(urlPatternValue)
.httpMethodCode(httpMethodCode)
.sortSeq(sortSeq)
.build();
}
}

View File

@@ -0,0 +1,52 @@
package org.egovframe.cloud.userservice.api.role.dto;
import lombok.Getter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* org.egovframe.cloud.userservice.api.role.dto.AuthorizationUpdateRequestDto
* <p>
* 인가 수정 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jooho 최초 생성
* </pre>
*/
@Getter
public class AuthorizationUpdateRequestDto {
/**
* 인가 명
*/
@NotBlank(message = "{authorization.authorizationName} {err.required}")
private String authorizationName;
/**
* URL 패턴 값
*/
@NotBlank(message = "{authorization.urlPatternValue} {err.required}")
private String urlPatternValue;
/**
* Http Method 코드
*/
@NotBlank(message = "{authorization.httpMethodCode} {err.required}")
private String httpMethodCode;
/**
* 정렬 순서
*/
@NotNull(message = "{authorization.sortSeq} {err.required}")
private Integer sortSeq;
}

View File

@@ -0,0 +1,69 @@
package org.egovframe.cloud.userservice.api.role.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.egovframe.cloud.userservice.domain.role.RoleAuthorization;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationDeleteRequestDto
* <p>
* 권한 인가 삭제 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/13
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/13 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class RoleAuthorizationDeleteRequestDto {
/**
* 권한 id
*/
@NotBlank(message = "{role.roleId} {err.required}")
private String roleId;
/**
* 인가 번호
*/
@NotNull(message = "{authorization.authorizationNo} {err.required}")
private Integer authorizationNo;
/**
* 권한 인가 삭제 요청 DTO 클래스 생성자
* 빌더 패턴으로 객체 생성
*
* @param roleId 권한 id
* @param authorizationNo 인가 번호
*/
@Builder
public RoleAuthorizationDeleteRequestDto(String roleId, Integer authorizationNo) {
this.roleId = roleId;
this.authorizationNo = authorizationNo;
}
/**
* 권한 인가 삭제 DTO 속성 값으로 권한 인가 엔티티 빌더를 사용하여 객체 생성
*
* @return RoleAuthorization 권한 인가 엔티티
*/
public RoleAuthorization toEntity() {
return RoleAuthorization.builder()
.roleId(roleId)
.authorizationNo(authorizationNo)
.build();
}
}

View File

@@ -0,0 +1,35 @@
package org.egovframe.cloud.userservice.api.role.dto;
import lombok.Getter;
import org.egovframe.cloud.common.dto.RequestDto;
import javax.validation.constraints.NotBlank;
/**
* org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationListRequestDto
* <p>
* 권한 인가 목록 요청 DTO 클래스
* 인가 목록 요청 DTO 클래스(org.egovframe.cloud.portalservice.api.authorization.dto.AuthorizationListRequestDto) 상속?
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/09
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/09 jooho 최초 생성
* </pre>
*/
@Getter
public class RoleAuthorizationListRequestDto extends RequestDto {
/**
* 권한 id
*/
@NotBlank(message = "{role.roleId} {err.required}")
private String roleId;
}

View File

@@ -0,0 +1,88 @@
package org.egovframe.cloud.userservice.api.role.dto;
import com.querydsl.core.annotations.QueryProjection;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationListResponseDto
* <p>
* 권한 인가 목록 응답 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/12
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/12 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class RoleAuthorizationListResponseDto {
/**
* 권한 id
*/
private String roleId;
/**
* 인가 번호
*/
private Integer authorizationNo;
/**
* 인가 명
*/
private String authorizationName;
/**
* URL 패턴 값
*/
private String urlPatternValue;
/**
* Http Method 코드
*/
private String httpMethodCode;
/**
* 정렬 순서
*/
private Integer sortSeq;
/**
* 생성 여부
*/
private Boolean createdAt;
/**
* 권한 인가 목록 응답 DTO 클래스 생성자
*
* @param roleId 권한 id
* @param authorizationNo 인가 번호
* @param authorizationName 인가 명
* @param urlPatternValue URL 패턴 값
* @param httpMethodCode Http Method 코드
* @param sortSeq 정렬 순서
* @param createdAt 생성 여부
*/
@QueryProjection
@Builder
public RoleAuthorizationListResponseDto(String roleId, Integer authorizationNo, String authorizationName,
String urlPatternValue, String httpMethodCode, Integer sortSeq, Boolean createdAt) {
this.roleId = roleId;
this.authorizationNo = authorizationNo;
this.authorizationName = authorizationName;
this.urlPatternValue = urlPatternValue;
this.httpMethodCode = httpMethodCode;
this.sortSeq = sortSeq;
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,78 @@
package org.egovframe.cloud.userservice.api.role.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.egovframe.cloud.userservice.domain.role.RoleAuthorization;
/**
* org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationResponseDto
* <p>
* 권한 인가 상세 응답 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/12
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/12 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class RoleAuthorizationResponseDto {
/**
* 권한 id
*/
private String roleId;
/**
* 인가 번호
*/
private Integer authorizationNo;
/**
* 인가 명
*/
private String authorizationName;
/**
* URL 패턴 값
*/
private String urlPatternValue;
/**
* Http Method 코드
*/
private String httpMethodCode;
/**
* 정렬 순서
*/
private Integer sortSeq;
/**
* 생성 여부
*/
private Integer createdAt;
/**
* 권한 인가 엔티티를 생성자로 주입 받아서 권한 인가 상세 응답 DTO 속성 값 세팅
*
* @param entity 권한 인가 엔티티
*/
public RoleAuthorizationResponseDto(RoleAuthorization entity) {
this.roleId = entity.getRoleAuthorizationId().getRoleId();
this.authorizationNo = entity.getAuthorization().getAuthorizationNo();
this.authorizationName = entity.getAuthorization().getAuthorizationName();
this.urlPatternValue = entity.getAuthorization().getUrlPatternValue();
this.httpMethodCode = entity.getAuthorization().getHttpMethodCode();
this.sortSeq = entity.getAuthorization().getSortSeq();
this.createdAt = 1;
}
}

View File

@@ -0,0 +1,53 @@
package org.egovframe.cloud.userservice.api.role.dto;
import lombok.Getter;
import org.egovframe.cloud.userservice.domain.role.RoleAuthorization;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationSaveRequestDto
* <p>
* 권한 인가 등록 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/12
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/12 jooho 최초 생성
* </pre>
*/
@Getter
public class RoleAuthorizationSaveRequestDto {
/**
* 권한 id
*/
@NotBlank(message = "{role.roleId} {err.required}")
private String roleId;
/**
* 인가 번호
*/
@NotNull(message = "{authorization.authorizationNo} {err.required}")
private Integer authorizationNo;
/**
* 권한 인가 등록 요청 DTO 속성 값으로 권한 인가 엔티티 빌더를 사용하여 객체 생성
*
* @return RoleAuthorization 권한 인가 엔티티
*/
public RoleAuthorization toEntity() {
return RoleAuthorization.builder()
.roleId(roleId)
.authorizationNo(authorizationNo)
.build();
}
}

View File

@@ -0,0 +1,68 @@
package org.egovframe.cloud.userservice.api.role.dto;
import com.querydsl.core.annotations.QueryProjection;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.userservice.api.role.dto.RoleListResponseDto
* <p>
* 권한 목록 응답 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/07
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/07 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class RoleListResponseDto {
/**
* 권한 id
*/
private String roleId;
/**
* 권한 명
*/
private String roleName;
/**
* 권한 내용
*/
private String roleContent;
/**
* 생성 일시
*/
private LocalDateTime createdDate;
/**
* 권한 목록 응답 DTO 클래스 생성자
*
* @param roleId 권한 id
* @param roleName 권한 명
* @param roleContent 권한 내용
* @param createdDate 생성 일시
*/
@QueryProjection
@Builder
public RoleListResponseDto(String roleId, String roleName, String roleContent, LocalDateTime createdDate) {
this.roleId = roleId;
this.roleName = roleName;
this.roleContent = roleContent;
this.createdDate = createdDate;
}
}

View File

@@ -0,0 +1,277 @@
package org.egovframe.cloud.userservice.api.user;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.common.exception.BusinessMessageException;
import org.egovframe.cloud.common.util.MessageUtil;
import org.egovframe.cloud.userservice.api.user.dto.UserEmailRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserFindPasswordSaveRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserFindPasswordUpdateRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserJoinRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserListResponseDto;
import org.egovframe.cloud.userservice.api.user.dto.UserPasswordMatchRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserPasswordUpdateRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserResponseDto;
import org.egovframe.cloud.userservice.api.user.dto.UserSaveRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserUpdateInfoRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserUpdateRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserVerifyRequestDto;
import org.egovframe.cloud.userservice.config.TokenProvider;
import org.egovframe.cloud.userservice.service.user.UserService;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.context.SecurityContextHolder;
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 lombok.RequiredArgsConstructor;
/**
* org.egovframe.cloud.userservice.api.user.UserApiController
* <p>
* 사용자 CRUD 요청을 처리하는 REST API Controller
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@RequiredArgsConstructor // final이 선언된 모든 필드를 인자값으로 하는 생성자를 대신 생성하여, 빈을 생성자로 주입받게 한다.
@RestController
public class UserApiController {
private final UserService userService;
private final Environment env;
private final TokenProvider tokenProvider;
private final MessageUtil messageUtil;
/**
* 유저 서비스 상태 확인
*
* @return
*/
@GetMapping("/actuator/health-user")
public String status() {
return String.format("GET User Service on" +
"\n local.server.port :" + env.getProperty("local.server.port")
+ "\n token expiration time :" + env.getProperty("token.expiration_time")
+ "\n spring.datasource.username :" + env.getProperty("spring.datasource.username")
+ "\n egov.message :" + env.getProperty("egov.message")
);
}
@PostMapping("/actuator/health-user")
public String poststatus() {
return String.format("POST User Service on" +
"\n local.server.port :" + env.getProperty("local.server.port")
+ "\n token expiration time :" + env.getProperty("token.expiration_time")
+ "\n spring.datasource.username :" + env.getProperty("spring.datasource.username")
+ "\n egov.message :" + env.getProperty("egov.message")
);
}
/**
* 사용자 정보 입력
*
* @param requestDto
* @return
*/
@PostMapping("/api/v1/users")
public Long save(@RequestBody @Valid UserSaveRequestDto requestDto) {
return userService.save(requestDto);
}
/**
* 사용자 정보 업데이트
*
* @param userId
* @param requestDto
* @return
*/
@PutMapping("/api/v1/users/{userId}")
public String update(@PathVariable String userId, @RequestBody @Valid UserUpdateRequestDto requestDto) {
return userService.update(userId, requestDto);
}
/**
* 사용자 페이지 목록 조회
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<UserListResponseDto> 페이지 사용자 목록 응답 DTO
*/
@GetMapping("/api/v1/users")
public Page<UserListResponseDto> findPage(RequestDto requestDto, Pageable pageable) {
return userService.findPage(requestDto, pageable);
}
/**
* 사용자 단 건 조회
*
* @param userId
* @return
*/
@GetMapping("/api/v1/users/{userId}")
public UserResponseDto findByUserId(@PathVariable String userId) {
return userService.findByUserId(userId);
}
/**
* refresh token 과 일치하는 사용자가 있으면 access token 을 새로 발급하여 리턴한다.
*
* @param request
* @param response
* @return
*/
@PutMapping("/api/v1/users/token/refresh")
public void refreshToken(HttpServletRequest request, HttpServletResponse response) {
String refreshToken = request.getHeader(HttpHeaders.AUTHORIZATION);
tokenProvider.refreshToken(refreshToken, response);
}
/**
* 이메일 중복 확인
*
* @param requestDto 사용자 이메일 확인 요청 DTO
* @return Boolean 중복 여부
*/
@PostMapping("/api/v1/users/exists")
public Boolean existsEmail(@RequestBody UserEmailRequestDto requestDto) {
return userService.existsEmail(requestDto.getEmail(), requestDto.getUserId());
}
/**
* 사용자 회원 가입
*
* @param requestDto 사용자 가입 요청 DTO
* @return Boolean 성공 여부
*/
@PostMapping("/api/v1/users/join")
public Boolean join(@RequestBody @Valid UserJoinRequestDto requestDto) {
return userService.join(requestDto);
}
/**
* 사용자 비밀번호 찾기
*
* @param requestDto 사용자 비밀번호 찾기 등록 요청 DTO
* @return Boolean 메일 전송 여부
*/
@PostMapping("/api/v1/users/password/find")
public Boolean findPassword(@RequestBody @Valid UserFindPasswordSaveRequestDto requestDto) {
return userService.findPassword(requestDto);
}
/**
* 사용자 비밀번호 찾기 유효성 확인
*
* @param token 토큰
* @return Boolean 유효 여부
*/
@GetMapping("/api/v1/users/password/valid/{token}")
public Boolean validPassword(@PathVariable String token) {
return userService.validPassword(token);
}
/**
* 사용자 비밀번호 찾기 변경
*
* @param requestDto 사용자 비밀번호 수정 요청 DTO
* @return Boolean 수정 여부
*/
@PutMapping("/api/v1/users/password/change")
public Boolean changePassword(@RequestBody @Valid UserFindPasswordUpdateRequestDto requestDto) {
return userService.changePassword(requestDto);
}
/**
* 사용자 비밀번호 변경
*
* @param requestDto 사용자 비밀번호 수정 요청 DTO
* @return Boolean 수정 여부
*/
@PutMapping("/api/v1/users/password/update")
public Boolean updatePassword(@RequestBody @Valid UserPasswordUpdateRequestDto requestDto) {
final String userId = SecurityContextHolder.getContext().getAuthentication().getName();
return userService.updatePassword(userId, requestDto);
}
/**
* 사용자 비밀번호 확인
*
* @param requestDto 사용자 비밀번호 확인 요청 DTO
* @return Boolean 일치 여부
*/
@PostMapping("/api/v1/users/password/match")
public Boolean matchPassword(@RequestBody @Valid UserPasswordMatchRequestDto requestDto) {
final String userId = SecurityContextHolder.getContext().getAuthentication().getName();
final String password = requestDto.getPassword();
return userService.matchPassword(userId, password);
}
/**
* 사용자 회원정보 변경
*
* @param userId 사용자 id
* @param requestDto 사용자 수정 요청 DTO
* @return String 사용자 id
*/
@PutMapping("/api/v1/users/info/{userId}")
public String updateInfo(@PathVariable String userId, @RequestBody @Valid UserUpdateInfoRequestDto requestDto) {
final String authUserId = SecurityContextHolder.getContext().getAuthentication().getName();
if (!authUserId.equals(userId)) {
throw new BusinessMessageException(messageUtil.getMessage("err.access.denied"));
}
return userService.updateInfo(userId, requestDto);
}
/**
* 사용자 회원탈퇴
*
* @param requestDto 사용자 비밀번호 확인 요청 DTO
* @return Boolean 일치 여부
* @throws GeneralSecurityException 보안 예외
* @throws IOException 입출력 예외
*/
@PostMapping("/api/v1/users/leave")
public Boolean leave(@RequestBody @Valid UserVerifyRequestDto requestDto) throws GeneralSecurityException, IOException {
final String userId = SecurityContextHolder.getContext().getAuthentication().getName();
return userService.leave(userId, requestDto);
}
/**
* 사용자 회원탈퇴
*
* @param userId 사용자 비밀번호 확인 요청 DTO
* @return Boolean 일치 여부
*/
@DeleteMapping("/api/v1/users/delete/{userId}")
public Boolean delete(@PathVariable String userId) {
return userService.delete(userId);
}
}

View File

@@ -0,0 +1,42 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserEmailRequestDto
*
* 사용자 이메일 확인 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/16
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/16 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserEmailRequestDto {
/**
* 사용자 id
*/
private String userId;
/**
* 이메일
*/
@NotBlank(message = "{user.email}{valid.required}")
@Email
private String email;
}

View File

@@ -0,0 +1,69 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.egovframe.cloud.userservice.domain.user.UserFindPassword;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserFindPasswordSaveRequestDto
*
* 사용자 비밀번호 찾기 등록 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserFindPasswordSaveRequestDto {
/**
* 사용자 명
*/
@NotBlank(message = "{user.user_name}{valid.required}")
private String userName;
/**
* 이메일 주소
*/
@NotBlank(message = "{user.email}{valid.required}")
@Email
private String emailAddr;
/**
* 메인 주소
*/
@NotBlank
private String mainUrl;
/**
* 비밀번호 변경 주소
*/
@NotBlank
private String changePasswordUrl;
/**
* 사용자 비밀번호 찾기 등록 요청 DTO 속성 값으로 개인정보처리방침 엔티티 빌더를 사용하여 객체 생성
*
* @return UserFindPassword 사용자 비밀번호 찾기 엔티티
*/
public UserFindPassword toEntity(Integer requestNo, String tokenValue) {
return UserFindPassword.builder()
.emailAddr(emailAddr)
.requestNo(requestNo)
.tokenValue(tokenValue)
.build();
}
}

View File

@@ -0,0 +1,43 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserFindPasswordUpdateRequestDto
*
* 사용자 비밀번호 찾기 수정 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserFindPasswordUpdateRequestDto {
/**
* 토큰 값
*/
@NotBlank(message = "{common.token}{valid.required}")
private String tokenValue;
/**
* 비밀번호
*/
@NotBlank(message = "{user.password}{valid.required}")
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", message = "{valid.password}") // (숫자)(영문)(특수문자)(공백제거)(자리수)
private String password;
}

View File

@@ -0,0 +1,43 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserInfoUpdateRequestDto
*
* 사용자 정보 수정 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/16
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/16 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserInfoUpdateRequestDto {
/**
* 이메일
*/
@NotBlank(message = "{user.email}{valid.required}")
@Email
private String email;
/**
* 사용자 명
*/
@NotBlank(message = "{user.user_name}{valid.required}")
private String userName;
}

View File

@@ -0,0 +1,65 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.egovframe.cloud.common.domain.Role;
import org.egovframe.cloud.userservice.domain.user.User;
import org.egovframe.cloud.userservice.domain.user.UserStateCode;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.util.UUID;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserJoinRequestDto
*
* 사용자 가입 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/23
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/23 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserJoinRequestDto {
@NotBlank(message = "{user.user_name}{valid.required}")
private String userName;
@NotBlank(message = "{user.email}{valid.required}")
@Email
private String email;
// (숫자)(영문)(특수문자)(공백제거)(자리수)
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}",
message = "{valid.password}")
private String password;
/**
* UserSaveRequestDto 의 필드 값을 User Entity 빌더를 사용하여 주입 후 User를 리턴한다.
* UserSaveRequestDto 가 가지고 있는 User 의 필드만 세팅할 수 있게 된다.
*
* @param passwordEncoder
* @return
*/
public User toEntity(BCryptPasswordEncoder passwordEncoder) {
return User.builder()
.userName(userName)
.email(email)
.encryptedPassword(passwordEncoder.encode(password)) // 패스워드 인코딩
.userId(UUID.randomUUID().toString()) // 사용자 아이디 랜덤하게 생성
.role(Role.USER) // 가입 시 기본 권한
.userStateCode(UserStateCode.NORMAL.getKey()) // 승인 절차 없이 정상 상태로 가입
.build();
}
}

View File

@@ -0,0 +1,77 @@
package org.egovframe.cloud.userservice.api.user.dto;
import com.querydsl.core.annotations.QueryProjection;
import lombok.Getter;
import org.egovframe.cloud.common.domain.Role;
import org.egovframe.cloud.userservice.domain.user.User;
import org.egovframe.cloud.userservice.domain.user.UserStateCode;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserListResponseDto
* <p>
* 사용자 목록 요청시 사용되는 필요한 정보만 담긴 DTO
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@Getter
public class UserListResponseDto {
private String userId;
private String userName;
private String email;
private String roleId;
private String roleName;
private String userStateCode;
private String userStateCodeName;
private LocalDateTime lastLoginDate;
private Integer loginFailCount;
/**
* UserListResponseDto 는 Entity의 필드 중 일부만 사용하므로 생성자로 Entity를 받아 필드에 값을 넣는다.
* 굳이 모든 필드를 가진 생성자가 필요하지 않다.
*
* @param entity
*/
public UserListResponseDto(User entity) {
this.userId = entity.getUserId();
this.userName = entity.getUserName();
this.email = entity.getEmail();
}
/**
* 사용자 목록 응답 DTO 생성자
*
* @param userId 사용자 id
* @param userName 사용자 명
* @param email 이메일 주소
* @param role 권한
* @param userStateCode 사용자 상태 코드
* @param lastLoginDate 마지막 로그인 일시
* @param loginFailCount 로그인 실패 수
*/
@QueryProjection
public UserListResponseDto(String userId, String userName, String email, Role role, String userStateCode, LocalDateTime lastLoginDate, Integer loginFailCount) {
this.userId = userId;
this.userName = userName;
this.email = email;
this.roleId = role.getKey();
this.roleName = role.getTitle();
UserStateCode usc = UserStateCode.findByKey(userStateCode);
this.userStateCode = usc.getKey();
this.userStateCodeName = usc.getTitle();
this.lastLoginDate = lastLoginDate;
this.loginFailCount = loginFailCount;
}
}

View File

@@ -0,0 +1,91 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserLoginRequestDto
* <p>
* 로그인 요청시 사용되는 필요한 정보만 담긴 DTO
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserLoginRequestDto {
/**
* 이메일
*/
@Email
private String email;
/**
* 비밀번호
*/
// (숫자)(영문)(특수문자)(공백제거)(자리수)
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}",
message = "{valid.password}")
private String password;
/**
* 공급자
*/
@NotBlank(message = "{common.provider}{valid.required}")
private String provider;
/**
* 토큰
*/
private String token;
/**
* 이름
*/
private String name;
/**
* 사용자 로그인 요청 DTO 클래스 생성자
* 빌더 패턴으로 객체 생성
*
* @param email 이메일
* @param password 비밀번호
* @param provider 공급자
* @param token 토큰
*/
@Builder
public UserLoginRequestDto(String email, String password, String provider, String token, String name) {
this.email = email;
this.password = password;
this.provider = provider;
this.token = token;
this.name = name;
}
/**
* OAuth 로그인 정보 세팅
*
* @param email
* @param password
*/
public void setOAuthLoginInfo(String email, String password) {
this.email = email;
this.password = password;
}
}

View File

@@ -0,0 +1,37 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserPasswordMatchRequestDto
*
* 사용자 비밀번호 확인 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/16
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/16 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserPasswordMatchRequestDto {
/**
* 비밀번호
*/
@NotBlank(message = "{label.title.current_password}{valid.required}")
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", message = "{valid.password}") // (숫자)(영문)(특수문자)(공백제거)(자리수)
private String password;
}

View File

@@ -0,0 +1,36 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Pattern;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserPasswordUpdateRequestDto
* <p>
* 사용자 비밀번호 변경 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/16
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/16 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserPasswordUpdateRequestDto extends UserVerifyRequestDto {
/**
* 신규 비밀번호
*/
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", message = "{valid.password}")
// (숫자)(영문)(특수문자)(공백제거)(자리수)
private String newPassword;
}

View File

@@ -0,0 +1,55 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Getter;
import org.egovframe.cloud.userservice.domain.user.User;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserResponseDto
* <p>
* 사용자 정보 요청시 사용되는 필요한 정보만 담긴 DTO
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@Getter
public class UserResponseDto {
private String userId;
private String userName;
private String email;
private String roleId;
private String userStateCode;
private String googleId;
private String kakaoId;
private String naverId;
private Boolean isSocialUser;
private Boolean hasPassword;
/**
* UserResponseDto는 Entity의 필드 중 일부만 사용하므로 생성자로 Entity를 받아 필드에 값을 넣는다.
* 굳이 모든 필드를 가진 생성자가 필요하지 않다.
*
* @param entity
*/
public UserResponseDto(User entity) {
this.userId = entity.getUserId();
this.userName = entity.getUserName();
this.email = entity.getEmail();
this.roleId = entity.getRoleKey();
this.userStateCode = entity.getUserStateCode();
this.googleId = entity.getGoogleId();
this.kakaoId = entity.getKakaoId();
this.naverId = entity.getNaverId();
this.isSocialUser = entity.isSocialUser();
this.hasPassword = entity.getEncryptedPassword() != null && !"".equals(entity.getEncryptedPassword());
}
}

View File

@@ -0,0 +1,83 @@
package org.egovframe.cloud.userservice.api.user.dto;
import java.util.Arrays;
import java.util.UUID;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import org.egovframe.cloud.common.domain.Role;
import org.egovframe.cloud.userservice.domain.user.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserSaveRequestDto
* <p>
* 사용자 정보 생성 요청시 처리 가능한 정보만 담긴 DTO
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserSaveRequestDto {
@NotBlank(message = "{user.user_name}{valid.required}")
private String userName;
@NotBlank(message = "{user.email}{valid.required}")
@Email
private String email;
// (숫자)(영문)(특수문자)(공백제거)(자리수)
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}",
message = "{valid.password}")
private String password;
@NotBlank(message = "{role}{valid.required}")
private String roleId;
@NotBlank(message = "{user.user_state_code}{valid.required}")
private String userStateCode;
@Builder
public UserSaveRequestDto(String userName, String email, String password, String roleId, String userStateCode) {
this.userName = userName;
this.email = email;
this.password = password;
this.roleId = roleId;
this.userStateCode = userStateCode;
}
/**
* UserSaveRequestDto 의 필드 값을 User Entity 빌더를 사용하여 주입 후 User를 리턴한다.
* UserSaveRequestDto 가 가지고 있는 User 의 필드만 세팅할 수 있게 된다.
*
* @param passwordEncoder
* @return
*/
public User toEntity(BCryptPasswordEncoder passwordEncoder) {
return User.builder()
.userName(userName)
.email(email)
.encryptedPassword(passwordEncoder.encode(password)) // 패스워드 인코딩
.userId(UUID.randomUUID().toString()) // 사용자 아이디 랜덤하게 생성
.role(Arrays.stream(Role.values()).filter(c -> c.getKey().equals(roleId)).findAny().orElse(null))
.userStateCode(userStateCode)
.build();
}
}

View File

@@ -0,0 +1,43 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserUpdateInfoRequestDto
* <p>
* 사용자 정보 수정 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/23
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/23 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserUpdateInfoRequestDto extends UserVerifyRequestDto {
@NotBlank(message = "{user.user_name}{valid.required}")
private String userName;
@NotBlank(message = "{user.email}{valid.required}")
@Email
private String email;
@Builder
public UserUpdateInfoRequestDto(String userName, String email) {
this.userName = userName;
this.email = email;
}
}

View File

@@ -0,0 +1,59 @@
package org.egovframe.cloud.userservice.api.user.dto;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserUpdateRequestDto
* <p>
* 사용자 정보 수정 요청시 처리 가능한 정보만 담긴 DTO
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserUpdateRequestDto {
@NotBlank(message = "{user.user_name}{valid.required}")
private String userName;
@NotBlank(message = "{user.email}{valid.required}")
@Email
private String email;
// (숫자)(영문)(특수문자)(공백제거)(자리수)
@Pattern(regexp = "((?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20})|()",
message = "{valid.password}")
private String password;
@NotBlank(message = "{role}{valid.required}")
private String roleId;
@NotBlank(message = "{user.user_state_code}{valid.required}")
private String userStateCode;
@Builder
public UserUpdateRequestDto(String userName, String email, String password, String roleId, String userStateCode) {
this.userName = userName;
this.email = email;
this.password = password;
this.roleId = roleId;
this.userStateCode = userStateCode;
}
}

View File

@@ -0,0 +1,47 @@
package org.egovframe.cloud.userservice.api.user.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
/**
* org.egovframe.cloud.userservice.api.user.dto.UserVerifyRequestDto
*
* 사용자 탈퇴 요청 DTO 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/16
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/16 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
public class UserVerifyRequestDto {
/**
* 비밀번호
*/
@Pattern(regexp = "((?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20})|()", message = "{valid.password}") // (숫자)(영문)(특수문자)(공백제거)(자리수)
private String password;
/**
* 공급자
*/
@NotBlank(message = "{common.provider}{valid.required}")
private String provider;
/**
* 토큰
*/
private String token;
}

View File

@@ -0,0 +1,208 @@
package org.egovframe.cloud.userservice.config;
import static org.egovframe.cloud.common.config.GlobalConstant.LOGIN_URI;
import static org.springframework.util.StringUtils.hasLength;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.egovframe.cloud.common.util.LogUtil;
import org.egovframe.cloud.userservice.api.user.dto.UserLoginRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserResponseDto;
import org.egovframe.cloud.userservice.service.user.UserService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.transaction.annotation.Transactional;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
/**
* org.egovframe.cloud.userservice.config.AuthenticationFilter
* <p>
* Spring Security AuthenticationFilter 처리
* 로그인 인증정보를 받아 토큰을 발급한다
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final TokenProvider tokenProvider;
private final UserService userService;
public AuthenticationFilter(AuthenticationManager authenticationManager, TokenProvider tokenProvider, UserService userService) {
super.setAuthenticationManager(authenticationManager);
this.tokenProvider = tokenProvider;
this.userService = userService;
}
/**
* 로그인 요청 시 호출되는 메소드이다.
* 계정 정보를 받아 인증정보를 생성한다.
*
* @param request http 요청
* @param response http 응답
* @return Authentication 인증정보
* @throws NullPointerException 널 포인터 예외
* @throws Exception 예외
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
// 사용자가 입력한 인증정보 받기, POST method 값이기 때문에 input stream으로 받았다.
UserLoginRequestDto creds = new ObjectMapper().readValue(request.getInputStream(), UserLoginRequestDto.class);
UsernamePasswordAuthenticationToken upat = null;
if (creds.getProvider() != null && !"email".equals(creds.getProvider())) {
UserResponseDto userDto = userService.loadUserBySocial(creds);
upat = new UsernamePasswordAuthenticationToken(
userDto.getEmail(),
null,
AuthorityUtils.createAuthorityList(userDto.getRoleId())
);
SecurityContextHolder.getContext().setAuthentication(upat);
return upat;
} else {
upat = new UsernamePasswordAuthenticationToken(
creds.getEmail(),
creds.getPassword(),
new ArrayList<>()
);
// 인증정보 만들기
return getAuthenticationManager().authenticate(upat);
}
} catch (NullPointerException e) {
log.error(e.getLocalizedMessage());
throw new RuntimeException(e);
} catch (Exception e) {
log.error(e.getLocalizedMessage());
throw new RuntimeException(e);
}
}
/**
* 로그인 인증 성공 후 호출된다.
* 토큰을 생성하여 헤더에 토큰 정보를 담는다.
*
* @param request
* @param response
* @param chain
* @param authResult
* @throws IOException
* @throws ServletException
*/
@Transactional
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// 토큰 생성 및 response header add
tokenProvider.createTokenAndAddHeader(request, response, chain, authResult);
// 로그인 성공 후처리
userService.loginCallback(LogUtil.getSiteId(request), authResult.getName(), true, "");
}
@Transactional
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
String failContent = failed.getMessage();
if (failed instanceof InternalAuthenticationServiceException) {
log.info("{} 해당 사용자가 없습니다", request.getAttribute("email"));
} else if (failed instanceof BadCredentialsException) {
failContent = "패스워드 인증에 실패하였습니다. " + failContent;
}
response.setStatus(HttpStatus.UNAUTHORIZED.value());
// 로그인 실패 후처리
String email = (String) request.getAttribute("email");
userService.loginCallback(LogUtil.getSiteId(request), email, false, failContent);
super.unsuccessfulAuthentication(request, response, failed);
}
/**
* 로그인 요청 뿐만 아니라 모든 요청시마다 호출된다.
* 토큰에 담긴 정보로 Authentication 정보를 설정한다.
* 이 처리를 하지 않으면 AnonymousAuthenticationToken 으로 처리된다.
*
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String token = httpRequest.getHeader(HttpHeaders.AUTHORIZATION);
if (!hasLength(token) || "undefined".equals(token)) {
super.doFilter(request, response, chain);
} else {
try {
final String requestURI = httpRequest.getRequestURI();
log.info("httpRequest.getRequestURI() ={}", requestURI);
if (LOGIN_URI.equals(requestURI)) {
// 로그인 등 토큰 정보를 꺼낼 필요가 없는 경우
SecurityContextHolder.getContext().setAuthentication(null);
} else {
// 토큰 유효성 검사는 API Gateway ReactiveAuthorization 클래스에서 미리 처리된다.
Claims claims = tokenProvider.getClaimsFromToken(token);
String username = claims.getSubject();
if (username == null) {
// refresh token 에는 subject, authorities 정보가 없다.
SecurityContextHolder.getContext().setAuthentication(null);
} else {
List<SimpleGrantedAuthority> roleList = Arrays.stream(claims.get(tokenProvider.TOKEN_CLAIM_NAME, String.class).split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(username, null, roleList));
}
}
chain.doFilter(request, response);
} catch (Exception e) {
SecurityContextHolder.getContext().setAuthentication(null);
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
log.error("AuthenticationFilter doFilter", e);
}
}
}
}

View File

@@ -0,0 +1,28 @@
package org.egovframe.cloud.userservice.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* org.egovframe.cloud.userservice.config.CacheConfig
*
* 캐시 설정 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/21
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/21 jooho 최초 생성
* </pre>
*/
@Configuration
@EnableCaching
@EnableAspectJAutoProxy(exposeProxy=true) // AopContext.currentProxy() 사용 옵션
public class CacheConfig {
}

View File

@@ -0,0 +1,36 @@
package org.egovframe.cloud.userservice.config;
import lombok.extern.slf4j.Slf4j;
import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
/**
* org.egovframe.cloud.userservice.config.CacheEventLogger
*
* 캐시 이밴트 로깅 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/22
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/22 jooho 최초 생성
* </pre>
*/
@Slf4j
public class CacheEventLogger implements CacheEventListener<Object, Object> {
/**
* 캐시 이벤트 발생시 로깅
*
* @param cacheEvent 캐시 이벤트
*/
public void onEvent(CacheEvent<? extends Object, ? extends Object> cacheEvent) {
log.info("cache event ::: key: {} / oldvalue: {} / newvalue:{}", cacheEvent.getKey(), cacheEvent.getOldValue(), cacheEvent.getNewValue());
}
}

View File

@@ -0,0 +1,51 @@
package org.egovframe.cloud.userservice.config;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* org.egovframe.cloud.userservice.config.CustomOAuth2SuccessHandler
* <p>
* OAuth2 인증 성공 시 호출된다.
* 로그인 인증정보를 받아 토큰을 발급한다
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/07/01
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/01 jaeyeolkim 최초 생성
* </pre>
*/
@RequiredArgsConstructor
@Component
public class CustomOAuth2SuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private final TokenProvider tokenProvider;
/**
* 인증 시 토큰 생성
* @param request
* @param response
* @param chain
* @param authentication
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
tokenProvider.createTokenAndAddHeader(request, response, chain, authentication);
}
}

View File

@@ -0,0 +1,85 @@
package org.egovframe.cloud.userservice.config;
import lombok.RequiredArgsConstructor;
import org.egovframe.cloud.userservice.config.dto.OAuthAttributes;
import org.egovframe.cloud.userservice.domain.user.User;
import org.egovframe.cloud.userservice.domain.user.UserRepository;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Collections;
/**
* org.egovframe.cloud.userservice.config.CustomOAuth2UserService
* <p>
* OAuth2 로그인 이후 가져온 사용자의 정보들을 기반으로 가입 및 정보수정, 세션 저장 등의 기능을 지원
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
// private final HttpSession httpSession;
/**
* OAuth2 로그인 이후 호출되어 사용자 정보를 DB에 입력한다.
*
* @param userRequest
* @return
* @throws OAuth2AuthenticationException
*/
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
// 현재 로그인 진행 중인 서비스를 구분하는 코드(구글 or 네이버..)
String registrationId = userRequest.getClientRegistration().getRegistrationId();
// OAuth2 로그인 진행시 키가 되는 필드 값(PK). 구글만 기본적으로 기본 코드(sub)를 지원.
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
// OAuth2UserService를 통해 가져온 OAuth2User의 attributes를 담을 클래스
OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());
User user = saveOrUpdate(attributes);
// 세션에 저장하는 경우 활성화한다
// httpSession.setAttribute("user", new SessionUser(user));
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
attributes.getAttributes(),
attributes.getNameAttributeKey()
);
}
/**
* OAuth2 사용자 정보가 변경는 경우 반영
*
* @param attributes
* @return
*/
private User saveOrUpdate(OAuthAttributes attributes) {
User user = userRepository.findByEmail(attributes.getEmail())
.map(entity -> entity.updateInfo(attributes.getUserName(), attributes.getEmail()))
.orElse(attributes.toEntity());
return userRepository.save(user);
}
}

View File

@@ -0,0 +1,37 @@
package org.egovframe.cloud.userservice.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* org.egovframe.cloud.userservice.config.RestTemplateConfig
*
* REST Template 설정 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/27
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/27 jooho 최초 생성
* </pre>
*/
@Configuration
public class RestTemplateConfig {
/**
* REST Template 빈 등록
*
* @return RestTemplate REST Template
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@@ -0,0 +1,87 @@
package org.egovframe.cloud.userservice.config;
import lombok.RequiredArgsConstructor;
import org.egovframe.cloud.userservice.service.user.UserService;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import static org.egovframe.cloud.common.config.GlobalConstant.SECURITY_PERMITALL_ANTPATTERNS;
/**
* org.egovframe.cloud.userservice.SecurityConfig
* <p>
* Spring Security Config 클래스
* AuthenticationFilter 를 추가하고 로그인 인증처리를 한다
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@RequiredArgsConstructor
@EnableWebSecurity // Spring Security 설정들을 활성화시켜 준다
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
/**
* 스프링 시큐리티 설정
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.headers().frameOptions().disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 토큰 사용하기 때문에 세션은 비활성화
.and()
.authorizeRequests()
.antMatchers(SECURITY_PERMITALL_ANTPATTERNS).permitAll()
.anyRequest().access("@authorizationService.isAuthorization(request, authentication)") // 호출 시 권한 인가 데이터 확인
.and()
.addFilter(getAuthenticationFilter())
.logout()
.logoutSuccessUrl("/");
}
/**
* 로그인 인증정보를 받아 토큰을 발급할 수 있도록 필터를 등록해준다.
*
* @return
* @throws Exception
*/
private AuthenticationFilter getAuthenticationFilter() throws Exception {
return new AuthenticationFilter(authenticationManager(), tokenProvider, userService);
}
/**
* 인증 관련 - 로그인 처리
* DB 에서 조회하여 일치하는지 체크한다.
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// userService.loadUserByUsername 메소드
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}
}

View File

@@ -0,0 +1,153 @@
package org.egovframe.cloud.userservice.config;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.egovframe.cloud.userservice.api.user.dto.UserResponseDto;
import org.egovframe.cloud.userservice.service.user.UserService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.stream.Collectors;
/**
* org.egovframe.cloud.userservice.config.TokenProvider
* <p>
* 로그인 성공 인증정보로 토큰을 생성한다.
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/07/01
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/01 jaeyeolkim 최초 생성
* </pre>
*/
@Component
public class TokenProvider {
private final UserService userService;
public TokenProvider(UserService userService) {
this.userService = userService;
}
@Value("${token.secret}")
private String TOKEN_SECRET;
@Value("${token.expiration_time}")
private String TOKEN_EXPIRATION_TIME;
@Value("${token.refresh_time}")
private String TOKEN_REFRESH_TIME;
final String TOKEN_CLAIM_NAME = "authorities";
final String TOKEN_ACCESS_KEY = "access-token";
final String TOKEN_REFRESH_KEY = "refresh-token";
final String TOKEN_USER_ID = "token-id";
/**
* 로그인 후 토큰을 생성하고 헤더에 정보를 담는다.
*
* @param request
* @param response
* @param chain
* @param authResult
*/
public void createTokenAndAddHeader(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
// 로그인 성공 후 토큰 처리
String email = authResult.getName();
String authorities = authResult.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
// userid 가져오기
UserResponseDto userResponseDto = userService.findByEmail(email);
String userId = userResponseDto.getUserId();
// JWT Access 토큰 생성
String accessToken = createAccessToken(authorities, userId);
// JWT Refresh 토큰 생성 후 사용자 도메인에 저장하여 토큰 재생성 요청시 활용한다.
String refreshToken = createRefreshToken();
userService.updateRefreshToken(userId, refreshToken);
// Header에 토큰 세팅
response.addHeader(TOKEN_ACCESS_KEY, accessToken);
response.addHeader(TOKEN_REFRESH_KEY, refreshToken);
response.addHeader(TOKEN_USER_ID, userId);
}
/**
* JWT Access Token 생성
*
* @param authorities
* @param userId
* @return
*/
private String createAccessToken(String authorities, String userId) {
return Jwts.builder()
.setSubject(userId)
.claim(TOKEN_CLAIM_NAME, authorities)
.setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(TOKEN_EXPIRATION_TIME)))
.signWith(SignatureAlgorithm.HS512, TOKEN_SECRET)
.compact();
}
/**
* JWT Refresh Token 생성
* 중복 로그인을 허용하려면 user domain 에 있는 refresh token 값을 반환하고 없는 경우에만 생성하도록 처리한다.
*
* @return
*/
private String createRefreshToken() {
return Jwts.builder()
.setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(TOKEN_REFRESH_TIME)))
.signWith(SignatureAlgorithm.HS512, TOKEN_SECRET)
.compact();
}
/**
* 사용자가 있으면 access token 을 새로 발급하여 리턴한다.
*
* @param refreshToken
* @param response
* @return
*/
public String refreshToken(String refreshToken, HttpServletResponse response) {
// refresh token 으로 유효한 사용자가 있는지 찾는다.
org.egovframe.cloud.userservice.domain.user.User user = userService.findByRefreshToken(refreshToken);
// 사용자가 있으면 access token 을 새로 발급하여 리턴한다.
String accessToken = createAccessToken(user.getRoleKey(), user.getUserId());
// Header에 토큰 세팅
response.addHeader(TOKEN_ACCESS_KEY, accessToken);
response.addHeader(TOKEN_REFRESH_KEY, refreshToken);
response.addHeader(TOKEN_USER_ID, user.getUserId());
return accessToken;
}
/**
* AuthenticationFilter.doFilter 메소드에서 UsernamePasswordAuthenticationToken 정보를 세팅할 때 호출된다.
*
* @param token
* @return
*/
public Claims getClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(TOKEN_SECRET)
.parseClaimsJws(token)
.getBody();
}
}

View File

@@ -0,0 +1,73 @@
package org.egovframe.cloud.userservice.config;
public class UserPasswordChangeEmailTemplate {
/**
* 객체 생성 금지
*/
private UserPasswordChangeEmailTemplate() {
throw new IllegalStateException("user password change email template class");
}
public static final String html = "<style type=\"text/css\">\n"
+ " @import url(http://fonts.googleapis.com/earlyaccess/notosanskr.css);\n"
+ " body {font-family:\"Noto Sans KR\", \"맑은 고딕\", sans-serif; font-weight:400; font-size:14px; color:#333; line-height:18px; -webkit-text-size-adjust:100%%; -moz-text-size-adjust:100%%; -ms-text-size-adjust:100%%;}\n"
+ "</style>\n"
+ "<div style=\"width:100%%; max-width:800px; margin:0 auto;\">\n"
+ " <a href=\"%s\" target=\"_blank\" style=\"display:block; margin:35px;\"><div><img src=\"https://user-images.githubusercontent.com/80671095/130181337-16d978c6-71c4-4759-82b3-9a542f4c1aad.png\" alt=\"표준프레임워크 포털로고\" style=\"width:170px;\"></div></a>\n"
+ " <table width=\"100%%\" align=\"center\" cellspacing=\"0\" border=\"0\" bgcolor=\"#fff\" style=\"margin:0 auto; border:35px solid #eee;\">\n"
+ " <colgroup>\n"
+ " <col width=\"165\">\n"
+ " <col width=\"400\">\n"
+ " <col width=\"165\">\n"
+ " </colgroup>\n"
+ " <thead>\n"
+ " <tr>\n"
+ " <th></th>\n"
+ " <th height=\"100\"></th>\n"
+ " <th></th>\n"
+ " </tr>\n"
+ " <tr>\n"
+ " <th></th>\n"
+ " <th align=\"center\" style=\"font-size:40px; font-weight:600;\">비밀번호 초기화 안내</th>\n"
+ " <th></th>\n"
+ " </tr>\n"
+ " <tr>\n"
+ " <th></th>\n"
+ " <th height=\"40\" style=\"border-bottom:1px solid #e8e8e8\"></th>\n"
+ " <th></th>\n"
+ " </tr>\n"
+ " </thead>\n"
+ " <tbody>\n"
+ " <tr>\n"
+ " <td colspan=\"3\" height=\"60\"></td>\n"
+ " </tr>\n"
+ " <tr>\n"
+ " <td colspan=\"3\" style=\"padding-left:45px; color:#666; font-size:24px; line-height:36px;\">\n"
+ " 안녕하세요. <b style=\"color:#333;\">%s</b> 회원님.<br /><br />\n"
+ " 비밀번호 초기화 관련하여 안내드립니다.<br />\n"
+ " 회원님의 계정 비밀번호를 초기화할 수 있는 URL을 알려드립니다.<br /><br />\n"
+ " <span style=\"color:#1e75d6; font-weight:600;\">[비밀번호 초기화] 버튼</span>으로 접속하여 비밀번호를 초기화 하신 후<br />서비스를 계속해서 이용해주시기 바랍니다.<br /><br />\n"
+ " 해당 링크는 발송후 1시간 동안만 유효합니다.<br /><br />\n"
+ " 감사합니다.\n"
+ " </td>\n"
+ " </tr>\n"
+ " <tr>\n"
+ " <td colspan=\"3\" height=\"60\"></td>\n"
+ " </tr>\n"
+ " <tr>\n"
+ " <td colspan=\"3\" align=\"center\">\n"
+ " <a href=\"%s\" target=\"_blank\" style=\"height:60px; padding:10px 50px; color:#fff; font-size:20px; font-weight:600; text-align:center; line-height:58px; border:0; background:#1e75d6; border-radius:5px; text-decoration: none;\">비밀번호 초기화</a>\n"
+ " </td>\n"
+ " </tr>\n"
+ " <tr>\n"
+ " <td colspan=\"3\" height=\"100\"></td>\n"
+ " </tr>\n"
+ " </tbody>\n"
+ " </table>\n"
+ " <div style=\"padding:25px 0; color:#999; font-size:15px; text-align:center; line-height:28px;\">\n"
+ " (C) 표준프레임워크 포털 All Rights Reserved.\n"
+ " </div>\n"
+ "</div>\n";
}

View File

@@ -0,0 +1,66 @@
package org.egovframe.cloud.userservice.config.dto;
import lombok.Builder;
import lombok.Getter;
import org.egovframe.cloud.common.domain.Role;
import org.egovframe.cloud.userservice.domain.user.User;
import java.util.HashMap;
import java.util.Map;
@Getter
public class OAuthAttributes {
private Map<String, Object> attributes;
private String nameAttributeKey;
private String userName;
private String email;
@Builder
public OAuthAttributes(Map<String, Object> attributes,
String nameAttributeKey, String userName, String email) {
// public으로 선언된 데이터가 private 선언된 배열에 저장되지 않도록 한다.(reference가 아닌, “값”을 할당함으로써 private 멤버로서의 접근권한을 유지 시켜준다.)
this.attributes = new HashMap<>();
attributes.forEach((k, v) -> this.attributes.put(k, attributes.get(k)));
this.nameAttributeKey = nameAttributeKey;
this.userName = userName;
this.email = email;
}
// OAuth2User에서 반환하는 사용자 정보는 Map이기 때문에 값 하나하나를 변환해야만 한다.
public static OAuthAttributes of(String registrationId, String userNameAttributeName, Map<String, Object> attributes) {
if ("naver".equals(registrationId)) {
return ofNaver("id", attributes);
}
return ofGoogle(userNameAttributeName, attributes);
}
private static OAuthAttributes ofGoogle(String userNameAttributeName, Map<String, Object> attributes) {
return OAuthAttributes.builder()
.userName((String) attributes.get("name"))
.email((String) attributes.get("email"))
.attributes(attributes)
.nameAttributeKey(userNameAttributeName)
.build();
}
private static OAuthAttributes ofNaver(String userNameAttributeName, Map<String, Object> attributes) {
Map<String, Object> response = (Map<String, Object>) attributes.get("response");
return OAuthAttributes.builder()
.userName((String) response.get("name"))
.email((String) response.get("email"))
.attributes(response)
.nameAttributeKey(userNameAttributeName)
.build();
}
// User 엔티티 생성
// OAuthAttributes에서 엔티티를 생성하는 시점은 처음 가입할 때이다.
// 가입할 때의 기본 권한을 USER 변경함
public User toEntity() {
return User.builder()
.userName(userName)
.email(email)
.role(Role.ANONYMOUS)
.build();
}
}

View File

@@ -0,0 +1,37 @@
package org.egovframe.cloud.userservice.config.dto;
import lombok.Getter;
import org.egovframe.cloud.userservice.domain.user.User;
import java.io.Serializable;
/**
* org.egovframe.cloud.userservice.config.dto.SessionUser
* <p>
* session 에 담을 사용자 정보
* session 에 저장하기 위해 직렬화를 구현하였다
* 토큰 사용시에는 사용되지 않는다.
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/07/06
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/06 jaeyeolkim 최초 생성
* </pre>
*/
@Getter
public class SessionUser implements Serializable {
private String userName;
private String email;
// 인증된 사용자 정보만 필요하여 두 컬럼만 정의
public SessionUser(User user) {
this.userName = user.getUserName();
this.email = user.getEmail();
}
}

View File

@@ -0,0 +1,382 @@
package org.egovframe.cloud.userservice.config.dto;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.*;
/**
* Models core user information retrieved by a {@link org.springframework.security.core.userdetails.UserDetailsService}.
* <p>
* Developers may use this class directly, subclass it, or write their own
* {@link UserDetails} implementation from scratch.
* <p>
* {@code equals} and {@code hashcode} implementations are based on the {@code username}
* property only, as the intention is that lookups of the same user principal object (in a
* user registry, for example) will match where the objects represent the same user, not
* just when all the properties (authorities, password for example) are the same.
* <p>
* Note that this implementation is not immutable. It implements the
* {@code CredentialsContainer} interface, in order to allow the password to be erased
* after authentication. This may cause side-effects if you are storing instances
* in-memory and reusing them. If so, make sure you return a copy from your
* {@code UserDetailsService} each time it is invoked.
*
* @author Ben Alex
* @author Luke Taylor
*/
public class SocialUser implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
/**
* Calls the more complex constructor with all boolean arguments set to {@code true}.
*/
public SocialUser(String username, Collection<? extends GrantedAuthority> authorities) {
this(username, true, true, true, true, authorities);
}
/**
* Construct the <code>User</code> with the details required by
* {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider}.
* @param username the username presented to the
* <code>DaoAuthenticationProvider</code>
* @param enabled set to <code>true</code> if the user is enabled
* @param accountNonExpired set to <code>true</code> if the account has not expired
* @param credentialsNonExpired set to <code>true</code> if the credentials have not
* expired
* @param accountNonLocked set to <code>true</code> if the account is not locked
* @param authorities the authorities that should be granted to the caller if they
* presented the correct username and password and the user is enabled. Not null.
* @throws IllegalArgumentException if a <code>null</code> value was passed either as
* a parameter or as an element in the <code>GrantedAuthority</code> collection
*/
public SocialUser(String username, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
// Assert.isTrue(username != null && !"".equals(username) && password != null,
// "Cannot pass null or empty values to constructor");
this.username = username;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}
@Override
public Collection<GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
@Override
public boolean isAccountNonExpired() {
return this.accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return this.accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}
@Override
public void eraseCredentials() {
this.password = null;
}
private static SortedSet<GrantedAuthority> sortAuthorities(Collection<? extends GrantedAuthority> authorities) {
Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
// Ensure array iteration order is predictable (as per
// UserDetails.getAuthorities() contract and SEC-717)
SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(new SocialUser.AuthorityComparator());
for (GrantedAuthority grantedAuthority : authorities) {
Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
sortedAuthorities.add(grantedAuthority);
}
return sortedAuthorities;
}
/**
* Returns {@code true} if the supplied object is a {@code User} instance with the
* same {@code username} value.
* <p>
* In other words, the objects are equal if they have the same username, representing
* the same principal.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof SocialUser) {
return this.username.equals(((SocialUser) obj).username);
}
return false;
}
/**
* Returns the hashcode of the {@code username}.
*/
@Override
public int hashCode() {
return this.username.hashCode();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getName()).append(" [");
sb.append("Username=").append(this.username).append(", ");
sb.append("Password=[PROTECTED], ");
sb.append("Enabled=").append(this.enabled).append(", ");
sb.append("AccountNonExpired=").append(this.accountNonExpired).append(", ");
sb.append("credentialsNonExpired=").append(this.credentialsNonExpired).append(", ");
sb.append("AccountNonLocked=").append(this.accountNonLocked).append(", ");
sb.append("Granted Authorities=").append(this.authorities).append("]");
return sb.toString();
}
/**
* Creates a UserBuilder with a specified user name
* @param username the username to use
* @return the UserBuilder
*/
public static SocialUser.UserBuilder withUsername(String username) {
return builder().username(username);
}
/**
* Creates a UserBuilder
* @return the UserBuilder
*/
public static SocialUser.UserBuilder builder() {
return new SocialUser.UserBuilder();
}
public static SocialUser.UserBuilder withUserDetails(UserDetails userDetails) {
// @formatter:off
return withUsername(userDetails.getUsername())
.accountExpired(!userDetails.isAccountNonExpired())
.accountLocked(!userDetails.isAccountNonLocked())
.authorities(userDetails.getAuthorities())
.credentialsExpired(!userDetails.isCredentialsNonExpired())
.disabled(!userDetails.isEnabled());
// @formatter:on
}
private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
@Override
public int compare(GrantedAuthority g1, GrantedAuthority g2) {
// Neither should ever be null as each entry is checked before adding it to
// the set. If the authority is null, it is a custom authority and should
// precede others.
if (g2.getAuthority() == null) {
return -1;
}
if (g1.getAuthority() == null) {
return 1;
}
return g1.getAuthority().compareTo(g2.getAuthority());
}
}
/**
* Builds the user to be added. At minimum the username, password, and authorities
* should provided. The remaining attributes have reasonable defaults.
*/
public static final class UserBuilder {
private String username;
private List<GrantedAuthority> authorities;
private boolean accountExpired;
private boolean accountLocked;
private boolean credentialsExpired;
private boolean disabled;
/**
* Creates a new instance
*/
private UserBuilder() {
}
/**
* Populates the username. This attribute is required.
* @param username the username. Cannot be null.
* @return the {@link SocialUser.UserBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public SocialUser.UserBuilder username(String username) {
Assert.notNull(username, "username cannot be null");
this.username = username;
return this;
}
/**
* Populates the roles. This method is a shortcut for calling
* {@link #authorities(String...)}, but automatically prefixes each entry with
* "ROLE_". This means the following:
*
* <code>
* builder.roles("USER","ADMIN");
* </code>
*
* is equivalent to
*
* <code>
* builder.authorities("ROLE_USER","ROLE_ADMIN");
* </code>
*
* <p>
* This attribute is required, but can also be populated with
* {@link #authorities(String...)}.
* </p>
* @param roles the roles for this user (i.e. USER, ADMIN, etc). Cannot be null,
* contain null values or start with "ROLE_"
* @return the {@link SocialUser.UserBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public SocialUser.UserBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList<>(roles.length);
for (String role : roles) {
Assert.isTrue(!role.startsWith("ROLE_"),
() -> role + " cannot start with ROLE_ (it is automatically added)");
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
return authorities(authorities);
}
/**
* Populates the authorities. This attribute is required.
* @param authorities the authorities for this user. Cannot be null, or contain
* null values
* @return the {@link SocialUser.UserBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
* @see #roles(String...)
*/
public SocialUser.UserBuilder authorities(GrantedAuthority... authorities) {
return authorities(Arrays.asList(authorities));
}
/**
* Populates the authorities. This attribute is required.
* @param authorities the authorities for this user. Cannot be null, or contain
* null values
* @return the {@link SocialUser.UserBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
* @see #roles(String...)
*/
public SocialUser.UserBuilder authorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = new ArrayList<>(authorities);
return this;
}
/**
* Populates the authorities. This attribute is required.
* @param authorities the authorities for this user (i.e. ROLE_USER, ROLE_ADMIN,
* etc). Cannot be null, or contain null values
* @return the {@link SocialUser.UserBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
* @see #roles(String...)
*/
public SocialUser.UserBuilder authorities(String... authorities) {
return authorities(AuthorityUtils.createAuthorityList(authorities));
}
/**
* Defines if the account is expired or not. Default is false.
* @param accountExpired true if the account is expired, false otherwise
* @return the {@link SocialUser.UserBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public SocialUser.UserBuilder accountExpired(boolean accountExpired) {
this.accountExpired = accountExpired;
return this;
}
/**
* Defines if the account is locked or not. Default is false.
* @param accountLocked true if the account is locked, false otherwise
* @return the {@link SocialUser.UserBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public SocialUser.UserBuilder accountLocked(boolean accountLocked) {
this.accountLocked = accountLocked;
return this;
}
/**
* Defines if the credentials are expired or not. Default is false.
* @param credentialsExpired true if the credentials are expired, false otherwise
* @return the {@link SocialUser.UserBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public SocialUser.UserBuilder credentialsExpired(boolean credentialsExpired) {
this.credentialsExpired = credentialsExpired;
return this;
}
/**
* Defines if the account is disabled or not. Default is false.
* @param disabled true if the account is disabled, false otherwise
* @return the {@link SocialUser.UserBuilder} for method chaining (i.e. to populate
* additional attributes for this user)
*/
public SocialUser.UserBuilder disabled(boolean disabled) {
this.disabled = disabled;
return this;
}
public UserDetails build() {
return new SocialUser(this.username, !this.disabled, !this.accountExpired,
!this.credentialsExpired, !this.accountLocked, this.authorities);
}
}
}

View File

@@ -0,0 +1,63 @@
package org.egovframe.cloud.userservice.domain.log;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.egovframe.cloud.servlet.domain.BaseTimeEntity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import static javax.persistence.GenerationType.IDENTITY;
/**
* org.egovframe.cloud.userservice.domain.log.LoginLog
* <p>
* 로그인 로그 엔티티
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/09/01
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/01 jaeyeolkim 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@Entity
public class LoginLog extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "log_id")
private Long id;
private Long siteId;
@Column(name = "email_addr", length = 100)
private String email;
@Column(name = "ip_addr", length = 100)
private String remoteIp;
private Boolean successAt;
@Column(name = "fail_content", length = 200)
private String failContent;
@Builder
public LoginLog(Long siteId, String email, Boolean successAt, String remoteIp, String failContent) {
this.siteId = siteId;
this.email = email;
this.successAt = successAt;
this.remoteIp = remoteIp;
this.failContent = failContent;
}
}

View File

@@ -0,0 +1,23 @@
package org.egovframe.cloud.userservice.domain.log;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* org.egovframe.cloud.userservice.domain.log.LoginLogRepository
* <p>
* Spring Data JPA 에서 제공되는 JpaRepository 를 상속하는 인터페이스
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/09/01
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/01 jaeyeolkim 최초 생성
* </pre>
*/
public interface LoginLogRepository extends JpaRepository<LoginLog, Long> {
}

View File

@@ -0,0 +1,113 @@
package org.egovframe.cloud.userservice.domain.role;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.egovframe.cloud.servlet.domain.BaseEntity;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import javax.persistence.*;
import java.util.List;
/**
* org.egovframe.cloud.userservice.domain.role.Authorization
* <p>
* 인가 엔티티 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class)
public class Authorization extends BaseEntity {
/**
* 인가 번호
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer authorizationNo;
/**
* 인가 명
*/
@Column(nullable = false, length = 50)
private String authorizationName;
/**
* URL 패턴 값
*/
@Column(nullable = false, length = 200)
private String urlPatternValue;
/**
* Http Method 코드
*/
@Column(nullable = false, length = 20)
private String httpMethodCode;
/**
* 정렬 순서
*/
@Column()
private Integer sortSeq;
/**
* 권한 인가 엔티티
*/
@OneToMany(mappedBy = "authorization", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@OnDelete(action = OnDeleteAction.CASCADE)
private List<RoleAuthorization> roleAuthorizations;
/**
* 빌더 패턴 클래스 생성자
*
* @param authorizationNo 인가 번호
* @param authorizationName 인가 명
* @param urlPatternValue URL 패턴 값
* @param httpMethodCode Http Method 코드
* @param sortSeq 정렬 순서
*/
@Builder
public Authorization(Integer authorizationNo, String authorizationName, String urlPatternValue, String httpMethodCode, Integer sortSeq, List<RoleAuthorization> roleAuthorizations) {
this.authorizationNo = authorizationNo;
this.authorizationName = authorizationName;
this.urlPatternValue = urlPatternValue;
this.httpMethodCode = httpMethodCode;
this.sortSeq = sortSeq;
this.roleAuthorizations = roleAuthorizations;
}
/**
* 인가 속성 값 수정
*
* @param authorizationName 인가 명
* @param urlPatternValue URL 패턴 값
* @param httpMethodCode Http Method 코드
* @param sortSeq 정렬 순서
* @return Authorization 인가 엔티티
*/
public Authorization update(String authorizationName, String urlPatternValue, String httpMethodCode, Integer sortSeq) {
this.authorizationName = authorizationName;
this.urlPatternValue = urlPatternValue;
this.httpMethodCode = httpMethodCode;
this.sortSeq = sortSeq;
return this;
}
}

View File

@@ -0,0 +1,34 @@
package org.egovframe.cloud.userservice.domain.role;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
/**
* org.egovframe.cloud.userservice.domain.role.AuthorizationRepository
* <p>
* 인가 레파지토리 인터페이스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jooho 최초 생성
* </pre>
*/
public interface AuthorizationRepository extends JpaRepository<Authorization, Integer>, AuthorizationRepositoryCustom {
/**
* 정렬 순서로 인가 단건 조회
*
* @param sortSeq 정렬 순서
* @return Optional<Authorization> 인가 엔티티
*/
Optional<Authorization> findBySortSeq(Integer sortSeq);
}

View File

@@ -0,0 +1,71 @@
package org.egovframe.cloud.userservice.domain.role;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.userservice.api.role.dto.AuthorizationListResponseDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
/**
* org.egovframe.cloud.userservice.domain.role.AuthorizationRepositoryCustom
* <p>
* 인가 Querydsl 인터페이스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/15 jooho 최초 생성
* </pre>
*/
public interface AuthorizationRepositoryCustom {
/**
* 인가 페이지 목록 조회
*
* @param requestDto 인가 목록 요청 DTO
* @param pageable 페이지 정보
* @return Page<AuthorizationListResponseDto> 페이지 인가 목록 응답 DTO
*/
Page<AuthorizationListResponseDto> findPage(RequestDto requestDto, Pageable pageable);
/**
* 권한 목록의 인가 전체 목록 조회
*
* @param roles 권한 목록
* @return Page<AuthorizationListResponseDto> 페이지 인가 목록 응답 DTO
*/
List<AuthorizationListResponseDto> findByRoles(List<String> roles);
/**
* 사용자의 인가 목록 조회
*
* @param userId 사용자 id
* @return List<AuthorizationListResponseDto> 인가 목록 응답 DTO
*/
List<AuthorizationListResponseDto> findByUserId(String userId);
/**
* 인가 다음 정렬 순서 조회
*
* @return Integer 다음 정렬 순서
*/
Integer findNextSortSeq();
/**
* 인가 정렬 순서 수정
*
* @param startSortSeq 시작 정렬 순서
* @param endSortSeq 종료 정렬 순서
* @param increaseSortSeq 증가 정렬 순서
* @return Long 처리 건수
*/
Long updateSortSeq(Integer startSortSeq, Integer endSortSeq, int increaseSortSeq);
}

View File

@@ -0,0 +1,213 @@
package org.egovframe.cloud.userservice.domain.role;
import java.util.List;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.userservice.api.role.dto.AuthorizationListResponseDto;
import org.egovframe.cloud.userservice.domain.user.QUser;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import com.google.common.base.CaseFormat;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
/**
* org.egovframe.cloud.userservice.domain.role.AuthorizationRepositoryImpl
* <p>
* 인가 Querydsl 구현 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/15 jooho 최초 생성
* </pre>
*/
@RequiredArgsConstructor
public class AuthorizationRepositoryImpl implements AuthorizationRepositoryCustom {
/**
* DML 생성을위한 Querydsl 팩토리 클래스
*/
private final JPAQueryFactory jpaQueryFactory;
/**
* 인가 페이지 목록 조회
* 가급적 Entity 보다는 Dto를 리턴 - Entity 조회시 hibernate 캐시, 불필요 컬럼 조회, oneToOne N+1 문제 발생
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<AuthorizationListResponseDto> 페이지 인가 목록 응답 DTO
*/
@Override
public Page<AuthorizationListResponseDto> findPage(RequestDto requestDto, Pageable pageable) {
JPQLQuery<AuthorizationListResponseDto> query = getAuthorizationListJPQLQuery()
.where(getBooleanExpressionKeyword(requestDto));
//정렬
pageable.getSort().stream().forEach(sort -> {
Order order = sort.isAscending() ? Order.ASC : Order.DESC;
String property = sort.getProperty();
Path<Object> target = Expressions.path(Object.class, QAuthorization.authorization, CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, property));
@SuppressWarnings({ "unchecked", "rawtypes" })
OrderSpecifier<?> orderSpecifier = new OrderSpecifier(order, target);
query.orderBy(orderSpecifier);
});
QueryResults<AuthorizationListResponseDto> result = query
.offset(pageable.getOffset())
.limit(pageable.getPageSize()) //페이징
.fetchResults();
return new PageImpl<>(result.getResults(), pageable, result.getTotal());
}
/**
* 권한 목록의 인가 전체 목록 조회
*
* @param roles 권한 목록
* @return Page<AuthorizationListResponseDto> 페이지 인가 목록 응답 DTO
*/
@Override
public List<AuthorizationListResponseDto> findByRoles(List<String> roles) {
JPQLQuery<AuthorizationListResponseDto> query = getAuthorizationListJPQLQuery()
.where(JPAExpressions
.selectFrom(QRoleAuthorization.roleAuthorization)
.where(QRoleAuthorization.roleAuthorization.roleAuthorizationId.authorizationNo.eq(QAuthorization.authorization.authorizationNo)
.and(QRoleAuthorization.roleAuthorization.roleAuthorizationId.roleId.in(roles)))
.exists());
QueryResults<AuthorizationListResponseDto> result = query.fetchResults();
return result.getResults();
}
/**
* 사용자의 인가 목록 조회
*
* @param userId 사용자 id
* @return List<AuthorizationListResponseDto> 인가 목록 응답 DTO
*/
@Override
public List<AuthorizationListResponseDto> findByUserId(String userId) {
JPQLQuery<AuthorizationListResponseDto> query = getAuthorizationListJPQLQuery()
.where(JPAExpressions
.selectFrom(QRoleAuthorization.roleAuthorization)
.innerJoin(QUser.user)
.on(QUser.user.role.stringValue().eq(QRoleAuthorization.roleAuthorization.roleAuthorizationId.roleId))
.where(QRoleAuthorization.roleAuthorization.roleAuthorizationId.authorizationNo.eq(QAuthorization.authorization.authorizationNo)
.and(QUser.user.userId.eq(userId)))
.exists());
QueryResults<AuthorizationListResponseDto> result = query.fetchResults();
return result.getResults();
}
/**
* 인가 목록 JPQL Query 반환
*
* @return JPQLQuery<AuthorizationListResponseDto> 인가 목록 JPQL Query
*/
public JPQLQuery<AuthorizationListResponseDto> getAuthorizationListJPQLQuery() {
return jpaQueryFactory
.select(Projections.constructor(AuthorizationListResponseDto.class,
QAuthorization.authorization.authorizationNo,
QAuthorization.authorization.authorizationName,
QAuthorization.authorization.urlPatternValue,
QAuthorization.authorization.httpMethodCode,
QAuthorization.authorization.sortSeq
))
.from(QAuthorization.authorization);
}
/**
* 인가 다음 정렬 순서 조회
*
* @return Integer 다음 정렬 순서
*/
@Override
public Integer findNextSortSeq() {
return jpaQueryFactory
.select(QAuthorization.authorization.sortSeq.max().add(1).coalesce(1))
.from(QAuthorization.authorization)
.fetchOne();
}
/**
* 인가 정렬 순서 수정
*
* @param startSortSeq 시작 정렬 순서
* @param endSortSeq 종료 정렬 순서
* @param increaseSortSeq 증가 정렬 순서
* @return Long 수정 건수
*/
@Override
public Long updateSortSeq(Integer startSortSeq, Integer endSortSeq, int increaseSortSeq) {
return jpaQueryFactory.update(QAuthorization.authorization)
.set(QAuthorization.authorization.sortSeq, QAuthorization.authorization.sortSeq.add(increaseSortSeq))
.where(isGoeSortSeq(startSortSeq),
isLoeSortSeq(endSortSeq))
.execute();
}
/**
* 요청 DTO로 동적 검색 표현식 리턴
*
* @param requestDto 요청 DTO
* @return BooleanExpression 검색 표현식
*/
private BooleanExpression getBooleanExpressionKeyword(RequestDto requestDto) {
if (requestDto.getKeyword() == null || "".equals(requestDto.getKeyword())) return null;
switch (requestDto.getKeywordType()) {
case "authorizationName": // 인가 명
return QAuthorization.authorization.authorizationName.containsIgnoreCase(requestDto.getKeyword());
case "urlPatternValue": // URL 패턴 값
return QAuthorization.authorization.urlPatternValue.containsIgnoreCase(requestDto.getKeyword());
case "httpMethodCode": // Http Method 코드
return QAuthorization.authorization.httpMethodCode.containsIgnoreCase(requestDto.getKeyword());
default:
return null;
}
}
/**
* 정렬 순서 이하 검색 표현식
*
* @param sortSeq 정렬 순서
* @return BooleanExpression 검색 표현식
*/
private BooleanExpression isLoeSortSeq(Integer sortSeq) {
return sortSeq == null ? null : QAuthorization.authorization.sortSeq.loe(sortSeq);
}
/**
* 정렬 순서 이상 검색 표현식
*
* @param sortSeq 정렬 순서
* @return BooleanExpression 검색 표현식
*/
private BooleanExpression isGoeSortSeq(Integer sortSeq) {
return sortSeq == null ? null : QAuthorization.authorization.sortSeq.goe(sortSeq);
}
}

View File

@@ -0,0 +1,86 @@
package org.egovframe.cloud.userservice.domain.role;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.Id;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.userservice.domain.role.Role
* <p>
* 권한 엔티티 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/07
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/07 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@Entity
@EntityListeners(AuditingEntityListener.class) // Auditing 기능 포함
public class Role {
/**
* 권한 id
*/
@Id
@Column(nullable = false, length = 20, unique = true)
private String roleId;
/**
* 권한 명
*/
@Column(nullable = false, length = 50)
private String roleName;
/**
* 권한 내용
*/
@Column(length = 200)
private String roleContent;
/**
* 정렬 순서
*/
@Column
private Integer sortSeq;
/**
* 생성 일시
*/
@CreatedDate
@Column
private LocalDateTime createdDate;
/**
* 빌드 패턴 클래스 생성자
*
* @param roleId 권한 id
* @param roleName 권한 명
* @param roleContent 권한 내용
* @param sortSeq 정렬 순서
*/
@Builder
public Role(String roleId, String roleName, String roleContent, Integer sortSeq) {
this.roleId = roleId;
this.roleName = roleName;
this.roleContent = roleContent;
this.sortSeq = sortSeq;
}
}

View File

@@ -0,0 +1,80 @@
package org.egovframe.cloud.userservice.domain.role;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;
/**
* org.egovframe.cloud.userservice.domain.role.RoleAuthorization
* <p>
* 권한 인가 엔티티 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/09
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/09 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@Entity
@EntityListeners(AuditingEntityListener.class) // Auditing 기능 포함
public class RoleAuthorization {
/**
* 권한 인가 복합키
*/
@EmbeddedId
private RoleAuthorizationId roleAuthorizationId;
/**
* 인가 엔티티
*/
@MapsId("authorizationNo") // RoleAuthorizationId.authorizationNo 매핑
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "authorization_no")
private Authorization authorization;
/**
* 생성자 id
*/
@CreatedBy
@Column(updatable = false)
private String createdBy;
/**
* 생성 일시
*/
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
/**
* 빌드 패턴 클래스 생성자
*
* @param roleId 권한 id
* @param authorizationNo 인가 번호
*/
@Builder
public RoleAuthorization(String roleId, Integer authorizationNo) {
this.roleAuthorizationId = RoleAuthorizationId.builder()
.roleId(roleId)
.authorizationNo(authorizationNo)
.build();
this.authorization = Authorization.builder()
.authorizationNo(authorizationNo).build();
}
}

View File

@@ -0,0 +1,87 @@
package org.egovframe.cloud.userservice.domain.role;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;
/**
* org.egovframe.cloud.userservice.domain.role.RoleAuthorizationId
* <p>
* 권한 인가 엔티티 복합키 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/09
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/09 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@Embeddable
public class RoleAuthorizationId implements Serializable {
/**
* SerialVersionUID
*/
private static final long serialVersionUID = 7191831905023135716L;
/**
* 권한 id
*/
@Column(length = 20)
private String roleId;
/**
* 인가 번호
*/
private Integer authorizationNo; // @MapsId("authorizationNo")로 매핑
/**
* 빌드 패턴 클래스 생성자
*
* @param roleId 권한 id
* @param authorizationNo 인가 번호
*/
@Builder
public RoleAuthorizationId(String roleId, Integer authorizationNo) {
this.roleId = roleId;
this.authorizationNo = authorizationNo;
}
/**
* Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by java.util.HashMap.
*
* @return int a hash code value for this object.
*/
@Override
public int hashCode() {
return Objects.hash(roleId, authorizationNo);
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param object the reference object with which to compare.
* @return {@code true} if this object is the same as the obj
*/
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (!(object instanceof RoleAuthorizationId)) return false;
RoleAuthorizationId that = (RoleAuthorizationId) object;
return Objects.equals(roleId, that.getRoleId()) &&
Objects.equals(authorizationNo, that.getAuthorizationNo());
}
}

View File

@@ -0,0 +1,23 @@
package org.egovframe.cloud.userservice.domain.role;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* org.egovframe.cloud.userservice.domain.role.RoleAuthorizationRepository
* <p>
* 권한 인가 레파지토리 인터페이스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/09
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/09 jooho 최초 생성
* </pre>
*/
public interface RoleAuthorizationRepository extends JpaRepository<RoleAuthorization, RoleAuthorizationId>, RoleAuthorizationRepositoryCustom {
}

View File

@@ -0,0 +1,38 @@
package org.egovframe.cloud.userservice.domain.role;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationListRequestDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationListResponseDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/**
* org.egovframe.cloud.userservice.domain.role.RoleAuthorizationRepositoryCustom
* <p>
* 권한 인가 Querydsl 인터페이스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/15 jooho 최초 생성
* </pre>
*/
public interface RoleAuthorizationRepositoryCustom {
/**
* 권한 인가 페이지 목록 조회
* 인가 기준으로 권한 인가 아우터 조인
* 가급적 Entity 보다는 Dto를 리턴 - Entity 조회시 hibernate 캐시, 불필요 컬럼 조회, oneToOne N+1 문제 발생
*
* @param requestDto 권한 인가 목록 요청 DTO
* @param pageable 페이지 정보
* @return Page<RoleAuthorizationListResponseDto> 페이지 권한 인가 목록 응답 DTO
*/
Page<RoleAuthorizationListResponseDto> findPageAuthorizationList(RoleAuthorizationListRequestDto requestDto, Pageable pageable);
}

View File

@@ -0,0 +1,114 @@
package org.egovframe.cloud.userservice.domain.role;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.userservice.api.role.dto.QRoleAuthorizationListResponseDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationListRequestDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationListResponseDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
/**
* org.egovframe.cloud.userservice.domain.role.RoleAuthorizationRepositoryImpl
* <p>
* 권한 인가 Querydsl 구현 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/15 jooho 최초 생성
* </pre>
*/
@RequiredArgsConstructor
public class RoleAuthorizationRepositoryImpl implements RoleAuthorizationRepositoryCustom {
/**
* DML 생성을위한 Querydsl 팩토리 클래스
*/
private final JPAQueryFactory jpaQueryFactory;
/**
* 권한 인가 페이지 목록 조회
* 인가 기준으로 권한 인가 아우터 조인
* 가급적 Entity 보다는 Dto를 리턴 - Entity 조회시 hibernate 캐시, 불필요 컬럼 조회, oneToOne N+1 문제 발생
*
* @param requestDto 권한 인가 목록 요청 DTO
* @param pageable 페이지 정보
* @return Page<RoleAuthorizationListResponseDto> 페이지 권한 인가 목록 응답 DTO
*/
public Page<RoleAuthorizationListResponseDto> findPageAuthorizationList(RoleAuthorizationListRequestDto requestDto, Pageable pageable) {
JPQLQuery<RoleAuthorizationListResponseDto> query = jpaQueryFactory
.select(new QRoleAuthorizationListResponseDto(
Expressions.as(Expressions.constant(requestDto.getRoleId()), "roleId"),
QAuthorization.authorization.authorizationNo,
QAuthorization.authorization.authorizationName,
QAuthorization.authorization.urlPatternValue,
QAuthorization.authorization.httpMethodCode,
QAuthorization.authorization.sortSeq,
Expressions.as(new CaseBuilder()
.when(QRoleAuthorization.roleAuthorization.roleAuthorizationId.roleId.isNotNull()
.and(QRoleAuthorization.roleAuthorization.roleAuthorizationId.authorizationNo.isNotNull()))
.then(true)
.otherwise(false)
, "createdAt") // 생성 여부
))
.from(QAuthorization.authorization)
.leftJoin(QRoleAuthorization.roleAuthorization).on(QAuthorization.authorization.authorizationNo.eq(QRoleAuthorization.roleAuthorization.roleAuthorizationId.authorizationNo)
.and(getBooleanExpressionRoleId(requestDto.getRoleId()))) // 권한 id
.fetchJoin()
.where(getBooleanExpressionKeyword(requestDto))
.orderBy(QAuthorization.authorization.sortSeq.asc()); // 인가 정렬 순서 오름차순
QueryResults<RoleAuthorizationListResponseDto> result = query
.offset(pageable.getOffset())
.limit(pageable.getPageSize()) //페이징
.fetchResults();
return new PageImpl<>(result.getResults(), pageable, result.getTotal());
}
/**
* 권한 id 검색 표현식 리턴
*
* @param roleId 권한 id
* @return BooleanExpression
*/
private BooleanExpression getBooleanExpressionRoleId(String roleId) {
return roleId != null && !"".equals(roleId) ? QRoleAuthorization.roleAuthorization.roleAuthorizationId.roleId.eq(roleId) : null;
}
/**
* 요청 DTO로 동적 검색 표현식 리턴
*
* @param requestDto 요청 DTO
* @return BooleanExpression 검색 표현식
*/
private BooleanExpression getBooleanExpressionKeyword(RequestDto requestDto) {
if (requestDto.getKeyword() == null || "".equals(requestDto.getKeyword())) return null;
switch (requestDto.getKeywordType()) {
case "authorizationName": // 인가 명
return QAuthorization.authorization.authorizationName.containsIgnoreCase(requestDto.getKeyword());
case "urlPatternValue": // URL 패턴 값
return QAuthorization.authorization.urlPatternValue.containsIgnoreCase(requestDto.getKeyword());
case "httpMethodCode": // Http Method 코드
return QAuthorization.authorization.httpMethodCode.containsIgnoreCase(requestDto.getKeyword());
default:
return null;
}
}
}

View File

@@ -0,0 +1,23 @@
package org.egovframe.cloud.userservice.domain.role;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* org.egovframe.cloud.userservice.domain.role.RoleRepository
* <p>
* 권한 레파지토리 인터페이스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/07
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/07 jooho 최초 생성
* </pre>
*/
public interface RoleRepository extends JpaRepository<Role, Long>, RoleRepositoryCustom {
}

View File

@@ -0,0 +1,37 @@
package org.egovframe.cloud.userservice.domain.role;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleListResponseDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/**
* org.egovframe.cloud.userservice.domain.role.RoleRepositoryCustom
* <p>
* 권한 Querydsl 인터페이스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/15 jooho 최초 생성
* </pre>
*/
public interface RoleRepositoryCustom {
/**
* 권한 페이지 목록 조회
* 가급적 Entity 보다는 Dto를 리턴 - Entity 조회시 hibernate 캐시, 불필요 컬럼 조회, oneToOne N+1 문제 발생
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<RoleListResponseDto> 페이지 권한 목록 응답 DTO
*/
Page<RoleListResponseDto> findPage(RequestDto requestDto, Pageable pageable);
}

View File

@@ -0,0 +1,106 @@
package org.egovframe.cloud.userservice.domain.role;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.userservice.api.role.dto.QRoleListResponseDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleListResponseDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import com.google.common.base.CaseFormat;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
/**
* org.egovframe.cloud.userservice.domain.role.RoleRepositoryImpl
* <p>
* 권한 Querydsl 구현 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/15 jooho 최초 생성
* </pre>
*/
@RequiredArgsConstructor
public class RoleRepositoryImpl implements RoleRepositoryCustom {
/**
* DML 생성을위한 Querydsl 팩토리 클래스
*/
private final JPAQueryFactory jpaQueryFactory;
/**
* 권한 페이지 목록 조회
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<RoleListResponseDto> 페이지 권한 목록 응답 DTO
*/
@Override
public Page<RoleListResponseDto> findPage(RequestDto requestDto, Pageable pageable) {
JPQLQuery<RoleListResponseDto> query = jpaQueryFactory
.select(new QRoleListResponseDto(
QRole.role.roleId,
QRole.role.roleName,
QRole.role.roleContent,
QRole.role.createdDate
))
.from(QRole.role)
.where(getBooleanExpressionKeyword(requestDto));
//정렬
pageable.getSort().stream().forEach(sort -> {
Order order = sort.isAscending() ? Order.ASC : Order.DESC;
String property = sort.getProperty();
Path<Object> target = Expressions.path(Object.class, QRole.role, CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, property));
@SuppressWarnings({ "unchecked", "rawtypes" })
OrderSpecifier<?> orderSpecifier = new OrderSpecifier(order, target);
query.orderBy(orderSpecifier);
});
QueryResults<RoleListResponseDto> result = query
.offset(pageable.getOffset())
.limit(pageable.getPageSize()) //페이징
.fetchResults();
return new PageImpl<>(result.getResults(), pageable, result.getTotal());
}
/**
* 요청 DTO로 동적 검색 표현식 리턴
*
* @param requestDto 요청 DTO
* @return BooleanExpression 검색 표현식
*/
private BooleanExpression getBooleanExpressionKeyword(RequestDto requestDto) {
if (requestDto.getKeyword() == null || "".equals(requestDto.getKeyword())) return null;
switch (requestDto.getKeywordType()) {
case "roleId": // 권한 id
return QRole.role.roleId.containsIgnoreCase(requestDto.getKeyword());
case "roleName": // 권한 명
return QRole.role.roleName.containsIgnoreCase(requestDto.getKeyword());
case "roleContent": // 권한 내용
return QRole.role.roleContent.containsIgnoreCase(requestDto.getKeyword());
default:
return null;
}
}
}

View File

@@ -0,0 +1,240 @@
package org.egovframe.cloud.userservice.domain.user;
import static javax.persistence.GenerationType.IDENTITY;
import java.time.LocalDateTime;
import java.util.Arrays;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.egovframe.cloud.common.domain.Role;
import org.egovframe.cloud.servlet.domain.BaseEntity;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* org.egovframe.cloud.userservice.domain.user.User
* <p>
* 사용자 정보 엔티티
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/06/30
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/06/30 jaeyeolkim 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@DynamicInsert
@DynamicUpdate
@Entity
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "user_no")
private Long id;
@Column(nullable = false, unique = true)
private String userId;
@Column(nullable = false, length = 50)
private String userName;
@Column(nullable = false, name = "email_addr", length = 100, unique = true)
private String email;
@Column(length = 100)
private String encryptedPassword;
@Enumerated(EnumType.STRING) // Enum 값을 String 문자열로 저장
@Column(name = "role_id", nullable = false)
private Role role;
private String refreshToken;
@Column(nullable = false, length = 20, columnDefinition = "varchar(20) default '00'")
private String userStateCode;
@Column
private LocalDateTime lastLoginDate;
@Column(nullable = false, columnDefinition = "tinyint default '0'")
private Integer loginFailCount;
@Column(length = 100)
private String googleId;
@Column(length = 100)
private String kakaoId;
@Column(length = 100)
private String naverId;
@Builder
public User(String userName, String email, String encryptedPassword, String userId,
Role role, String userStateCode, String googleId, String kakaoId, String naverId) {
this.userName = userName;
this.email = email;
this.encryptedPassword = encryptedPassword;
this.userId = userId;
this.role = role;
this.userStateCode = userStateCode;
this.googleId = googleId;
this.kakaoId = kakaoId;
this.naverId = naverId;
}
/**
* 사용자 명과 이메일 정보를 수정한다.
*
* @param username 사용자 명
* @param email 이메일
* @param encryptedPassword 암호화 비밀번호
* @param roleId 권한 id
* @param userStateCode 회원 상태 코드
* @return
*/
public User update(String username, String email, String encryptedPassword, String roleId, String userStateCode) {
this.userName = username;
this.email = email;
this.encryptedPassword = encryptedPassword;
this.role = Arrays.stream(Role.values()).filter(c -> c.getKey().equals(roleId)).findAny().orElse(null);
this.userStateCode = userStateCode;
return this;
}
/**
* 사용자 refresh token 정보를 필드에 입력한다.
*
* @param refreshToken
* @return
*/
public User updateRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
return this;
}
/**
* 사용자 비밀번호 정보를 필드에 입력한다.
*
* @param encryptedPassword 암호화 비밀번호
* @return User 사용자 엔티티
*/
public User updatePassword(String encryptedPassword) {
this.encryptedPassword = encryptedPassword;
return this;
}
/**
* 사용자 명과 이메일 정보를 수정한다.
*
* @param username
* @param email
* @return
*/
public User updateInfo(String username, String email) {
this.userName = username;
this.email = email;
return this;
}
/**
* 사용자 상태 코드 정보를 필드에 입력한다.
*
* @param userStateCode 상태 코드
* @return User 사용자 엔티티
*/
public User updateUserStateCode(String userStateCode) {
this.userStateCode = userStateCode;
return this;
}
/**
* 로그인 실패 시 로그인실패수를 증가시키고 5회 이상 실패한 경우 회원상태를 정지로 변경
*
* @return User 사용자 엔티티
*/
public User failLogin() {
this.loginFailCount = loginFailCount + 1;
if (this.loginFailCount >= 5) {
this.userStateCode = UserStateCode.HALT.getKey();
}
return this;
}
/**
* 로그인 성공 시 로그인실패수와 마지막로그인일시 정보를 갱신
*
* @return User 사용자 엔티티
*/
public User successLogin() {
this.loginFailCount = 0;
this.lastLoginDate = LocalDateTime.now();
return this;
}
/**
* 구글 id 등록
*
* @return User 사용자 엔티티
*/
public User updateGoogleId(String googleId) {
this.googleId = googleId;
return this;
}
/**
* 카카오 id 등록
*
* @return User 사용자 엔티티
*/
public User updateKakaoId(String kakaoId) {
this.kakaoId = kakaoId;
return this;
}
/**
* 네이버 id 등록
*
* @return User 사용자 엔티티
*/
public User updateNaverId(String naverId) {
this.naverId = naverId;
return this;
}
/**
* 소셜 사용자 여부 반환
*
* @return boolean 소셜 사용자 여부
*/
public boolean isSocialUser() {
if (this.googleId != null && !"".equals(this.googleId)) return true;
else if (this.kakaoId != null && !"".equals(this.kakaoId)) return true;
else if (this.naverId != null && !"".equals(this.naverId)) return true;
return false;
}
public String getRoleKey() {
return this.role.getKey();
}
}

View File

@@ -0,0 +1,86 @@
package org.egovframe.cloud.userservice.domain.user;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.egovframe.cloud.servlet.domain.BaseTimeEntity;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
/**
* org.egovframe.cloud.userservice.domain.user.UserFindPassword
* <p>
* 사용자 비밀번호 찾기 엔티티 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 jooho 최초 생성
* </pre>
*/
@Getter
@NoArgsConstructor
@Entity
@DynamicInsert
@DynamicUpdate
public class UserFindPassword extends BaseTimeEntity {
/**
* 복합키
*/
@EmbeddedId
private UserFindPasswordId userFindPasswordId;
/**
* 토큰 값
*/
@Column(nullable = false, length = 50)
private String tokenValue;
/**
* 변경 여부
*/
@Column(nullable = false, columnDefinition = "tinyint(1) default '0'")
private Boolean changeAt;
/**
* 빌드 패턴 클래스 생성자
*
* @param emailAddr 이메일 주소
* @param requestNo 요청 번호
* @param tokenValue 토큰 값
* @param changeAt 변경 여부
*/
@Builder
public UserFindPassword(String emailAddr, Integer requestNo, String tokenValue, Boolean changeAt) {
this.userFindPasswordId = UserFindPasswordId.builder()
.emailAddr(emailAddr)
.requestNo(requestNo)
.build();
this.tokenValue = tokenValue;
this.changeAt = changeAt;
}
/**
* 변경 여부 수정
*
* @param changeAt 변경 여부
* @return UserFindPassword 사용자 비밀번호 찾기 엔티티
*/
public UserFindPassword updateChangeAt(Boolean changeAt) {
this.changeAt = changeAt;
return this;
}
}

View File

@@ -0,0 +1,70 @@
package org.egovframe.cloud.userservice.domain.user;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;
@Getter
@NoArgsConstructor
@Embeddable
public class UserFindPasswordId implements Serializable {
/**
* SerialVersionUID
*/
private static final long serialVersionUID = -2267755880384011782L;
/**
* 이메일 주소
*/
@Column(length = 50)
private String emailAddr;
/**
* 요청 번호
*/
private Integer requestNo;
/**
* 빌드 패턴 클래스 생성자
*
* @param emailAddr 이메일 주소
* @param requestNo 요청 번호
*/
@Builder
public UserFindPasswordId(String emailAddr, Integer requestNo) {
this.emailAddr = emailAddr;
this.requestNo = requestNo;
}
/**
* Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by java.util.HashMap.
*
* @return int a hash code value for this object.
*/
@Override
public int hashCode() {
return Objects.hash(emailAddr, requestNo);
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param object the reference object with which to compare.
* @return {@code true} if this object is the same as the obj
*/
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (!(object instanceof UserFindPasswordId)) return false;
UserFindPasswordId that = (UserFindPasswordId) object;
return Objects.equals(emailAddr, that.getEmailAddr()) &&
Objects.equals(requestNo, that.getRequestNo());
}
}

View File

@@ -0,0 +1,34 @@
package org.egovframe.cloud.userservice.domain.user;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
/**
* org.egovframe.cloud.userservice.domain.user.UserFindPasswordRepository
*
* 사용자 비밀번호 찾기 레파지토리 인터페이스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 jooho 최초 생성
* </pre>
*/
public interface UserFindPasswordRepository extends JpaRepository<UserFindPassword, UserFindPasswordId>, UserFindPasswordRepositoryCustom {
/**
* 토큰 값이 일치하는 사용자 비밀번호 찾기 조회
*
* @param tokenValue 토큰 값
* @return Optional<UserFindPassword> 사용자 비밀번호 번경 요청 엔티티
*/
Optional<UserFindPassword> findByTokenValue(String tokenValue);
}

View File

@@ -0,0 +1,30 @@
package org.egovframe.cloud.userservice.domain.user;
/**
* org.egovframe.cloud.userservice.domain.user.UserFindPasswordRepositoryCustom
* <p>
* 사용자 비밀번호 찾기 Querydsl 인터페이스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 jooho 최초 생성
* </pre>
*/
public interface UserFindPasswordRepositoryCustom {
/**
* 다음 요청 번호 조회
*
* @param emailAddr 이메일 주소
* @return Integer 다음 요청 번호
*/
Integer findNextRequestNo(String emailAddr);
}

View File

@@ -0,0 +1,46 @@
package org.egovframe.cloud.userservice.domain.user;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
/**
* org.egovframe.cloud.userservice.domain.user.UserFindPasswordRepositoryImpl
* <p>
* 사용자 비밀번호 찾기 Querydsl 구현 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/15
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/15 jooho 최초 생성
* </pre>
*/
@RequiredArgsConstructor
public class UserFindPasswordRepositoryImpl implements UserFindPasswordRepositoryCustom {
/**
* DML 생성을위한 Querydsl 팩토리 클래스
*/
private final JPAQueryFactory jpaQueryFactory;
/**
* 다음 요청 번호 조회
*
* @param emailAddr 이메일 주소
* @return Integer 다음 요청 번호
*/
@Override
public Integer findNextRequestNo(String emailAddr) {
return jpaQueryFactory
.select(QUserFindPassword.userFindPassword.userFindPasswordId.requestNo.max().add(1).coalesce(1))
.from(QUserFindPassword.userFindPassword)
.where(QUserFindPassword.userFindPassword.userFindPasswordId.emailAddr.eq(emailAddr))
.fetchOne();
}
}

View File

@@ -0,0 +1,37 @@
package org.egovframe.cloud.userservice.domain.user;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
/**
* org.egovframe.cloud.userservice.domain.user.UserRepository
* <p>
* Spring Data JPA 에서 제공되는 JpaRepository 를 상속하는 인터페이스
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/07/01
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/01 jaeyeolkim 최초 생성
* </pre>
*/
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
// email을 통해 이미 생성된 사용자인지 판단하기 위한 메소드
Optional<User> findByEmail(String email);
Optional<User> findByUserId(String userId);
Optional<User> findByRefreshToken(String refreshToken);
List<User> findByEmailContains(String email);
Optional<User> findByEmailAndUserName(String email, String userName);
Optional<User> findByEmailAndUserIdNot(String email, String userId);
Optional<User> findByGoogleId(String googleId);
Optional<User> findByKakaoId(String kakaoId);
Optional<User> findByNaverId(String naverId);
}

View File

@@ -0,0 +1,36 @@
package org.egovframe.cloud.userservice.domain.user;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserListResponseDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
/**
* org.egovframe.cloud.userservice.domain.user.UserRepositoryCustom
* <p>
* 사용자 Querydsl 인터페이스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/23
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/23 jooho 최초 생성
* </pre>
*/
public interface UserRepositoryCustom {
/**
* 사용자 페이지 목록 조회
*
* @param requestDto 사용자 목록 요청 DTO
* @param pageable 페이지 정보
* @return Page<UserListResponseDto> 페이지 사용자 목록 응답 DTO
*/
Page<UserListResponseDto> findPage(RequestDto requestDto, Pageable pageable);
}

View File

@@ -0,0 +1,86 @@
package org.egovframe.cloud.userservice.domain.user;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserListResponseDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
/**
* org.egovframe.cloud.userservice.domain.user.UserRepositoryImpl
* <p>
* 사용자 Querydsl 구현 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/23
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/23 jooho 최초 생성
* </pre>
*/
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepositoryCustom {
/**
* DML 생성을위한 Querydsl 팩토리 클래스
*/
private final JPAQueryFactory jpaQueryFactory;
/**
* 사용자 페이지 목록 조회
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<UserListResponseDto> 페이지 사용자 목록 응답 DTO
*/
public Page<UserListResponseDto> findPage(RequestDto requestDto, Pageable pageable) {
QueryResults<UserListResponseDto> result = jpaQueryFactory
.select(Projections.constructor(UserListResponseDto.class,
QUser.user.userId,
QUser.user.userName,
QUser.user.email,
QUser.user.role,
QUser.user.userStateCode,
QUser.user.lastLoginDate,
QUser.user.loginFailCount
))
.from(QUser.user)
.where(getBooleanExpression(requestDto))
.orderBy(QUser.user.userName.asc(), QUser.user.email.asc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetchResults();
return new PageImpl<>(result.getResults(), pageable, result.getTotal());
}
/**
* 요청 DTO로 동적 검색 표현식 리턴
*
* @param requestDto 요청 DTO
* @return BooleanExpression 검색 표현식
*/
private BooleanExpression getBooleanExpression(RequestDto requestDto) {
if (requestDto.getKeyword() == null || "".equals(requestDto.getKeyword())) return null;
switch (requestDto.getKeywordType()) {
case "userName": // 사용자 명
return QUser.user.userName.containsIgnoreCase(requestDto.getKeyword());
case "email": // 이메일 주소
return QUser.user.email.containsIgnoreCase(requestDto.getKeyword());
default:
return null;
}
}
}

View File

@@ -0,0 +1,48 @@
package org.egovframe.cloud.userservice.domain.user;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* org.egovframe.cloud.userservice.domain.user.UserStateCode
*
* 사용자 상태 코드 열거형 상수
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/09/17
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/09/17 jooho 최초 생성
* </pre>
*/
@Getter
@RequiredArgsConstructor
public enum UserStateCode {
WAIT("00", "대기"),
NORMAL("01", "정상"),
HALT("07", "정지"),
LEAVE("08", "탈퇴"),
DELETE("09", "삭제");
private final String key;
private final String title;
/**
* 사용자 상태 코드로 상수 검색
*
* @param key 사용자 상태 코드
* @return UserStateCode 사용자 상태 코드 상수
*/
public static UserStateCode findByKey(String key) {
return Arrays.stream(UserStateCode.values()).filter(c -> c.getKey().equals(key)).findAny().orElse(null);
}
}

View File

@@ -0,0 +1,298 @@
package org.egovframe.cloud.userservice.service.role;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.egovframe.cloud.common.config.GlobalConstant;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.common.exception.EntityNotFoundException;
import org.egovframe.cloud.common.service.AbstractService;
import org.egovframe.cloud.userservice.api.role.dto.AuthorizationListResponseDto;
import org.egovframe.cloud.userservice.api.role.dto.AuthorizationResponseDto;
import org.egovframe.cloud.userservice.api.role.dto.AuthorizationSaveRequestDto;
import org.egovframe.cloud.userservice.api.role.dto.AuthorizationUpdateRequestDto;
import org.egovframe.cloud.userservice.domain.role.Authorization;
import org.egovframe.cloud.userservice.domain.role.AuthorizationRepository;
import org.springframework.aop.framework.AopContext;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.AntPathMatcher;
import lombok.RequiredArgsConstructor;
/**
* org.egovframe.cloud.userservice.service.role.AuthorizationService
* <p>
* 인가 서비스 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jooho 최초 생성
* </pre>
*/
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class AuthorizationService extends AbstractService {
/**
* 인가 레파지토리 인터페이스
*/
private final AuthorizationRepository authorizationRepository;
/**
* 캐시 관리자
*/
private final CacheManager cacheManager;
/**
* 조회 조건에 일치하는 인가 페이지 목록 조회
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<AuthorizationListResponseDto> 페이지 인가 목록 응답 DTO
*/
public Page<AuthorizationListResponseDto> findPage(RequestDto requestDto, Pageable pageable) {
return authorizationRepository.findPage(requestDto, pageable);
}
/**
* 권한의 인가 여부 확인
* 사용자 서비스 시큐리티 필터에서 호출
*
* @param request http 요청
* @param authentication 시큐리티 인증 토큰
* @return Boolean 인가 여부
*/
public Boolean isAuthorization(HttpServletRequest request, Authentication authentication) {
List<String> roles = authentication.getAuthorities().stream().map(GrantedAuthority::toString).collect(Collectors.toList());
List<AuthorizationListResponseDto> authorizationList = ((AuthorizationService) AopContext.currentProxy()).findByRoles(roles);
return isContainMatch(authorizationList, request.getMethod(), GlobalConstant.USER_SERVICE_URI + request.getRequestURI());
}
/**
* 권한의 인가 전체 목록 조회
*
* @param roles 권한 목록
* @return List<AuthorizationListResponseDto> 인가 목록
*/
@Cacheable(value = "cache-user-authorization-by-roles", key = "#roles")
public List<AuthorizationListResponseDto> findByRoles(List<String> roles) {
return authorizationRepository.findByRoles(roles);
}
/**
* 권한의 인가 여부 확인
* gateway 에서 호출
* <p>
* Spring Cache는 Spring AOP를 이용해서 proxy로 동작하기 때문에 외부 method 호출만 인터셉트해서 작동하고 self-invocation의 경우 동작하지 않음
* 스프링에서는 AspectJ를 권장하지만 Load-time Weaving 방식은 퍼포먼스 문제가 있고
* Compile-time Weaving 방식은 컴파일 시 수행되는 라이브러리(lombok)와 충돌 문제가 있음
* AopContext.currentProxy()를 이용해서 proxy로 호출하도록 함 - CacheConfig @EnableAspectJAutoProxy(exposeProxy=true)
*
* @param roles 권한 목록
* @param httpMethod Http Method
* @param requestPath 요청 경로
* @return Boolean 인가 여부
*/
public Boolean isAuthorization(List<String> roles, String httpMethod, String requestPath) {
List<AuthorizationListResponseDto> authorizationList = ((AuthorizationService) AopContext.currentProxy()).findByRoles(roles);
return isContainMatch(authorizationList, httpMethod, requestPath);
}
/**
* 사용자의 인가 전체 목록 조회
*
* @param userId 사용자 id
* @return List<AuthorizationListResponseDto> 인가 목록
*/
@Cacheable(value = "cache-user-authorization-by-userid", key = "#roles")
public List<AuthorizationListResponseDto> findByUserId(String userId) {
return authorizationRepository.findByUserId(userId);
}
/**
* 사용자의 인가 여부 확인
* gateway 에서 호출
*
* @param userId 사용자 id
* @param httpMethod Http Method
* @param requestPath 요청 경로
* @return Boolean 인가 여부
*/
public Boolean isAuthorization(String userId, String httpMethod, String requestPath) {
List<AuthorizationListResponseDto> authorizationList = ((AuthorizationService) AopContext.currentProxy()).findByUserId(userId);
return isContainMatch(authorizationList, httpMethod, requestPath);
}
/**
* 인가 여부 체크
*
* @param authorizationList 인가 목록
* @param httpMethod Http Method
* @param requestPath 요청 경로
* @return Boolean 인가 여부
*/
private Boolean isContainMatch(List<AuthorizationListResponseDto> authorizationList, String httpMethod, String requestPath) {
AntPathMatcher antPathMatcher = new AntPathMatcher();
for (AuthorizationListResponseDto dto : authorizationList) {
if (antPathMatcher.match(dto.getUrlPatternValue(), requestPath) && dto.getHttpMethodCode().equals(httpMethod)) {
return true;
}
}
return false;
}
/**
* 인가 단건 조회
*
* @param authorizationNo 인가 번호
* @return AuthorizationResponseDto 인가 응답 DTO
*/
public AuthorizationResponseDto findById(Integer authorizationNo) {
Authorization entity = findAuthorization(authorizationNo);
return new AuthorizationResponseDto(entity);
}
/**
* 인가 다음 정렬 순서 조회
*
* @return Integer 다음 정렬 순서
*/
public Integer findNextSortSeq() {
return authorizationRepository.findNextSortSeq();
}
/**
* 인가 등록
*
* @param requestDto 인가 등록 요청 DTO
* @return AuthorizationResponseDto 인가 응답 DTO
*/
@Transactional
public AuthorizationResponseDto save(AuthorizationSaveRequestDto requestDto) {
// 동일한 정렬 순서가 존재할 경우 +1
Optional<Authorization> authorization = authorizationRepository.findBySortSeq(requestDto.getSortSeq());
if (authorization.isPresent()) {
authorizationRepository.updateSortSeq(requestDto.getSortSeq(), null, 1);
}
// 등록
Authorization entity = authorizationRepository.save(requestDto.toEntity());
clearAuthorizationCache();
return new AuthorizationResponseDto(entity);
}
/**
* 인가 수정
*
* @param authorizationNo 인가 번호
* @param requestDto 인가 수정 요청 DTO
* @return AuthorizationResponseDto 인가 응답 DTO
*/
@Transactional
public AuthorizationResponseDto update(Integer authorizationNo, AuthorizationUpdateRequestDto requestDto) {
Authorization entity = findAuthorization(authorizationNo);
// 정렬 순서가 변경된 경우 사이 구간 정렬 순서 조정
Integer beforeSortSeq = entity.getSortSeq();
Integer afterSortSeq = requestDto.getSortSeq();
Integer startSortSeq = null;
Integer endSortSeq = null;
int increaseSortSeq = 0;
if (beforeSortSeq == null && afterSortSeq != null) {
startSortSeq = afterSortSeq;
increaseSortSeq = 1;
} else if (beforeSortSeq != null && afterSortSeq == null) {
startSortSeq = beforeSortSeq + 1;
increaseSortSeq = -1;
} else if (beforeSortSeq != null && afterSortSeq != null && beforeSortSeq.compareTo(afterSortSeq) != 0) {
if (beforeSortSeq.compareTo(afterSortSeq) > 0) {
startSortSeq = afterSortSeq;
endSortSeq = beforeSortSeq - 1;
increaseSortSeq = 1;
} else {
startSortSeq = beforeSortSeq + 1;
endSortSeq = afterSortSeq;
increaseSortSeq = -1;
}
}
if (startSortSeq != null || endSortSeq != null) {
authorizationRepository.updateSortSeq(startSortSeq, endSortSeq, increaseSortSeq);
}
// 수정
entity.update(requestDto.getAuthorizationName(), requestDto.getUrlPatternValue(), requestDto.getHttpMethodCode(), requestDto.getSortSeq());
clearAuthorizationCache();
return new AuthorizationResponseDto(entity);
}
/**
* 인가 삭제
* 권한 인가도 같이 삭제됨
*
* @param authorizationNo 인가 번호
*/
@Transactional
public void delete(Integer authorizationNo) {
Authorization entity = findAuthorization(authorizationNo);
// 삭제
authorizationRepository.delete(entity);
// 삭제한 데이터보다 정렬 순서가 더 큰 데이터 -1
authorizationRepository.updateSortSeq(entity.getSortSeq() + 1, null, -1);
clearAuthorizationCache();
}
/**
* 인가 번호로 인가 엔티티 조회
*
* @param authorizationNo 인가 번호
* @return Authorization 인가 엔티티
*/
private Authorization findAuthorization(Integer authorizationNo) {
return authorizationRepository.findById(authorizationNo)
.orElseThrow(() -> new EntityNotFoundException(getMessage("valid.notexists.format", new Object[]{getMessage("authorization")})));
}
/**
* 인가 조회 캐시 클리어
*/
private void clearAuthorizationCache() {
Cache useridCache = cacheManager.getCache("cache-user-authorization-by-userid");
if (useridCache != null) useridCache.clear();
Cache rolesCache = cacheManager.getCache("cache-user-authorization-by-roles");
if (rolesCache != null) rolesCache.clear();
}
}

View File

@@ -0,0 +1,120 @@
package org.egovframe.cloud.userservice.service.role;
import lombok.RequiredArgsConstructor;
import org.egovframe.cloud.common.service.AbstractService;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationDeleteRequestDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationListRequestDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationListResponseDto;
import org.egovframe.cloud.userservice.api.role.dto.RoleAuthorizationSaveRequestDto;
import org.egovframe.cloud.userservice.domain.role.RoleAuthorization;
import org.egovframe.cloud.userservice.domain.role.RoleAuthorizationRepository;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* org.egovframe.cloud.userservice.service.role.RoleAuthorizationService
* <p>
* 권한 인가 서비스 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/12
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/12 jooho 최초 생성
* </pre>
*/
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class RoleAuthorizationService extends AbstractService {
/**
* 권한 인가 레파지토리 인터페이스
*/
private final RoleAuthorizationRepository roleAuthorizationRepository;
/**
* 캐시 관리자
*/
private final CacheManager cacheManager;
/**
* 조회 조건에 일치하는 권한 인가 페이지 목록 조회
*
* @param requestDto 권한 인가 목록 요청 DTO
* @param pageable 페이지 정보
* @return Page<RoleAuthorizationListResponseDto> 페이지 권한 인가 목록 응답 DTO
*/
public Page<RoleAuthorizationListResponseDto> findPageAuthorizationList(RoleAuthorizationListRequestDto requestDto, Pageable pageable) {
if (requestDto.getRoleId() == null || "".equals(requestDto.getRoleId())) {
return new PageImpl<>(Collections.emptyList(), pageable, 0);
}
return roleAuthorizationRepository.findPageAuthorizationList(requestDto, pageable);
}
/**
* 권한 인가 다건 등록
*
* @param requestDtoList 권한 인가 등록 요청 DTO List
* @return List<RoleAuthorizationListResponseDto> 등록 권한 인가 목록
*/
@Transactional
public List<RoleAuthorizationListResponseDto> save(List<RoleAuthorizationSaveRequestDto> requestDtoList) {
List<RoleAuthorization> saveEntityList = requestDtoList.stream()
.map(RoleAuthorizationSaveRequestDto::toEntity)
.collect(Collectors.toList());
List<RoleAuthorization> savedEntityList = roleAuthorizationRepository.saveAll(saveEntityList);
clearAuthorizationCache();
return savedEntityList.stream()
.map(m -> RoleAuthorizationListResponseDto.builder()
.roleId(m.getRoleAuthorizationId().getRoleId())
.authorizationNo(m.getRoleAuthorizationId().getAuthorizationNo())
.build())
.collect(Collectors.toList());
}
/**
* 권한 인가 다건 삭제
*
* @param requestDtoList 권한 인가 삭제 요청 DTO List
*/
@Transactional
public void delete(List<RoleAuthorizationDeleteRequestDto> requestDtoList) {
List<RoleAuthorization> deleteEntityList = requestDtoList.stream()
.map(RoleAuthorizationDeleteRequestDto::toEntity)
.collect(Collectors.toList());
roleAuthorizationRepository.deleteAll(deleteEntityList);
clearAuthorizationCache();
}
/**
* 인가 조회 캐시 클리어
*/
private void clearAuthorizationCache() {
Cache useridCache = cacheManager.getCache("cache-user-authorization-by-userid");
if (useridCache != null) useridCache.clear();
Cache rolesCache = cacheManager.getCache("cache-user-authorization-by-roles");
if (rolesCache != null) rolesCache.clear();
}
}

View File

@@ -0,0 +1,72 @@
package org.egovframe.cloud.userservice.service.role;
import lombok.RequiredArgsConstructor;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.common.service.AbstractService;
import org.egovframe.cloud.userservice.api.role.dto.RoleListResponseDto;
import org.egovframe.cloud.userservice.domain.role.RoleRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/**
* org.egovframe.cloud.userservice.service.role.RoleService
* <p>
* 권한 서비스 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/07
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/07 jooho 최초 생성
* </pre>
*/
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class RoleService extends AbstractService {
/**
* 권한 레파지토리 인터페이스
*/
private final RoleRepository roleRepository;
/**
* 조회 조건에 일치하는 권한 페이지 목록 조회
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<RoleListResponseDto> 페이지 권한 목록 응답 DTO
*/
public Page<RoleListResponseDto> findPage(RequestDto requestDto, Pageable pageable) {
return roleRepository.findPage(requestDto, pageable);
}
/**
* 권한 전체 조회
*
* @param sort 정렬
* @return List<RoleListResponseDto> 권한 목록 응답 DTO
*/
public List<RoleListResponseDto> findAllBySort(Sort sort) {
return roleRepository.findAll(sort).stream()
.map(m -> RoleListResponseDto.builder()
.roleId(m.getRoleId())
.roleName(m.getRoleName())
.roleContent(m.getRoleContent())
.createdDate(m.getCreatedDate())
.build())
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,861 @@
package org.egovframe.cloud.userservice.service.user;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.cloud.common.domain.Role;
import org.egovframe.cloud.common.dto.RequestDto;
import org.egovframe.cloud.common.exception.BusinessMessageException;
import org.egovframe.cloud.common.service.AbstractService;
import org.egovframe.cloud.common.util.LogUtil;
import org.egovframe.cloud.userservice.api.user.dto.*;
import org.egovframe.cloud.userservice.config.UserPasswordChangeEmailTemplate;
import org.egovframe.cloud.userservice.config.dto.SocialUser;
import org.egovframe.cloud.userservice.domain.log.LoginLog;
import org.egovframe.cloud.userservice.domain.log.LoginLogRepository;
import org.egovframe.cloud.userservice.domain.user.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.*;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* org.egovframe.cloud.userservice.service.user.UserService
* <p>
* 사용자 정보 서비스
*
* @author 표준프레임워크센터 jaeyeolkim
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jaeyeolkim 최초 생성
* </pre>
*/
@Slf4j
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class UserService extends AbstractService implements UserDetailsService {
/**
* 구글 클라이언트 ID
*/
@Value("${spring.security.oauth2.client.registration.google.client-id}")
private String GOOGLE_CLIENT_ID;
/**
* 카카오 사용자 정보 URL
*/
@Value("${spring.security.oauth2.client.provider.kakao.user-info-uri}")
private String KAKAO_USER_INFO_URI;
/**
* 네이버 사용자 정보 URL
*/
@Value("${spring.security.oauth2.client.provider.naver.user-info-uri}")
private String NAVER_USER_INFO_URI;
/**
* REST Template
*/
private final RestTemplate restTemplate;
private final UserRepository userRepository;
private final UserFindPasswordRepository userFindPasswordRepository;
private final BCryptPasswordEncoder passwordEncoder;
private final LoginLogRepository loginLogRepository;
/**
* 자바 메일 전송 인터페이스
*/
private final JavaMailSender javaMailSender;
/**
* 조회 조건에 일치하는 사용자 페이지 목록 조회
*
* @param requestDto 요청 DTO
* @param pageable 페이지 정보
* @return Page<UserListResponseDto> 페이지 사용자 목록 응답 DTO
*/
public Page<UserListResponseDto> findPage(RequestDto requestDto, Pageable pageable) {
return userRepository.findPage(requestDto, pageable);
}
/**
* 사용자 등록
*
* @param requestDto
* @return
*/
@Transactional
public Long save(UserSaveRequestDto requestDto) {
return userRepository.save(requestDto.toEntity(passwordEncoder)).getId();
}
/**
* 사용자 수정
*
* @param userId 사용자 id
* @param requestDto 사용자 수정 요청 DTO
* @return String 사용자 id
*/
@Transactional
public String update(String userId, UserUpdateRequestDto requestDto) {
User user = getUserByUserId(userId);
final String password = requestDto.getPassword() != null && !"".equals(requestDto.getPassword())
? passwordEncoder.encode(requestDto.getPassword())
: user.getEncryptedPassword();
user.update(requestDto.getUserName(), requestDto.getEmail(), password,
requestDto.getRoleId(), requestDto.getUserStateCode());
return userId;
}
/**
* 사용자 refresh token 정보를 필드에 입력한다
*
* @param userId
* @param updateRefreshToken
* @return
*/
@Transactional
public String updateRefreshToken(String userId, String updateRefreshToken) {
User user = userRepository.findByUserId(userId)
.orElseThrow(() -> new UsernameNotFoundException("해당 사용자가 없습니다."));
user.updateRefreshToken(updateRefreshToken);
return user.getRoleKey();
}
/**
* 토큰으로 사용자를 찾아 반환한다.
*
* @param refreshToken
* @return
*/
public User findByRefreshToken(String refreshToken) {
return userRepository.findByRefreshToken(refreshToken)
.orElseThrow(() -> new UsernameNotFoundException("해당 사용자가 없습니다."));
}
/**
* 아이디로 사용자를 찾아 반환한다.
*
* @param userId
* @return
*/
public UserResponseDto findByUserId(String userId) {
User user = userRepository.findByUserId(userId)
.orElseThrow(() -> new UsernameNotFoundException("해당 사용자가 없습니다."));
return new UserResponseDto(user);
}
/**
* 이메일로 사용자를 찾아 반환한다.
*
* @param email
* @return
*/
public UserResponseDto findByEmail(String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("해당 사용자가 없습니다."));
return new UserResponseDto(user);
}
/**
* 모든 사용자를 생성일 역순으로 정렬하여 조회하여 List<UserListResponseDto> 형태로 반환한다.
*
* @return
*/
public List<UserListResponseDto> findAllDesc() {
return userRepository.findAll(Sort.by(Sort.Direction.DESC, "createdDate")).stream()
.map(UserListResponseDto::new) // User의 Stream을 map을 통해 UserListResponseDto로 변환한다. 실제로 .map(user -> new UserListResponseDto(user)) 과 같다.
.collect(Collectors.toList());
}
/**
* SecurityConfig > configure > UserDetailsService 메소드에서 호출된다.
* 스프링 시큐리티에 의해 로그인 대상 사용자의 패스워드와 권한 정보를 DB에서 조회하여 UserDetails 를 리턴한다.
*
* @param email
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
log.info("loadUserByUsername! email={}", email);
// 로그인 실패시 이메일 계정을 로그에 남기기 위해 세팅하고 unsuccessfulAuthentication 메소드에서 받아서 로그에 입력한다.
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
request.setAttribute("email", email);
// UsernameNotFoundException 을 던지면 AbstractUserDetailsAuthenticationProvider 에서 BadCredentialsException 으로 처리하기 때문에 IllegalArgumentException 을 발생시켰다.
// 사용자가 없는 것인지 패스워드가 잘못된 것인지 구분하기 위함이다.
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException(getMessage("err.user.notexists")));
log.info("{} 사용자 존재함", user);
if (!UserStateCode.NORMAL.getKey().equals(user.getUserStateCode())) {
throw new IllegalArgumentException(getMessage("err.user.state.cantlogin"));
}
// 로그인 유저의 권한 목록 주입
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(user.getRoleKey()));
if (user.isSocialUser() && user.getEncryptedPassword() == null || "".equals(user.getEncryptedPassword())) { // 소셜 회원이고 비밀번호가 등록되지 않은 경우
return new SocialUser(user.getEmail(), authorities);
} else {
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getEncryptedPassword(), authorities);
}
}
/**
* 로그인 후처리
*
* @param siteId 사이트 id
* @param email 이메일
* @param successAt 성공 여부
* @param failContent 실패 내용
*/
@Transactional
public void loginCallback(Long siteId, String email, Boolean successAt, String failContent) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException(getMessage("err.user.notexists")));
if (Boolean.TRUE.equals(successAt)) {
user.successLogin();
} else {
user.failLogin();
}
// 로그인 로그 입력
loginLogRepository.save(
LoginLog.builder()
.siteId(siteId)
.email(email)
.remoteIp(LogUtil.getUserIp())
.successAt(successAt)
.failContent(failContent)
.build()
);
}
/**
* 이메일 중복 확인
*
* @param email 이메일
* @param userId 사용자 id
* @return Boolean 중복 여부
*/
public Boolean existsEmail(String email, String userId) {
if (email == null || "".equals(email)) {
throw new BusinessMessageException(getMessage("valid.required.format", new Object[]{getMessage("user.email")}));
}
if (userId == null || "".equals(userId)) {
return userRepository.findByEmail(email).isPresent();
} else {
return userRepository.findByEmailAndUserIdNot(email, userId).isPresent();
}
}
/**
* 사용자 회원 가입
*
* @param requestDto 사용자 가입 요청 DTO
* @return Boolean 성공 여부
*/
@Transactional
public Boolean join(UserJoinRequestDto requestDto) {
boolean exists = existsEmail(requestDto.getEmail(), null);
if (exists) {
throw new BusinessMessageException(getMessage("msg.join.email.exists"));
}
userRepository.save(requestDto.toEntity(passwordEncoder));
return true;
}
/**
* 사용자 비밀번호 찾기
*
* @param requestDto 사용자 비밀번호 찾기 등록 요청 DTO
* @return Boolean 메일 전송 여부
*/
@Transactional
public Boolean findPassword(UserFindPasswordSaveRequestDto requestDto) {
final String emailAddr = requestDto.getEmailAddr();
Optional<User> user = userRepository.findByEmailAndUserName(emailAddr, requestDto.getUserName());
if (!user.isPresent()) {
throw new BusinessMessageException(getMessage("err.user.notexists"));
}
User entity = user.get();
// 이메일 전송
try {
final String mainUrl = requestDto.getMainUrl();
final String tokenValue = UUID.randomUUID().toString().replaceAll("-", "");
final String subject = getMessage("email.user.password.title");
//final String text = getMessage("email.user.password.content"); // varchar(2000)
final String text = UserPasswordChangeEmailTemplate.html;
final String userName = entity.getUserName();
final String changePasswordUrl = requestDto.getChangePasswordUrl() + "?token=" + tokenValue;
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setTo(emailAddr);
helper.setSubject(subject);
helper.setText(String.format(text, mainUrl, userName, changePasswordUrl), true); // String.format에서 %를 쓰려면 %%로
log.info("start send change password email: " + emailAddr);
javaMailSender.send(message);
Integer requestNo = userFindPasswordRepository.findNextRequestNo(emailAddr);
UserFindPassword userFindPassword = requestDto.toEntity(requestNo, tokenValue);
userFindPasswordRepository.save(userFindPassword);
log.info("end send change password email - emailAddr: " + emailAddr + ", tokenValue: " + tokenValue);
} catch (MessagingException e) {
e.printStackTrace();
String errorMessage = getMessage("err.user.find.password");
log.error(errorMessage + ": " + e.getMessage());
throw new BusinessMessageException(errorMessage);
} catch (Exception e) {
e.printStackTrace();
String errorMessage = getMessage("err.user.find.password");
log.error(errorMessage + ": " + e.getMessage());
throw new BusinessMessageException(errorMessage);
}
return true;
}
/**
* 사용자 비밀번호 찾기 유효성 확인
*
* @param tokenValue 토큰 값
* @return Boolean 유효 여부
*/
@Transactional
public Boolean validPassword(String tokenValue) {
if (tokenValue == null || "".equals(tokenValue)) {
throw new BusinessMessageException(getMessage("err.invalid.input.value"));
}
Optional<UserFindPassword> userPassword = userFindPasswordRepository.findByTokenValue(tokenValue);
if (userPassword.isPresent()) {
UserFindPassword entity = userPassword.get();
boolean isExpired = LocalDateTime.now().isAfter(entity.getCreatedDate().plusHours(1)); // 1시간 후 만료
if (Boolean.FALSE.equals(entity.getChangeAt()) && !isExpired) return true;
}
return false;
}
/**
* 사용자 비밀번호 찾기 변경
*
* @param requestDto 사용자 비밀번호 수정 요청 DTO
* @return Boolean 수정 여부
*/
@Transactional
public Boolean changePassword(UserFindPasswordUpdateRequestDto requestDto) {
final String tokenValue = requestDto.getTokenValue();
Optional<UserFindPassword> userPassword = userFindPasswordRepository.findByTokenValue(tokenValue);
if (!userPassword.isPresent()) {
throw new BusinessMessageException(getMessage("err.user.change.password"));
}
UserFindPassword entity = userPassword.get();
if (Boolean.TRUE.equals(entity.getChangeAt()) || LocalDateTime.now().isAfter(entity.getCreatedDate().plusHours(1))) { // 1시간 후 만료
throw new BusinessMessageException(getMessage("err.user.change.password"));
}
User user = userRepository.findByEmail(entity.getUserFindPasswordId().getEmailAddr())
.orElseThrow(() -> new UsernameNotFoundException("해당 사용자가 없습니다."));
user.updatePassword(passwordEncoder.encode(requestDto.getPassword())); // 비밀번호 수정
entity.updateChangeAt(Boolean.TRUE); // 변경 완료
return true;
}
/**
* 사용자 비밀번호 변경
*
* @param userId 사용자 id
* @param requestDto 사용자 비밀번호 변경 요청 DTO
* @return Boolean 수정 여부
*/
@Transactional
public Boolean updatePassword(String userId, UserPasswordUpdateRequestDto requestDto) {
User entity = findUserVerify(userId, requestDto);
entity.updatePassword(passwordEncoder.encode(requestDto.getNewPassword())); // 비밀번호 수정
return true;
}
/**
* 사용자 비밀번호 확인
*
* @param userId 사용자 id
* @param password 비밀번호
* @return Boolean 일치 여부
*/
public Boolean matchPassword(String userId, String password) {
try {
findUserVerifyPassword(userId, password);
} catch (BusinessMessageException e) {
return false;
} catch (Exception e) {
return false;
}
return true;
}
/**
* 사용자 id로 조회
*
* @param userId 사용자 id
* @return User 사용자 엔티티
*/
private User getUserByUserId(String userId) {
Optional<User> user = userRepository.findByUserId(userId);
if (!user.isPresent()) {
throw new BusinessMessageException(getMessage("err.user.notexists"));
}
return user.get();
}
/**
* 사용자 조회, 비밀번호 검증
*
* @param userId 사용자 id
* @param password 비밀번호
* @return User 사용자 엔티티
*/
private User findUserVerifyPassword(String userId, String password) {
User entity = getUserByUserId(userId);
if (!passwordEncoder.matches(password, entity.getEncryptedPassword())) { // 소셜 사용자가 아닌 경우 비밀번호 확인
throw new BusinessMessageException(getMessage("err.user.password.notmatch"));
}
return entity;
}
/**
* 사용자 정보 수정
*
* @param userId 사용자 id
* @param requestDto 사용자 정보 수정 요청 DTO
* @return String 사용자 id
*/
@Transactional
public String updateInfo(String userId, UserUpdateInfoRequestDto requestDto) {
User user = findUserVerify(userId, requestDto);
user.updateInfo(requestDto.getUserName(), requestDto.getEmail());
return userId;
}
/**
* 사용자 회원탈퇴
*
* @param userId 사용자 id
* @param requestDto 회원 탈퇴 요청 DTO
* @return User 사용자 엔티티
*/
@Transactional
public Boolean leave(String userId, UserVerifyRequestDto requestDto) {
User entity = findUserVerify(userId, requestDto);
entity.updateUserStateCode(UserStateCode.LEAVE.getKey());
return true;
}
/**
* 사용자 검증 및 조회
*
* @param userId 사용자 id
* @param requestDto 회원 탈퇴 요청 DTO
* @return User 사용자 엔티티
*/
private User findUserVerify(String userId, UserVerifyRequestDto requestDto) {
User user = null;
if ("password".equals(requestDto.getProvider())) {
user = findUserVerifyPassword(userId, requestDto.getPassword());
} else {
user = findSocialUserByToken(requestDto.getProvider(), requestDto.getToken());
if (user == null) {
throw new BusinessMessageException(getMessage("err.user.socail.find"));
}
if (!userId.equals(user.getUserId())) {
throw new BusinessMessageException(getMessage("err.unauthorized"));
}
}
return user;
}
/**
* 사용자 삭제
*
* @param userId 사용자 id
* @return User 사용자 엔티티
*/
@Transactional
public Boolean delete(String userId) {
User user = getUserByUserId(userId);
user.updateUserStateCode(UserStateCode.DELETE.getKey());
return true;
}
/**
* OAuth 사용자 검색
*
* @param requestDto 사용자 로그인 요청 DTO
* @return UserLoginRequestDto 사용자 로그인 요청 DTO
*/
@Transactional
public UserResponseDto loadUserBySocial(UserLoginRequestDto requestDto) {
String[] userInfo = getSocialUserInfo(requestDto.getProvider(), requestDto.getToken());
UserResponseDto userDto = getAndSaveSocialUser(requestDto.getProvider(), userInfo[0], userInfo[1], userInfo[2]);
if (userDto == null) {
throw new BusinessMessageException(getMessage("err.user.join.social"));
}
if (!UserStateCode.NORMAL.getKey().equals(userDto.getUserStateCode())) {
throw new BusinessMessageException(getMessage("err.user.state.cantlogin"));
}
return userDto;
}
/**
* 토큰으로 사용자 엔티티 조회
*
* @param provider 공급자
* @param token 토큰
* @return User 사용자 엔티티
*/
private User findSocialUserByToken(String provider, String token) {
String[] userInfo = getSocialUserInfo(provider, token);
return findSocialUser(provider, userInfo[0]);
}
/**
* 토큰으로 소셜 사용자 정보 조회
*
* @param provider 공급자
* @param token 토큰
* @return String[] 소셜 사용자 정보
*/
private String[] getSocialUserInfo(String provider, String token) {
String[] userInfo = null;
switch (provider) {
case "google":
userInfo = getGoogleUserInfo(token);
break;
case "naver":
userInfo = getNaverUserInfo(token);
break;
case "kakao":
userInfo = getKakaoUserInfo(token);
break;
default:
break;
}
if (userInfo == null) throw new BusinessMessageException(getMessage("err.user.social.get"));
return userInfo;
}
/**
* 구글 사용자 정보 조회
*
* @param token 토큰
* @return String[] 구글 사용자 정보
*/
private String[] getGoogleUserInfo(String token) {
try {
HttpTransport transport = new NetHttpTransport();
GsonFactory gsonFactory = new GsonFactory();
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, gsonFactory)
.setAudience(Collections.singletonList(GOOGLE_CLIENT_ID))
.build();
GoogleIdToken idToken = verifier.verify(token);
GoogleIdToken.Payload payload = idToken.getPayload();
log.info("google oauth2: {}", payload.toString());
return new String[]{
payload.getSubject(),
payload.getEmail(),
(String) payload.get("name")
};
} catch (GeneralSecurityException e) {
throw new BusinessMessageException(getMessage("err.user.social.get"));
} catch (IOException e) {
throw new BusinessMessageException(getMessage("err.user.social.get"));
} catch (Exception e) {
throw new BusinessMessageException(getMessage("err.user.social.get"));
}
}
/**
* 네이버 사용자 정보 조회
*
* @param token 토큰
* @return String[] 네이버 사용자 정보
*/
private String[] getNaverUserInfo(String token) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + token);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.exchange(NAVER_USER_INFO_URI, HttpMethod.GET, request, String.class);
if (response.getBody() != null && !"".equals(response.getBody())) {
JsonElement element = JsonParser.parseString(response.getBody());
JsonObject object = element.getAsJsonObject();
log.info("naver oauth2: {}", object.toString());
if (object.get("resultcode") != null && "00".equals(object.get("resultcode").getAsString())) {
return new String[]{
object.get("response").getAsJsonObject().get("id").getAsString(),
object.get("response").getAsJsonObject().get("email").getAsString(),
object.get("response").getAsJsonObject().get("name").getAsString()
};
}
}
return null;
}
/**
* 카카오 사용자 정보 조회
*
* @param token 토큰
* @return String[] 카카오 사용자 정보
*/
private String[] getKakaoUserInfo(String token) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + token);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
ResponseEntity<String> response = restTemplate.exchange(KAKAO_USER_INFO_URI, HttpMethod.GET, request, String.class);
if (response.getBody() != null && !"".equals(response.getBody())) {
JsonElement element = JsonParser.parseString(response.getBody());
JsonObject object = element.getAsJsonObject();
log.info("kakao oauth2: {}", object.toString());
if (object.get("id") != null && !"".equals(object.get("id").getAsString())) {
return new String[]{
object.get("id").getAsString(),
object.get("kakao_account").getAsJsonObject().get("email").getAsString(),
object.get("kakao_account").getAsJsonObject().get("profile").getAsJsonObject().get("nickname").getAsString()
};
}
}
return null;
}
/**
* 소셜 사용자 엔티티 조회
*
* @param providerCode 공급자 코드
* @param providerId 공급자 id
* @return User 사용자 엔티티
*/
private User findSocialUser(String providerCode, String providerId) {
Optional<User> user;
// 공급자 id로 조회
switch (providerCode) {
case "google":
user = userRepository.findByGoogleId(providerId);
break;
case "kakao":
user = userRepository.findByKakaoId(providerId);
break;
case "naver":
user = userRepository.findByNaverId(providerId);
break;
default:
user = Optional.empty();
break;
}
return user.orElse(null);
}
/**
* 소셜 사용자 엔티티 조회
* 등록되어 있지 않은 경우 사용자 등록
*
* @param providerCode 공급자 코드
* @param providerId 공급자 id
* @param email 이메일
* @param userName 사용자 명
* @return UserLoginRequestDto 사용자 로그인 요청 DTO
*/
private UserResponseDto getAndSaveSocialUser(String providerCode, String providerId, String email, String userName) {
User user = findSocialUser(providerCode, providerId);
// 이메일로 조회
// 공급자에서 동일한 이메일을 사용할 수 있고
// 현재 시스템 구조 상 이메일을 사용자 식별키로 사용하고 있어서 이메일로 사용자를 한번 더 검색한다.
if (user == null) {
user = userRepository.findByEmail(email).orElse(null);
// 공급자 id로 조회되지 않지만 이메일로 조회되는 경우 공급자 id 등록
if (user != null) {
switch (providerCode) {
case "google":
user = user.updateGoogleId(providerId);
break;
case "kakao":
user = user.updateKakaoId(providerId);
break;
case "naver":
user = user.updateNaverId(providerId);
break;
default:
break;
}
}
}
if (user == null) {
// 사용자 등록
final String userId = UUID.randomUUID().toString();
//final String password = makeRandomPassword(); // 임의 비밀번호 생성 시 복호화 불가능
User.UserBuilder userBuilder = User.builder()
.email(email) // 100byte
//.encryptedPassword(passwordEncoder.encode(password)) // 100 byte
.userName(userName)
.userId(userId)
.role(Role.USER)
.userStateCode(UserStateCode.NORMAL.getKey());
switch (providerCode) {
case "google":
user = userBuilder.googleId(providerId).build();
break;
case "kakao":
user = userBuilder.kakaoId(providerId).build();
break;
case "naver":
user = userBuilder.naverId(providerId).build();
break;
default:
break;
}
if (user != null) {
userRepository.save(user);
}
}
return user == null ? null : new UserResponseDto(user);
}
/**
* 임의 비밀번호 10자리 생성
*
* @return String 비밀번호
*/
private String makeRandomPassword() {
char[] terms = new char[]{
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'!', '@', '#', '$', '%', '^', '&', '*', '(', ')'};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
int index = (int) (Math.random() * terms.length);
sb.append(terms[index]);
}
return sb.toString();
}
}

View File

@@ -0,0 +1,42 @@
# oauth2 를 사용하기 위해서는 아래의 TODO 를 등록해야 함
spring:
security:
oauth2:
client:
registration:
# /oauth2/authorization/google
google:
client-id: google_client_id # TODO
client-secret: google_client_secret # TODO
scope: profile,email
# 네이버는 Spring Security를 공식 지원하지 않기 때문에 CommonOAuth2Provider 에서 해주는 값들을 수동으로 입력한다.
# /oauth2/authorization/naver
naver:
client-id: naver_client_id # TODO
client-secret: naver_client_secret # TODO
redirect_uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
authorization_grant_type: authorization_code
scope: name,email,profile_image
client-name: Naver
# /oauth2/authorization/kakao
kakao:
client-id: kakao_client_id # TODO
client-secret: kakao_client_secret # TODO
redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
client-authentication-method: POST
authorization-grant-type: authorization_code
scope: profile_nickname, account_email
client-name: Kakao
provider:
naver:
authorization_uri: https://nid.naver.com/oauth2.0/authorize
token_uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
# 기준이 되는 user_name 의 이름을 네이버에서는 response로 지정해야한다. (네이버 회원 조회시 반환되는 JSON 형태 때문이다)
# response를 user_name으로 지정하고 이후 자바 코드로 response의 id를 user_name으로 지정한다. (스프링 시큐리티에서 하위 필드를 명시할 수 없기 때문)
user_name_attribute: response
kakao:
authorization_uri: https://kauth.kakao.com/oauth/authorize
token_uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user_name_attribute: id

View File

@@ -0,0 +1,66 @@
server:
port: 0 # random port
spring:
application:
name: user-service
profiles:
group:
default: oauth
docker: oauth
cf: oauth
k8s: oauth
jpa:
hibernate:
ddl-auto: none
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL57Dialect
storage_engine: innodb
format_sql: true
default_batch_fetch_size: 1000
jdbc:
batch_size: 1000
order_inserts: true
order_updates: true
show-sql: true
cache:
jcache:
config: classpath:ehcache.xml
# config server actuator
management:
endpoints:
web:
exposure:
include: refresh, health, beans
health:
mail:
enabled: false
# @TODO application-oauth.yml
# spring:
# security:
# oauth2:
# client:
# registration:
# google:
# client-id: @TODO https://console.cloud.google.com
# client-secret: @TODO
# scope: profile,email
# # 네이버는 Spring Security를 공식 지원하지 않기 때문에 CommonOAuth2Provider 에서 해주는 값들을 수동으로 입력한다.
# naver:
# client-id: @TODO https://developers.naver.com/apps/#/register?api=nvlogin
# client-secret: @TODO
# redirect_uri_template: "{baseUrl}/{action}/oauth2/code/{registrationId}"
# authorization_grant_type: authorization_code
# scope: name,email,profile_image
# client-name: Naver
# provider:
# naver:
# authorization_uri: https://nid.naver.com/oauth2.0/authorize
# token_uri: https://nid.naver.com/oauth2.0/token
# user-info-uri: https://openapi.naver.com/v1/nid/me
# # 기준이 되는 user_name 의 이름을 네이버에서는 response로 지정해야한다. (네이버 회원 조회시 반환되는 JSON 형태 때문이다)
# # response를 user_name으로 지정하고 이후 자바 코드로 response의 id를 user_name으로 지정한다. (스프링 시큐리티에서 하위 필드를 명시할 수 없기 때문)
# user_name_attribute: response

View File

@@ -0,0 +1,8 @@
spring:
cloud:
config:
uri: http://localhost:8888
name: user-service # user-service.yml이 있으면 불러오게 된다
# name: config-service # config-service의 application.yml 을 불러오게 된다
# profiles:
# active: prod # application-prod.yml

View File

@@ -0,0 +1,51 @@
<config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd">
<cache alias="cache-user-authorization-by-roles"> <!-- 캐시 이름 -->
<key-type>java.util.List</key-type> <!-- 캐시 키 타입 -->
<value-type>java.util.List</value-type> <!-- 캐시 저장 값 타입 -->
<expiry>
<ttl unit="minutes">3</ttl> <!-- 만료 시간 -->
</expiry>
<listeners>
<listener>
<class>org.egovframe.cloud.userservice.config.CacheEventLogger</class> <!-- 캐시 이벤트 리스너 -->
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap unit="entries">2000</heap> <!-- 힙 사이즈 -->
<offheap unit="MB">1</offheap> <!-- 힙 사이즈가 부족할 경우 디스크 사용 용량 -->
</resources>
</cache>
<cache alias="cache-user-authorization-by-userid"> <!-- 캐시 이름 -->
<key-type>java.lang.String</key-type> <!-- 캐시 키 타입 -->
<value-type>java.util.List</value-type> <!-- 캐시 저장 값 타입 -->
<expiry>
<ttl unit="minutes">3</ttl> <!-- 만료 시간 -->
</expiry>
<listeners>
<listener>
<class>org.egovframe.cloud.userservice.config.CacheEventLogger</class> <!-- 캐시 이벤트 리스너 -->
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap unit="entries">2000</heap> <!-- 힙 사이즈 -->
<offheap unit="MB">1</offheap> <!-- 힙 사이즈가 부족할 경우 디스크 사용 용량 -->
</resources>
</cache>
</config>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %thread %-5level %logger - %m%n</pattern>
</encoder>
</appender>
<!-- 로컬에서는 로그를 전송하지 않도록 설정 -->
<springProfile name="default">
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</springProfile>
<springProfile name="!default">
<!-- java -Ddestination="localhost:8088" 와 같이 변경할 수 있다. cf 환경에서는 manifest.yml 파일에 환경변수로 추가 -->
<property name="destination" value="localhost:8088" />
<property name="app_name" value="${app_name}" />
<!-- ELK - Logstash 로 로그를 전송하기 위한 appender -->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>${destination}</destination><!-- native profile => localhost:8088 -->
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app.name":"${app_name}"}</customFields>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="LOGSTASH" />
<appender-ref ref="STDOUT" />
</root>
</springProfile>
</configuration>

View File

@@ -0,0 +1,13 @@
package org.egovframe.cloud.userservice;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class UserServiceApplicationTests {
@Test
void contextLoads() {
}
}

View File

@@ -0,0 +1,143 @@
package org.egovframe.cloud.userservice.api;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.egovframe.cloud.common.domain.Role;
import org.egovframe.cloud.userservice.api.user.dto.UserResponseDto;
import org.egovframe.cloud.userservice.api.user.dto.UserSaveRequestDto;
import org.egovframe.cloud.userservice.api.user.dto.UserUpdateRequestDto;
import org.egovframe.cloud.userservice.domain.user.User;
import org.egovframe.cloud.userservice.domain.user.UserRepository;
import org.egovframe.cloud.userservice.service.user.UserService;
import org.json.JSONObject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.web.client.RestClientException;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnableConfigurationProperties
@TestPropertySource(properties = {"spring.config.location=classpath:application-test.yml"})
@ActiveProfiles(profiles = "test")
class UserApiControllerTest {
@Autowired
private UserRepository userRepository;
@Autowired
private UserService userService;
@Autowired
private TestRestTemplate restTemplate;
// private static final String USER_SERVICE_URL = "http://localhost:8000/user-service";
private static final String TEST_COM = "@test.com";
private static final String TEST_EMAIL = System.currentTimeMillis() + TEST_COM;
private static final String TEST_PASSWORD = "test1234!";
@Test
@Order(Integer.MAX_VALUE)
public void cleanup() throws Exception {
// 테스트 후 데이터 삭제
List<User> users = userRepository.findByEmailContains("test.com");
users.forEach(user -> userRepository.deleteById(user.getId()));
}
@Test
@Order(Integer.MIN_VALUE)
public void 사용자_등록된다() throws Exception {
// given
UserSaveRequestDto userSaveRequestDto = UserSaveRequestDto.builder()
.userName("사용자")
.email(TEST_EMAIL)
.password(TEST_PASSWORD)
.roleId(Role.USER.getKey())
.userStateCode("01")
.build();
userService.save(userSaveRequestDto);
// when
UserResponseDto findUser = userService.findByEmail(TEST_EMAIL);
// then
assertThat(findUser.getEmail()).isEqualTo(TEST_EMAIL);
}
@Test
@Order(2)
public void 사용자_수정된다() throws Exception {
// given
UserResponseDto findUser = userService.findByEmail(TEST_EMAIL);
UserUpdateRequestDto userUpdateRequestDto = UserUpdateRequestDto.builder()
.userName("사용자수정")
.email(TEST_EMAIL)
.roleId(Role.USER.getKey())
.userStateCode("01")
.build();
// when
userService.update(findUser.getUserId(), userUpdateRequestDto);
UserResponseDto updatedUser = userService.findByEmail(TEST_EMAIL);
// then
assertThat(updatedUser.getUserName()).isEqualTo("사용자수정");
}
@Test
public void 사용자_등록오류() throws Exception {
// given
UserSaveRequestDto userSaveRequestDto = UserSaveRequestDto.builder()
.userName("사용자")
.email("email")
.password("test")
.build();
String url = "/api/v1/users";
RestClientException restClientException = Assertions.assertThrows(RestClientException.class, () -> {
restTemplate.postForEntity(url, userSaveRequestDto, Long.class);
});
System.out.println("restClientException.getMessage() = " + restClientException.getMessage());
}
@Test
public void 사용자_로그인된다() throws Exception {
// given
JSONObject loginJson = new JSONObject();
loginJson.put("email", TEST_EMAIL);
loginJson.put("password", TEST_PASSWORD);
String url = "/login";
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, loginJson.toString(), String.class);
responseEntity.getHeaders().entrySet().forEach(System.out::println);
assertThat(responseEntity.getHeaders().containsKey("access-token")).isTrue();
}
@Test
public void 사용자_로그인_오류발생한다() throws Exception {
// given
JSONObject loginJson = new JSONObject();
loginJson.put("email", TEST_EMAIL);
loginJson.put("password", "test");
String url = "/login";
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, loginJson.toString(), String.class);
System.out.println("responseEntity = " + responseEntity);
assertThat(responseEntity.getHeaders().containsKey("access-token")).isFalse();
}
}

View File

@@ -0,0 +1,403 @@
package org.egovframe.cloud.userservice.api.role;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.egovframe.cloud.userservice.domain.role.Authorization;
import org.egovframe.cloud.userservice.domain.role.AuthorizationRepository;
import org.egovframe.cloud.userservice.domain.role.RoleAuthorization;
import org.egovframe.cloud.userservice.domain.role.RoleAuthorizationRepository;
import org.json.JSONObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* org.egovframe.cloud.userservice.api.role.AuthorizationApiControllerTest
* <p>
* 인가 Rest API 컨트롤러 테스트 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jooho 최초 생성
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnableConfigurationProperties
@TestPropertySource(properties = {"spring.config.location=classpath:application-test.yml"})
@ActiveProfiles(profiles = "test")
class AuthorizationApiControllerTest {
/**
* WebApplicationContext
*/
@Autowired
private WebApplicationContext context;
/**
* MockMvc
*/
private MockMvc mvc;
/**
* ObjectMapper
*/
@Autowired
private ObjectMapper objectMapper;
/**
* test rest template
*/
@Autowired
TestRestTemplate restTemplate;
/**
* 인가 레파지토리 인터페이스
*/
@Autowired
AuthorizationRepository authorizationRepository;
/**
* 권한 인가 레파지토리 인터페이스
*/
@Autowired
RoleAuthorizationRepository roleAuthorizationRepository;
/**
* 인가 API 경로
*/
private static final String URL = "/api/v1/authorizations";
/**
* 테스트 데이터 등록 횟수
*/
private final Integer GIVEN_DATA_COUNT = 10;
/**
* 테스트 데이터
*/
private final String AUTHORIZATION_NAME_PREFIX = "인가 명";
//private final String URL_PATTERN_VALUE_PREFIX = "URL 패턴 값";
private final String URL_PATTERN_VALUE_PREFIX = "/api/v1/authorizations";
//private final String HTTP_METHOD_VALUE_PREFIX = "Http Method 코드";
private final String HTTP_METHOD_VALUE_PREFIX = "GET";
private final String INSERT_AUTHORIZATION_NAME = AUTHORIZATION_NAME_PREFIX + "_1";
private final String INSERT_URL_PATTERN_VALUE = URL_PATTERN_VALUE_PREFIX + "_1";
private final String INSERT_HTTP_METHOD_VALUE = HTTP_METHOD_VALUE_PREFIX + "_1";
private final Integer INSERT_SORT_SEQ = 2;
private final String UPDATE_AUTHORIZATION_NAME = AUTHORIZATION_NAME_PREFIX + "_2";
private final String UPDATE_URL_PATTERN_VALUE = URL_PATTERN_VALUE_PREFIX + "_";
private final String UPDATE_HTTP_METHOD_VALUE = HTTP_METHOD_VALUE_PREFIX + "_";
private final Integer UPDATE_SORT_SEQ = 2;
/**
* 테스트 데이터
*/
private List<Authorization> testDatas = new ArrayList<>();
/**
* 테스트 시작 전 수행
*/
@BeforeEach
void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(context)
.addFilter(new CharacterEncodingFilter("UTF-8"))
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
/**
* 테스트 종료 후 수행
*/
@AfterEach
void tearDown() {
}
/**
* 인가 페이지 목록 조회 테스트
*/
@Test
@WithMockUser(roles = "ADMIN")
void 인가_페이지_목록_조회() throws Exception {
// given
insertTestDatas();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("keywordType", "authorizationName");
params.add("keyword", AUTHORIZATION_NAME_PREFIX);
params.add("page", "0");
params.add("size", "10");
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get(URL)
.params(params));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
// .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.numberOfElements").value(GIVEN_DATA_COUNT))
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].authorizationName").value(AUTHORIZATION_NAME_PREFIX + "_1"));
deleteTestDatas();
}
/**
* 인가 상세 조회 테스트
*/
@Test
@WithMockUser(roles = "ADMIN")
void 인가_상세_조회() throws Exception {
// given
Authorization entity = insertTestData();
final Integer authorizationNo = entity.getAuthorizationNo();
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get(URL + "/" + authorizationNo));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.authorizationNo").value(authorizationNo))
.andExpect(MockMvcResultMatchers.jsonPath("$.authorizationName").value(INSERT_AUTHORIZATION_NAME))
.andExpect(MockMvcResultMatchers.jsonPath("$.urlPatternValue").value(INSERT_URL_PATTERN_VALUE))
.andExpect(MockMvcResultMatchers.jsonPath("$.httpMethodCode").value(INSERT_HTTP_METHOD_VALUE))
.andExpect(MockMvcResultMatchers.jsonPath("$.sortSeq").value(INSERT_SORT_SEQ));
deleteTestData(authorizationNo);
}
/**
* 인가 다음 정렬 순서 조회
*/
@Test
@WithMockUser(roles = "ADMIN")
void 인가_다음정렬순서_조회() throws Exception {
// given
insertTestDatas();
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get(URL + "/sort-seq/next"));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
// .andExpect(MockMvcResultMatchers.content().string("11"));
.andExpect(MockMvcResultMatchers.content().string("129")); // /src/test/resources/h2/data.sql 초기화 데이터의 마지막 순번 + 1
deleteTestDatas();
}
/**
* 인가 등록 테스트
*/
@Test
@WithMockUser(roles = "ADMIN")
void 인가_등록() throws Exception {
// given
Map<String, Object> params = new HashMap<>();
params.put("authorizationName", INSERT_AUTHORIZATION_NAME);
params.put("urlPatternValue", INSERT_URL_PATTERN_VALUE);
params.put("httpMethodCode", INSERT_HTTP_METHOD_VALUE);
params.put("sortSeq", INSERT_SORT_SEQ);
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.post(URL)
.accept(MediaType.APPLICATION_JSON)
.contentType("application/json;charset=UTF-8")
.content(objectMapper.writeValueAsString(params)));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk());
String responseData = resultActions.andReturn().getResponse().getContentAsString();
JSONObject jsonObject = new JSONObject(responseData);
final Integer authorizationNo = Integer.parseInt(jsonObject.get("authorizationNo").toString());
Optional<Authorization> authorization = selectData(authorizationNo);
assertThat(authorization).isPresent();
Authorization entity = authorization.get();
assertThat(entity.getAuthorizationNo()).isEqualTo(authorizationNo);
assertThat(entity.getAuthorizationName()).isEqualTo(INSERT_AUTHORIZATION_NAME);
assertThat(entity.getUrlPatternValue()).isEqualTo(INSERT_URL_PATTERN_VALUE);
assertThat(entity.getHttpMethodCode()).isEqualTo(INSERT_HTTP_METHOD_VALUE);
assertThat(entity.getSortSeq()).isEqualTo(INSERT_SORT_SEQ);
deleteTestData(authorizationNo);
}
/**
* 인가 수정 테스트
*/
@Test
@WithMockUser(roles = "ADMIN")
void 인가_수정() throws Exception {
// given
Authorization entity = insertTestData();
final Integer authorizationNo = entity.getAuthorizationNo();
Map<String, Object> params = new HashMap<>();
params.put("authorizationName", UPDATE_AUTHORIZATION_NAME);
params.put("urlPatternValue", UPDATE_URL_PATTERN_VALUE);
params.put("httpMethodCode", UPDATE_HTTP_METHOD_VALUE);
params.put("sortSeq", UPDATE_SORT_SEQ);
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.put(URL + "/" + authorizationNo)
.accept(MediaType.APPLICATION_JSON)
.contentType("application/json;charset=UTF-8")
.content(objectMapper.writeValueAsString(params)));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk());
Optional<Authorization> authorization = selectData(authorizationNo);
assertThat(authorization).isPresent();
Authorization updatedAuthorization = authorization.get();
assertThat(updatedAuthorization.getAuthorizationNo()).isEqualTo(authorizationNo);
assertThat(updatedAuthorization.getAuthorizationName()).isEqualTo(UPDATE_AUTHORIZATION_NAME);
assertThat(updatedAuthorization.getUrlPatternValue()).isEqualTo(UPDATE_URL_PATTERN_VALUE);
assertThat(updatedAuthorization.getHttpMethodCode()).isEqualTo(UPDATE_HTTP_METHOD_VALUE);
assertThat(updatedAuthorization.getSortSeq()).isEqualTo(UPDATE_SORT_SEQ);
deleteTestData(authorizationNo);
}
/**
* 인가 삭제 테스트
*/
@Test
@WithMockUser(roles = "ADMIN")
void 인가_삭제() throws Exception {
// given
Authorization entity = insertTestData();
final Integer authorizationNo = entity.getAuthorizationNo();
// 권한 인가 2건 등록 후 같이 삭제
roleAuthorizationRepository.save(RoleAuthorization.builder()
.roleId("ROLE_1")
.authorizationNo(authorizationNo)
.build());
roleAuthorizationRepository.save(RoleAuthorization.builder()
.roleId("ROLE_2")
.authorizationNo(authorizationNo)
.build());
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.delete(URL + "/" + authorizationNo));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk());
Optional<Authorization> authorization = selectData(authorizationNo);
assertThat(authorization).isNotPresent();
}
/**
* 테스트 데이터 등록
*/
private void insertTestDatas() {
for (int i = 1; i <= GIVEN_DATA_COUNT; i++) {
testDatas.add(authorizationRepository.save(Authorization.builder()
.authorizationName(AUTHORIZATION_NAME_PREFIX + "_" + i)
.urlPatternValue(URL_PATTERN_VALUE_PREFIX + "_" + i)
.httpMethodCode(HTTP_METHOD_VALUE_PREFIX + "_" + i)
.sortSeq(i)
.build()));
}
}
/**
* 테스트 데이터 삭제
*/
private void deleteTestDatas() {
if (testDatas != null) {
if (!testDatas.isEmpty()) authorizationRepository.deleteAll(testDatas);
testDatas.clear();
}
}
/**
* 테스트 데이터 단건 등록
*
* @return Authorization 인가 엔티티
*/
private Authorization insertTestData() {
return authorizationRepository.save(Authorization.builder()
.authorizationName(INSERT_AUTHORIZATION_NAME)
.urlPatternValue(INSERT_URL_PATTERN_VALUE)
.httpMethodCode(INSERT_HTTP_METHOD_VALUE)
.sortSeq(INSERT_SORT_SEQ)
.build());
}
/**
* 테스트 데이터 단건 삭제
*/
private void deleteTestData(Integer authorizationNo) {
authorizationRepository.deleteById(authorizationNo);
}
/**
* 테스트 데이터 단건 조회
*
* @param authorizationNo 인가 번호
* @return Optional<Authorization> 인가 엔티티
*/
private Optional<Authorization> selectData(Integer authorizationNo) {
return authorizationRepository.findById(authorizationNo);
}
}

View File

@@ -0,0 +1,182 @@
package org.egovframe.cloud.userservice.api.role;
import java.util.ArrayList;
import java.util.List;
import org.egovframe.cloud.userservice.domain.role.Role;
import org.egovframe.cloud.userservice.domain.role.RoleRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
/**
* org.egovframe.cloud.userservice.api.role.RoleApiControllerTest
* <p>
* 권한 Rest API 컨트롤러 테스트 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/07
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/07 jooho 최초 생성
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnableConfigurationProperties
@TestPropertySource(properties = {"spring.config.location=classpath:application-test.yml"})
@ActiveProfiles(profiles = "test")
class RoleApiControllerTest {
/**
* WebApplicationContext
*/
@Autowired
private WebApplicationContext context;
/**
* MockMvc
*/
private MockMvc mvc;
/**
* 권한 레파지토리 인터페이스
*/
@Autowired
private RoleRepository roleRepository;
/**
* 권한 API 경로
*/
private static final String URL = "/api/v1/roles";
private static final Integer GIVEN_DATA_COUNT = 4;
private static final String ROLE_ID_PREFIX = "_ROLE_";
private static final String ROLE_NAME_PREFIX = "권한 명 테스트";
private static final String ROLE_CONTENT_PREFIX = "권한 내용";
/**
* 테스트 데이터
*/
private List<Role> testDatas = new ArrayList<>();
/**
* 테스트 시작 전
*/
@BeforeEach
void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(context)
.addFilter(new CharacterEncodingFilter("UTF-8"))
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
/**
* 테스트 종료 후
*/
@BeforeEach
void tearDown() {
}
/**
* 권한 페이지 목록 조회 테스트
*/
@Test
@WithMockUser(roles = "ADMIN")
void 권한_페이지_목록_조회() throws Exception {
// given
insertTestDatas();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("keywordType", "roleName");
params.add("keyword", ROLE_NAME_PREFIX);
params.add("page", "0");
params.add("size", "10");
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get(URL)
.params(params));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.numberOfElements").value(GIVEN_DATA_COUNT))
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].roleId").value(ROLE_ID_PREFIX + "_1"));
deleteTestDatas();
}
/**
* 권한 전체 목록 조회 테스트
*/
@Test
@WithMockUser(roles = "ADMIN")
void 권한_전체_목록_조회() throws Exception {
// given
insertTestDatas();
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get(URL + "/all"));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$[0].roleId").value(ROLE_ID_PREFIX + "_1"));
deleteTestDatas();
}
/**
* 테스트 데이터 등록
*/
private void insertTestDatas() {
for (int i = 1; i <= GIVEN_DATA_COUNT; i++) {
String roleId = ROLE_ID_PREFIX + "_" + i;
String roleName = ROLE_NAME_PREFIX + "_" + i;
String roleContent = ROLE_CONTENT_PREFIX + "_" + i;
testDatas.add(roleRepository.save(Role.builder()
.roleId(roleId)
.roleName(roleName)
.roleContent(roleContent)
.sortSeq(i)
.build()));
}
}
/**
* 테스트 데이터 삭제
*/
private void deleteTestDatas() {
roleRepository.deleteAll(testDatas);
testDatas.clear();
}
}

View File

@@ -0,0 +1,370 @@
package org.egovframe.cloud.userservice.api.role;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.assertj.core.api.Condition;
import org.egovframe.cloud.userservice.domain.role.Authorization;
import org.egovframe.cloud.userservice.domain.role.AuthorizationRepository;
import org.egovframe.cloud.userservice.domain.role.Role;
import org.egovframe.cloud.userservice.domain.role.RoleAuthorization;
import org.egovframe.cloud.userservice.domain.role.RoleAuthorizationId;
import org.egovframe.cloud.userservice.domain.role.RoleAuthorizationRepository;
import org.egovframe.cloud.userservice.domain.role.RoleRepository;
import org.json.JSONArray;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* org.egovframe.cloud.userservice.api.role.RoleAuthorizationApiControllerTest
* <p>
* 권한 인가 Rest API 컨트롤러 테스트 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/12
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/12 jooho 최초 생성
* </pre>
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnableConfigurationProperties
@TestPropertySource(properties = {"spring.config.location=classpath:application-test.yml"})
@ActiveProfiles(profiles = "test")
class RoleAuthorizationApiControllerTest {
/**
* WebApplicationContext
*/
@Autowired
private WebApplicationContext context;
/**
* MockMvc
*/
private MockMvc mvc;
/**
* ObjectMapper
*/
@Autowired
private ObjectMapper objectMapper;
/**
* 권한 레파지토리 인터페이스
*/
@Autowired
RoleRepository roleRepository;
/**
* 인가 레파지토리 인터페이스
*/
@Autowired
AuthorizationRepository authorizationRepository;
/**
* 권한 인가 레파지토리 인터페이스
*/
@Autowired
RoleAuthorizationRepository roleAuthorizationRepository;
/**
* 인가 API 경로
*/
private static final String URL = "/api/v1/role-authorizations";
/**
* 테스트 데이터 등록 횟수
*/
private final Integer GIVEN_AUTHORIZATION_COUNT = 5;
private final String ROLE_ID = "_ROLE_1";
private final String ROLE_NAME = "권한 명_1";
private final String ROLE_CONTENT = "권한 내용_1";
private final String AUTHORIZATION_NAME_PREFIX = "인가 명";
private final String URL_PATTERN_VALUE_PREFIX = "/api/v1/test";
private final String HTTP_METHOD_VALUE_PREFIX = "GET";
/**
* 테스트 데이터
*/
private Role role = null;
private final List<Authorization> authorizations = new ArrayList<>();
private final List<RoleAuthorization> testDatas = new ArrayList<>();
/**
* 테스트 시작 전 수행
*/
@BeforeEach
void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(context)
.addFilter(new CharacterEncodingFilter("UTF-8"))
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
// 권한 등록
role = roleRepository.save(Role.builder()
.roleId(ROLE_ID)
.roleName(ROLE_NAME)
.roleContent(ROLE_CONTENT)
.sortSeq(1)
.build());
// 인가 등록
for (int i = 1; i <= GIVEN_AUTHORIZATION_COUNT; i++) {
authorizations.add(authorizationRepository.save(Authorization.builder()
.authorizationName(AUTHORIZATION_NAME_PREFIX + "_" + i)
.urlPatternValue(URL_PATTERN_VALUE_PREFIX + "_" + i)
.httpMethodCode(HTTP_METHOD_VALUE_PREFIX + "_" + i)
.sortSeq(i)
.build()));
}
}
/**
* 테스트 종료 후 수행
*/
@AfterEach
void tearDown() {
// 인가 삭제
authorizationRepository.deleteAll(authorizations);
authorizations.clear();
// 권한 삭제
roleRepository.delete(role);
}
/**
* 권한 인가 페이지 목록 조회
*/
@Test
@WithMockUser(roles = "ADMIN")
void 권한_인가_페이지_목록_조회() throws Exception {
// given
insertTestDatas();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("roleId", role.getRoleId());
params.add("keywordType", "urlPatternValue");
params.add("keyword", URL_PATTERN_VALUE_PREFIX);
params.add("page", "0");
params.add("size", "10");
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.get(URL)
.params(params));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.numberOfElements").value(authorizations.size()))
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].roleId").value(role.getRoleId()))
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].authorizationNo").value(authorizations.get(0).getAuthorizationNo()))
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].createdAt").value(true));
deleteTestDatas();
}
/**
* 권한 인가 다건 등록
*/
@Test
@WithMockUser(roles = "ADMIN")
void 권한_인가_다건_등록() throws Exception {
// given
List<Map<String, Object>> requestDtoList = new ArrayList<>();
for (int i = 1; i <= authorizations.size(); i++) {
if (i % 2 == 0) continue; //홀수만 등록
Map<String, Object> params = new HashMap<>();
params.put("roleId", role.getRoleId());
params.put("authorizationNo", authorizations.get(i - 1).getAuthorizationNo());
requestDtoList.add(params);
}
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.post(URL)
.accept(MediaType.APPLICATION_JSON)
.contentType("application/json;charset=UTF-8")
.content(objectMapper.writeValueAsString(requestDtoList)));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk());
String responseData = resultActions.andReturn().getResponse().getContentAsString();
JSONArray jsonArray = new JSONArray(responseData);
assertThat(jsonArray.length()).isEqualTo(requestDtoList.size());
List<RoleAuthorization> entityList = roleAuthorizationRepository.findAll(Sort.by(Sort.Direction.ASC, "roleAuthorizationId.authorizationNo"));
for (int i = entityList.size() - 1; i >= 0; i--) {
if (!entityList.get(i).getRoleAuthorizationId().getRoleId().equals(role.getRoleId())) {
entityList.remove(i);
}
}
assertThat(entityList).isNotNull();
assertThat(entityList.size()).isEqualTo(requestDtoList.size());
assertThat(entityList)
.isNotEmpty()
.has(new Condition<>(l -> l.get(0).getRoleAuthorizationId().getRoleId().equals(role.getRoleId()) && l.get(0).getRoleAuthorizationId().getAuthorizationNo().compareTo(authorizations.get(0).getAuthorizationNo()) == 0,
"RoleAuthorizationApiControllerTest.saveList authorizationNo eq 1"))
.has(new Condition<>(l -> l.get(1).getRoleAuthorizationId().getRoleId().equals(role.getRoleId()) && l.get(1).getRoleAuthorizationId().getAuthorizationNo().compareTo(authorizations.get(2).getAuthorizationNo()) == 0,
"RoleAuthorizationApiControllerTest.saveList authorizationNo eq 3"));
for (int i = entityList.size() - 1; i >= 0; i--) {
deleteTestData(entityList.get(i).getRoleAuthorizationId().getRoleId(), entityList.get(i).getRoleAuthorizationId().getAuthorizationNo());
}
}
/**
* 권한 인가 다건 삭제
*/
@Test
@WithMockUser(roles = "ADMIN")
void 권한_인가_다건_삭제() throws Exception {
// given
insertTestDatas();
List<Map<String, Object>> requestDtoList = new ArrayList<>();
for (RoleAuthorization testData : testDatas) {
Map<String, Object> params = new HashMap<>();
params.put("roleId", testData.getRoleAuthorizationId().getRoleId());
params.put("authorizationNo", testData.getRoleAuthorizationId().getAuthorizationNo());
requestDtoList.add(params);
}
// when
ResultActions resultActions = mvc.perform(MockMvcRequestBuilders.put(URL)
.accept(MediaType.APPLICATION_JSON)
.contentType("application/json;charset=UTF-8")
.content(objectMapper.writeValueAsString(requestDtoList)));
// then
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk());
List<RoleAuthorization> entityList = roleAuthorizationRepository.findAll(Sort.by(Sort.Direction.ASC, "roleAuthorizationId.authorizationNo"));
for (int i = entityList.size() - 1; i >= 0; i--) {
if (!entityList.get(i).getRoleAuthorizationId().getRoleId().equals(role.getRoleId())) {
entityList.remove(i);
}
}
assertThat(entityList).isNotNull();
assertThat(entityList.size()).isZero();
}
/**
* 권한 인가 레파지토리 등록/조회 테스트
*/
@Test
@Disabled
void 권한_인가_등록_조회() {
// given
final Integer authorizationNo = authorizations.get(0).getAuthorizationNo();
roleAuthorizationRepository.save(RoleAuthorization.builder()
.roleId(role.getRoleId())
.authorizationNo(authorizationNo)
.build());
// when
Optional<RoleAuthorization> roleAuthorization = selectData(role.getRoleId(), authorizationNo);
// then
assertThat(roleAuthorization).isPresent();
RoleAuthorization entity = roleAuthorization.get();
assertThat(entity.getRoleAuthorizationId().getRoleId()).isEqualTo(role.getRoleId());
assertThat(entity.getRoleAuthorizationId().getAuthorizationNo()).isEqualTo(authorizationNo);
}
/**
* 테스트 데이터 등록
*/
private void insertTestDatas() {
// 권한 인가 등록
for (int i = 1; i <= authorizations.size(); i++) {
if (i % 2 == 0) continue; //인가 번호 홀수만 등록
testDatas.add(roleAuthorizationRepository.save(RoleAuthorization.builder()
.roleId(role.getRoleId())
.authorizationNo(authorizations.get(i - 1).getAuthorizationNo())
.build()));
}
}
/**
* 테스트 데이터 삭제
*/
private void deleteTestDatas() {
// 권한 인가 삭제
roleAuthorizationRepository.deleteAll(testDatas);
testDatas.clear();
}
/**
* 테스트 데이터 단건 삭제
*/
private void deleteTestData(String roleId, Integer authorizationNo) {
roleAuthorizationRepository.deleteById(RoleAuthorizationId.builder()
.roleId(roleId)
.authorizationNo(authorizationNo)
.build());
}
/**
* 테스트 데이터 단건 조회
*
* @param roleId 권한 id
* @param authorizationNo 인가 번호
* @return Optional<RoleAuthorization> 권한 인가 엔티티
*/
private Optional<RoleAuthorization> selectData(String roleId, Integer authorizationNo) {
return roleAuthorizationRepository.findById(RoleAuthorizationId.builder()
.roleId(roleId)
.authorizationNo(authorizationNo)
.build());
}
}

View File

@@ -0,0 +1,28 @@
package org.egovframe.cloud.userservice.config;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.MessageSource;
import java.util.Locale;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@SpringBootTest(webEnvironment = RANDOM_PORT)
class MessageSourceConfigTest {
@Autowired
TestRestTemplate restTemplate;
@Test
public void 메세지를_외부위치에서_읽어온다() throws Exception {
// when
String message = restTemplate.getForObject("http://localhost:8000/user-service/api/v1/messages/common.login/ko", String.class);
// then
assertThat(message).isEqualTo("로그인");
}
}

View File

@@ -0,0 +1,87 @@
package org.egovframe.cloud.userservice.util;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import java.util.Collections;
import java.util.List;
/**
* org.egovframe.cloud.userservice.util.RestResponsePage
* <p>
* 페이지 API 조회 시 JSON 형식의 응답 데이터를 페이지 객체를 구현하여 마이그레이션 해주는 클래스
*
* @author 표준프레임워크센터 jooho
* @version 1.0
* @since 2021/07/08
*
* <pre>
* << 개정이력(Modification Information) >>
*
* 수정일 수정자 수정내용
* ---------- -------- ---------------------------
* 2021/07/08 jooho 최초 생성
* </pre>
*/
public class RestResponsePage<T> extends PageImpl<T> {
/**
* Rest 응답 페이지 생성자
*
* @param content 목록
* @param number 페이지 번호
* @param size 조회할 데이터 수
* @param totalElements 총 데이터 수
* @param pageable 페이지 정보
* @param last 마지막
* @param totalPages 총 페이지
* @param sort 정렬
* @param first 처음
* @param numberOfElements 조회된 데이터 수
*/
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public RestResponsePage(@JsonProperty("content") List<T> content,
@JsonProperty("number") int number,
@JsonProperty("size") int size,
@JsonProperty("totalElements") Long totalElements,
@JsonProperty("pageable") JsonNode pageable,
@JsonProperty("last") boolean last,
@JsonProperty("totalPages") int totalPages,
@JsonProperty("sort") JsonNode sort,
@JsonProperty("first") boolean first,
@JsonProperty("numberOfElements") int numberOfElements) {
super(content, PageRequest.of(number, size), totalElements);
}
/**
* Rest 응답 페이지 생성자
*
* @param content 목록
* @param pageable 페이지 정보
* @param total 총 데이터 수
*/
public RestResponsePage(List<T> content, Pageable pageable, long total) {
super(content, pageable, total);
}
/**
* Rest 응답 페이지 생성자
*
* @param content 목록
*/
public RestResponsePage(List<T> content) {
super(content);
}
/**
* Rest 응답 페이지 생성자
*/
public RestResponsePage() {
super(Collections.emptyList());
}
}

View File

@@ -0,0 +1,107 @@
spring:
application:
name: user-service
datasource:
url: jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false
username: sa
password:
driver-class-name: org.h2.Driver
initialization-mode: always
# schema: classpath:h2/schema.sql
data: classpath:h2/data.sql
jpa:
hibernate:
generate-ddl: true
ddl-auto: create-drop
dialect: org.hibernate.dialect.MySQL5Dialect
properties:
hibernate:
format_sql: true
default_batch_fetch_size: 1000
show-sql: true
h2:
console:
enabled: true
path: /h2
cache:
jcache:
config: classpath:ehcache.xml
mail: # 비밀번호 변경 이메일 발송
host: smtp.gmail.com # smtp host
port: 587 # smtp port
username: email_username # 계정
password: 'email_password' # 비밀번호 - 구글 보안 2단계 인증 해제, 보안 수준이 낮은 앱의 액세스 허용(https://myaccount.google.com/lesssecureapps)
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
security:
# oauth2 를 사용하려면 아래 google, naver, kakao 의 client-id, client-secret 을 발급받아야 한다.
oauth2:
client:
registration:
# /oauth2/authorization/google
google:
client-id: google_client_id # TODO
client-secret: google_client_secret # TODO
scope: profile,email
# 네이버는 Spring Security를 공식 지원하지 않기 때문에 CommonOAuth2Provider 에서 해주는 값들을 수동으로 입력한다.
# /oauth2/authorization/naver
naver:
client-id: naver_client_id # TODO
client-secret: naver_client_secret # TODO
redirect_uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
authorization_grant_type: authorization_code
scope: name,email,profile_image
client-name: Naver
# /oauth2/authorization/kakao
kakao:
client-id: kakao_client_id # TODO
client-secret: kakao_client_secret # TODO
redirect-uri: "{baseUrl}/{action}/oauth2/code/{registrationId}"
client-authentication-method: POST
authorization-grant-type: authorization_code
scope: profile_nickname, account_email
client-name: Kakao
provider:
naver:
authorization_uri: https://nid.naver.com/oauth2.0/authorize
token_uri: https://nid.naver.com/oauth2.0/token
user-info-uri: https://openapi.naver.com/v1/nid/me
# 기준이 되는 user_name 의 이름을 네이버에서는 response로 지정해야한다. (네이버 회원 조회시 반환되는 JSON 형태 때문이다)
# response를 user_name으로 지정하고 이후 자바 코드로 response의 id를 user_name으로 지정한다. (스프링 시큐리티에서 하위 필드를 명시할 수 없기 때문)
user_name_attribute: response
kakao:
authorization_uri: https://kauth.kakao.com/oauth/authorize
token_uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user_name_attribute: id
logging.level:
org.hibernate.SQL: debug
org.hibernate.type: trace
file:
directory: ${user.home}/msa-attach-volume
messages:
directory: ${file.directory}/messages
# jwt token
token:
expiration_time: 7200000
refresh_time: 86400000
secret: egovframe_token_secret
# ftp server
ftp:
enabled: false # ftp 사용 여부, FTP 서버에 최상위 디렉토리 자동 생성 및 구현체를 결정하게 된다.
# eureka 가 포함되면 eureka server 도 등록되므로 해제한다.
eureka:
client:
register-with-eureka: false
fetch-registry: false

View File

@@ -0,0 +1,38 @@
INSERT INTO `authorization` (authorization_name,url_pattern_value,http_method_code,sort_seq,created_by,created_date,last_modified_by,modified_date) VALUES
('사용자 목록 조회','/user-service/api/v1/users','GET',101,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 단건 조회','/user-service/api/v1/users/?*','GET',102,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 등록','/user-service/api/v1/users','POST',103,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 수정','/user-service/api/v1/users/?*','PUT',104,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 토큰 갱신','/user-service/api/v1/users/token/refresh','GET',105,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('권한 페이지 목록 조회','/user-service/api/v1/roles','GET',106,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('권한 전체 목록 조회','/user-service/api/v1/roles/all','GET',107,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('인가 페이지 목록 조회','/user-service/api/v1/authorizations','GET',108,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('인가 단건 조회','/user-service/api/v1/authorizations/?*','GET',109,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('인가 다음 정렬 순서 조회','/user-service/api/v1/authorizations/sort-seq/next','GET',110,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('인가 등록','/user-service/api/v1/authorizations','POST',111,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('인가 수정','/user-service/api/v1/authorizations/?*','PUT',112,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('인가 삭제','/user-service/api/v1/authorizations/?*','DELETE',113,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('인가 여부 확인','/user-service/api/v1/authorizations/check','GET',114,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('권한 인가 페이지 목록 조회','/user-service/api/v1/role-authorizations','GET',115,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('권한 인가 다건 등록','/user-service/api/v1/role-authorizations','POST',116,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('권한 인가 다건 삭제','/user-service/api/v1/role-authorizations','PUT',117,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 이메일 중복 확인','/user-service/api/v1/users/exists','POST',118,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 회원 가입','/user-service/api/v1/users/join','POST',119,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 비밀번호 찾기','/user-service/api/v1/users/password/find','POST',120,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 비밀번호 찾기 유효성 확인','/user-service/api/v1/users/password/valid/?*','GET',121,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 비밀번호 찾기 변경','/user-service/api/v1/users/password/change','PUT',122,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 비밀번호 변경','/user-service/api/v1/users/password/update','PUT',123,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 비밀번호 확인','/user-service/api/v1/users/password/match','POST',124,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('예약지역 사용여부 토글','/reserve-item-service/api/v1/locations/?*/?*','PUT',125,'87638675-11fa-49e5-9bd1-d2524bf6fa45',now(),'87638675-11fa-49e5-9bd1-d2524bf6fa45',now()),
('사용자 정보 수정','/user-service/api/v1/users/info/?*','PUT',126,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 회원탈퇴','/user-service/api/v1/users/leave','POST',127,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now()),
('사용자 삭제','/user-service/api/v1/users/delete/?*','DELETE',128,'65a00f65-8460-49af-98ec-042977e56f4b',now(),'65a00f65-8460-49af-98ec-042977e56f4b',now());
INSERT INTO `role` (role_id,role_name,role_content,sort_seq,created_date) VALUES
('ROLE_ADMIN','시스템 관리자','시스템 관리자 권한',101,'2021-10-20 13:39:15'),
('ROLE_ANONYMOUS','손님','손님 권한',104,'2021-10-20 13:39:15'),
('ROLE_EMPLOYEE','내부 사용자','내부 사용자 권한',102,'2021-10-20 13:39:15'),
('ROLE_USER','일반 사용자','일반 사용자 권한',103,'2021-10-20 13:39:15');
INSERT INTO role_authorization (role_id,authorization_no,created_by,created_date)
select 'ROLE_ADMIN', authorization_no, '65a00f65-8460-49af-98ec-042977e56f4b', now() from `authorization`;