From 5e8e150066ec8a01cac0ddf1d1f612a9a1de8c79 Mon Sep 17 00:00:00 2001 From: Lim Jonguk Date: Thu, 10 Feb 2022 18:26:56 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?&=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인증 처리 : token 적용 - 사용자 조회 - 로컬(H2) / 개발(Oracle) 분리 적용 --- .../xit/biz/ctgy/auth/MinPasswordEncoder.java | 69 ++++ .../xit/biz/ctgy/auth/UserMinPrincipal.java | 105 ++++++ .../ctgy/auth/service/IAuthMinService.java | 32 ++ .../auth/service/impl/AuthMinService.java | 312 ++++++++++++++++++ .../impl/CustomMinOAuth2UserService.java | 95 ++++++ .../impl/CustomMinUserDetailsService.java | 25 ++ .../controller/MinCivBoard680Controller.java | 15 +- .../controller/MinUserinfoController.java | 56 ++++ .../ctgy/controller/OAuth2MinController.java | 171 ++++++++++ .../xit/biz/ctgy/dto/LoginMinRequestDto.java | 61 ++++ .../com/xit/biz/ctgy/dto/MinUserinfoDto.java | 66 ++++ .../ctgy/dto/struct/MinUserinfoMapstruct.java | 12 + .../com/xit/biz/ctgy/entity/MinSimsa680.java | 2 +- .../xit/biz/ctgy/entity/MinSimsaUser680.java | 52 ++- .../com/xit/biz/ctgy/entity/MinUserinfo.java | 65 +++- .../repository/IMinUserinfoRepository.java | 18 + .../biz/ctgy/service/IMinUserinfoService.java | 10 + .../service/impl/MinCivBoard680Service.java | 10 +- .../ctgy/service/impl/MinUserinfoService.java | 37 +++ .../com/xit/core/config/SecurityConfig.java | 21 +- .../config/support/SpringDocApiConfig.java | 2 +- ...FrameworkApplicationCommandLineRunner.java | 14 +- src/main/resources/config/application-dev.yml | 26 +- .../resources/config/application-local.yml | 24 +- src/main/resources/logback-spring.xml | 2 +- 25 files changed, 1199 insertions(+), 103 deletions(-) create mode 100644 src/main/java/com/xit/biz/ctgy/auth/MinPasswordEncoder.java create mode 100644 src/main/java/com/xit/biz/ctgy/auth/UserMinPrincipal.java create mode 100644 src/main/java/com/xit/biz/ctgy/auth/service/IAuthMinService.java create mode 100644 src/main/java/com/xit/biz/ctgy/auth/service/impl/AuthMinService.java create mode 100644 src/main/java/com/xit/biz/ctgy/auth/service/impl/CustomMinOAuth2UserService.java create mode 100644 src/main/java/com/xit/biz/ctgy/auth/service/impl/CustomMinUserDetailsService.java create mode 100644 src/main/java/com/xit/biz/ctgy/controller/MinUserinfoController.java create mode 100644 src/main/java/com/xit/biz/ctgy/controller/OAuth2MinController.java create mode 100644 src/main/java/com/xit/biz/ctgy/dto/LoginMinRequestDto.java create mode 100644 src/main/java/com/xit/biz/ctgy/dto/MinUserinfoDto.java create mode 100644 src/main/java/com/xit/biz/ctgy/dto/struct/MinUserinfoMapstruct.java create mode 100644 src/main/java/com/xit/biz/ctgy/repository/IMinUserinfoRepository.java create mode 100644 src/main/java/com/xit/biz/ctgy/service/IMinUserinfoService.java create mode 100644 src/main/java/com/xit/biz/ctgy/service/impl/MinUserinfoService.java diff --git a/src/main/java/com/xit/biz/ctgy/auth/MinPasswordEncoder.java b/src/main/java/com/xit/biz/ctgy/auth/MinPasswordEncoder.java new file mode 100644 index 0000000..9e27056 --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/auth/MinPasswordEncoder.java @@ -0,0 +1,69 @@ +package com.xit.biz.ctgy.auth; + +import com.xit.biz.ctgy.repository.IMinUserinfoRepository; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Objects; + +@Slf4j +@RequiredArgsConstructor +public class MinPasswordEncoder implements PasswordEncoder { + private final Log logger = LogFactory.getLog(getClass()); + + @Setter + private String userid; + private final IMinUserinfoRepository repository; + +// public MinPasswordEncoder(IMinUserinfoRepository repository) { +// this.repository = repository; +// } + + /** + * 비밀번호 암호화 + * @param rawPassword + * @return + */ + @Override + public String encode(CharSequence rawPassword) { + if (rawPassword == null) { + throw new IllegalArgumentException("rawPassword cannot be null"); + } + return repository.queryGetPasswdEncode(rawPassword.toString()); + } + + /** + * 암호화 되지 않은 비밀 번호와 암호화된 비밀번호 일치 여부 비교 + * @param rawPassword + * @param encodedPassword + * @return + */ + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + //return false; + + if (rawPassword == null) { + throw new IllegalArgumentException("rawPassword cannot be null"); + } + if (encodedPassword == null || encodedPassword.length() == 0) { + this.logger.warn("Empty encoded password"); + return false; + } + + return Objects.equals(repository.queryGetPasswd(rawPassword.toString()), encodedPassword); + } + + /** + * 암호화된 비밀번호를 다시 암호화 하고자 할 경우 true return + * @param encodedPassword + * @return + */ + @Override + public boolean upgradeEncoding(String encodedPassword) { + return PasswordEncoder.super.upgradeEncoding(encodedPassword); + } +} diff --git a/src/main/java/com/xit/biz/ctgy/auth/UserMinPrincipal.java b/src/main/java/com/xit/biz/ctgy/auth/UserMinPrincipal.java new file mode 100644 index 0000000..9cf1afc --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/auth/UserMinPrincipal.java @@ -0,0 +1,105 @@ +package com.xit.biz.ctgy.auth; + +import com.xit.biz.ctgy.entity.MinUserinfo; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import com.xit.core.oauth2.oauth.entity.RoleType; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.OidcUserInfo; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +@Getter +@Setter +@AllArgsConstructor +@RequiredArgsConstructor +public class UserMinPrincipal implements OAuth2User, UserDetails, OidcUser { + private final String userId; + private final String password; + private final ProviderType providerType; + private final RoleType roleType; + private final Collection authorities; + private Map attributes; + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getName() { + return userId; + } + + @Override + public String getUsername() { + return userId; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public Map getClaims() { + return null; + } + + @Override + public OidcUserInfo getUserInfo() { + return null; + } + + @Override + public OidcIdToken getIdToken() { + return null; + } + + public static UserMinPrincipal create(MinUserinfo user) { + return new UserMinPrincipal( + user.getUserid(), + user.getPasswd(), + ProviderType.LOCAL, + RoleType.USER, + Collections.singletonList(new SimpleGrantedAuthority(RoleType.USER.getCode())) + ); + } + + public static UserMinPrincipal create(MinUserinfo user, Map attributes) { + UserMinPrincipal userPrincipal = create(user); + userPrincipal.setAttributes(attributes); + + return userPrincipal; + } +} diff --git a/src/main/java/com/xit/biz/ctgy/auth/service/IAuthMinService.java b/src/main/java/com/xit/biz/ctgy/auth/service/IAuthMinService.java new file mode 100644 index 0000000..c0a06f1 --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/auth/service/IAuthMinService.java @@ -0,0 +1,32 @@ +package com.xit.biz.ctgy.auth.service; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.ctgy.dto.LoginMinRequestDto; +import com.xit.core.oauth2.api.dto.LoginRequestDto; +import com.xit.core.oauth2.api.dto.TokenDto; +import com.xit.core.oauth2.api.dto.TokenRequestDto; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.Map; +import java.util.Optional; + +public interface IAuthMinService { + + //TokenDto login(final CmmUserRequestDto cmmUserRequestDto); + TokenDto login(final LoginMinRequestDto loginRequestDto, + HttpServletRequest request, + HttpServletResponse response, + HttpSession session); + + TokenDto reissue(final TokenRequestDto tokenRequestDto, HttpServletRequest request, HttpServletResponse response); + + boolean validationToken(final String accessToken, final boolean isExceptionThrow); + + Map findAccessTokenInfo(final String accessToken); + + Optional findMyUserWithoutAuthorities(); + + Optional findUserWithAuthorities(final String userId); +} diff --git a/src/main/java/com/xit/biz/ctgy/auth/service/impl/AuthMinService.java b/src/main/java/com/xit/biz/ctgy/auth/service/impl/AuthMinService.java new file mode 100644 index 0000000..d07b74c --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/auth/service/impl/AuthMinService.java @@ -0,0 +1,312 @@ +package com.xit.biz.ctgy.auth.service.impl; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.ctgy.auth.service.IAuthMinService; +import com.xit.biz.ctgy.dto.LoginMinRequestDto; +import com.xit.biz.ctgy.entity.MinUserinfo; +import com.xit.biz.ctgy.repository.IMinUserinfoRepository; +import com.xit.core.constant.ErrorCode; +import com.xit.core.constant.XitConstants; +import com.xit.core.exception.TokenAuthException; +import com.xit.core.oauth2.api.dto.TokenDto; +import com.xit.core.oauth2.api.dto.TokenRequestDto; +import com.xit.core.oauth2.api.entity.RefreshToken; +import com.xit.core.oauth2.api.repository.RefreshTokenRepository; +import com.xit.core.oauth2.config.properties.AppProperties; +import com.xit.core.oauth2.oauth.JwtTokenProvider; +import com.xit.core.oauth2.utils.CookieUtil; +import com.xit.core.oauth2.utils.HeaderUtil; +import com.xit.core.util.Checks; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.*; + +@Service +@RequiredArgsConstructor +public class AuthMinService implements IAuthMinService { + @Value("${xit.auth.save.type:header}") + private String authSaveType; + + @Value("${jwt.refresh.save.type:HEADER}") + private String tokenParamType; + + private static final int EXPIRE_CONVERT_SECOND_FROM_DAY = 60 * 60 * 24; + private final static long THREE_DAYS_MILISECONDS = 3 * (1000 * 60 * 60 * 24); + + private final AppProperties appProperties; + private final AuthenticationManager authenticationManager; + private final IMinUserinfoRepository userRepository; + private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenRepository refreshTokenRepository; + + /** + *
+     * 로그인 요청을 처리(Redis에 저장)하고 토큰(Access + Refresh) return
+     *
+     * 1. Login ID, PW 로 인증 정보 객체 UsernamePasswordAuthenticationToken 생성
+     * 2. AuthenticationManager에 authenticate 메소드의 파라미터로 넘겨, 검증 후 Authentication(사용자ID가 들어있다) return
+     *    AuthenticationManager --> 스프링 시큐리티의 실제 인증이 이루어지는 곳
+     *                              authenticate 메소드 하나만 정의되어 있는 인터페이스
+     *                              Builder 에서 UserDetails 의 유저 정보가 서로 일치하는지 검사
+     * 3. 인증정보로 JWT 토큰 생성
+     * 4. Refresh 토큰 저장 : : Redis에 저장
+     * 5. 토큰(Access + Refresh) return
+     *
+     * @see AuthenticationManagerBuilder
+     * @see JwtTokenProvider
+     * 
+ * @param loginRequestDto LoginMinRequestDto + * @return TokenDto + */ + @Override + @Transactional + public TokenDto login(final LoginMinRequestDto loginRequestDto, + HttpServletRequest request, + HttpServletResponse response, + HttpSession session) { + TokenDto tokenDto = null; + Authentication authentication = null; + + // 1. Login ID/PW 를 기반으로 AuthenticationToken 생성 + UsernamePasswordAuthenticationToken authenticationToken = loginRequestDto.toAuthentication(); + + // 2. 실제로 검증 (사용자 비밀번호 체크) 이 이루어지는 부분 + authentication = authenticationManager.authenticate(authenticationToken); + + // Authentication 저장 + if(Objects.equals(authSaveType, XitConstants.AuthSaveType.SECURITY.getCode())){ + // TODO :: SessionCreationPolicy.STATELESS 인 경우 사용 불가 + SecurityContextHolder.getContext().setAuthentication(authentication); + + }else if(Objects.equals(authSaveType, XitConstants.AuthSaveType.SESSION.getCode())){ + session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); + } + + MinUserinfo user = userRepository.findByUserid(loginRequestDto.getUserid()).orElseThrow(() -> new UsernameNotFoundException(loginRequestDto.getUserid() + " -> 사용자를 찾을 수 없습니다.")); + Map infoMap = new HashMap<>(); + infoMap.put("userName", user.getName()); + infoMap.put("userEmail", user.getEmail()); + + RefreshToken savedRefreshToken = refreshTokenRepository.findByKey(authentication.getName()) + .orElse(null); + + // 저장된 refresh token not exists + if (Checks.isNull(savedRefreshToken) || Checks.isNull(savedRefreshToken.getValue())) { + // 없는 경우 새로 등록 + tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap); + refreshTokenRepository.saveAndFlush( + RefreshToken.builder() + .key(loginRequestDto.getUserid()) + .value(tokenDto.getRefreshToken()) + .build() + + ); + + // 저장된 토큰이 있다 + } else { + + // 만료되지 않은 refresh token + if(!jwtTokenProvider.isExpiredToken(savedRefreshToken.getValue())) { + + // refresh token 잔여기간 기준 도달여부 check + Date now = new Date(); + long validTime = jwtTokenProvider.parseClaims(savedRefreshToken.getValue()).getExpiration().getTime() - now.getTime(); + + if (validTime <= THREE_DAYS_MILISECONDS) { + // 새로운 토큰 생성 : access token and refresh token + tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap); + + // DB에 refresh 토큰 업데이트 + savedRefreshToken.updateValue(Objects.requireNonNull(tokenDto.getRefreshToken())); + + }else{ + tokenDto = TokenDto.builder() + .grantType(XitConstants.JwtToken.GRANT_TYPE.getCode()) + .accessToken(jwtTokenProvider.generateJwtAccessToken(authentication, infoMap)) + .refreshToken(null) + .build(); + + refreshTokenRepository.saveAndFlush( + RefreshToken.builder() + .key(loginRequestDto.getUserid()) + .value(tokenDto.getRefreshToken()) + .build() + ); + } + + // refresh token 만료 + }else{ + tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap); + + refreshTokenRepository.saveAndFlush( + RefreshToken.builder() + .key(loginRequestDto.getUserid()) + .value(tokenDto.getRefreshToken()) + .build() + ); + } + } + + // COOKIE 타입의 요청인 경우 COOKIE set + if(Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType)) { + CookieUtil.deleteCookie(request, response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getCode()); + CookieUtil.addCookie(response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getCode(), tokenDto.getRefreshToken(), appProperties.getAuth().getRefreshTokenExpiry() * EXPIRE_CONVERT_SECOND_FROM_DAY); + } + + // 5. 토큰 발급 + return tokenDto; + } + + /** + *
+     * 토큰 재발급 : Redis에 저장
+     * 1. Access 토큰 검증
+     * 2. Access Token 복호화 --> 유저 정보 (Member ID) GET
+     * 3. Refresh Token 만료 여부 검증
+     * 4. 저장소의 Refresh Token GET
+     * 5. 저장소의 Refresh Token == 전달받은 Refresh Token 의 일치 여부 확인
+     * 6. 토큰 생성
+     * 7. Refresh Token update & 저장소 저장
+     * 8. 토큰 return
+     *
+     * @see JwtTokenProvider
+     * 
+ * @param tokenRequestDto TokenRequestDto + * @return TokenDto + */ + @Override + @Transactional + public TokenDto reissue(final TokenRequestDto tokenRequestDto, HttpServletRequest request, HttpServletResponse response) { + TokenDto tokenDto = new TokenDto(); + String sAccessToken = null; + String sRefreshToken = null; + Authentication authentication = null; + + //------------------------------------------------------- + // access token 검증 + //------------------------------------------------------- + // Get access token ------------------------------------- + if(Objects.equals(XitConstants.JwtTokenParamType.DTO.name(), tokenParamType)) + sAccessToken = tokenRequestDto.getAccessToken(); + + else if( + Objects.nonNull(request) && + (Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType) + || Objects.equals(XitConstants.JwtTokenParamType.HEADER.name(), tokenParamType)) + ) + sAccessToken = HeaderUtil.getAccessToken(request); + else + throw new TokenAuthException("Token 전달 형태가 미 정의 되었습니다."); + + // Access token check ------------------------------------- + // 오염된 토큰 인지만 체크 + jwtTokenProvider.validateTokenExcludeExpired(sAccessToken, false, true); + //if(!jwtTokenProvider.validateToken(sAccessToken, false, false)) throw new TokenAuthException(ErrorCode.INVALID_TOKEN); + // expired access token 여부 체크 + // TODO :: 유효기간 만료된 경우에만 재발급 - 테스트를 위해 반대로 발급되도록 함 + if(jwtTokenProvider.isExpiredToken(sAccessToken)) throw new TokenAuthException(ErrorCode.NOT_EXPIRED_TOKEN_YET); + // -------------------------------------------------------- + + authentication = jwtTokenProvider.getAuthentication(sAccessToken); + + //--------------------------------------------------------- + // refresh token 검증 + //--------------------------------------------------------- + // Get refresh token -------------------------------------- + if(Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType)) + sRefreshToken = CookieUtil.getCookie(request, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getCode()) + .map(Cookie::getValue) + .orElse((null)); + else if(Objects.equals(XitConstants.JwtTokenParamType.HEADER.name(), tokenParamType) + || Objects.equals(XitConstants.JwtTokenParamType.DTO.name(), tokenParamType)) + sRefreshToken = tokenRequestDto.getRefreshToken(); + + // Refresh token check ------------------------------------ + jwtTokenProvider.validateTokenExcludeExpired(sRefreshToken, true, true); + + // userId refresh token 으로 DB 확인 + RefreshToken savedRefreshToken = refreshTokenRepository.findByKey(authentication.getName()) + .orElse(null); + if (Checks.isEmpty(savedRefreshToken)) throw new TokenAuthException(ErrorCode.NOT_EXISTS_SAVED_REFRESH_TOKEN); + + if (!Objects.equals(Objects.requireNonNull(savedRefreshToken).getValue(), sRefreshToken)) { + throw new TokenAuthException(ErrorCode.MISMATCH_REFRESH_ACCESS_TOKEN); + } + + // 토큰 부가 정보 set + MinUserinfo user = userRepository.findByUserid(authentication.getName()).orElse(null); + if(Checks.isNull(user)) throw new TokenAuthException(ErrorCode.INVALID_ROLE_TOKEN); + Map infoMap = new HashMap<>(); + infoMap.put("userName", user.getName()); + infoMap.put("userEmail", user.getEmail()); + + // TODO : refresh토큰 발급 기준인 정의 필요 + // refresh 토큰 기간이 3일 이하로 남은 경우, refresh 토큰 갱신 + Date now = new Date(); + long validTime = jwtTokenProvider.parseClaims(sRefreshToken).getExpiration().getTime() - now.getTime(); + + if (validTime <= THREE_DAYS_MILISECONDS) { + // 토큰 생성 : access token and refresh token + tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap); + + // TODO : DB 갱신 확인 필요 + // DB refresh 토큰 업데이트 + RefreshToken newRefreshToken = savedRefreshToken.updateValue(Objects.requireNonNull(tokenDto.getRefreshToken())); + //refreshTokenRepository.saveAndFlush(newRefreshToken); + + if(Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType)) { + CookieUtil.deleteCookie(request, response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getCode()); + CookieUtil.addCookie(response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getCode(), newRefreshToken.getValue(), appProperties.getAuth().getRefreshTokenExpiry() * EXPIRE_CONVERT_SECOND_FROM_DAY); + } + + }else{ + tokenDto = TokenDto.builder() + .grantType(XitConstants.JwtToken.GRANT_TYPE.getCode()) + .accessToken(jwtTokenProvider.generateJwtAccessToken(authentication, infoMap)) + .refreshToken(null) + .build(); + } + return tokenDto; + } + + @Override + @Transactional(readOnly = true) + public boolean validationToken(final String accessToken, final boolean isExceptionThrow) { + return jwtTokenProvider.validateTokenExcludeExpired(accessToken, false, isExceptionThrow); + } + + // TODO :: 적용 필요 + @Override + @Transactional(readOnly = true) + public Map findAccessTokenInfo(String accessToken) { + return jwtTokenProvider.getAccessTokenInfo(accessToken); //authTokenProvider.getAccessTokenInfo(accessToken); + } + + + @Override + @Transactional(readOnly = true) + public Optional findMyUserWithoutAuthorities() { + //cmmUserRepos + return Optional.empty(); //cmmUserRepository.findOneWithAuthorities(SecurityUtil.getCurrentMemberId()); + } + + @Override + @Transactional(readOnly = true) + public Optional findUserWithAuthorities(final String userId) { + return Optional.empty(); //cmmUserRepository.findOneWithAuthorities(userId); + } +} diff --git a/src/main/java/com/xit/biz/ctgy/auth/service/impl/CustomMinOAuth2UserService.java b/src/main/java/com/xit/biz/ctgy/auth/service/impl/CustomMinOAuth2UserService.java new file mode 100644 index 0000000..eed58da --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/auth/service/impl/CustomMinOAuth2UserService.java @@ -0,0 +1,95 @@ +package com.xit.biz.ctgy.auth.service.impl; + +import com.xit.biz.ctgy.auth.UserMinPrincipal; +import com.xit.biz.ctgy.entity.MinUserinfo; +import com.xit.biz.ctgy.repository.IMinUserinfoRepository; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import com.xit.core.oauth2.oauth.info.AbstractOAuth2UserInfo; +import com.xit.core.oauth2.oauth.info.OAuth2UserInfoFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.InternalAuthenticationServiceException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class CustomMinOAuth2UserService extends DefaultOAuth2UserService { + + private final IMinUserinfoRepository userRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User user = super.loadUser(userRequest); + + try { + return this.process(userRequest, user); + } catch (AuthenticationException ex) { + throw ex; + } catch (Exception ex) { + ex.printStackTrace(); + throw new InternalAuthenticationServiceException(ex.getLocalizedMessage(), ex.getCause()); + } + } + + private OAuth2User process(OAuth2UserRequest userRequest, OAuth2User user) { + // TODO :: 적용 여부 파악 필요 + ProviderType providerType = ProviderType.valueOf(userRequest.getClientRegistration().getRegistrationId().toUpperCase()); + + AbstractOAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerType, user.getAttributes()); + MinUserinfo savedUser = null; + Optional optionalCmmUser = userRepository.findByUserid(userInfo.getId()); + + if(optionalCmmUser.isPresent()) { +// savedUser = optionalCmmUser.get(); +// if (Objects.equals(providerType, savedUser.getProviderType())) { +// throw new OAuthProviderMissMatchException( +// "Looks like you're signed up with " + providerType + +// " account. Please use your " + savedUser.getProviderType() + " account to login." +// ); +// } +// updateUser(savedUser, userInfo); + + }else{ + savedUser = createUser(userInfo, providerType); + } + +// if (optionalCmmUser.isEmpty()) { +// if (providerType != savedUser.getProviderType()) { +// throw new OAuthProviderMissMatchException( +// "Looks like you're signed up with " + providerType + +// " account. Please use your " + savedUser.getProviderType() + " account to login." +// ); +// } +// updateUser(savedUser, userInfo); +// } else { +// savedUser = createUser(userInfo, providerType); +// } + + return UserMinPrincipal.create(savedUser, user.getAttributes()); + } + + private MinUserinfo createUser(AbstractOAuth2UserInfo userInfo, ProviderType providerType) { + MinUserinfo user = MinUserinfo.builder() + .userid(userInfo.getId()) + .name(userInfo.getName()) + .email(userInfo.getEmail()) + .build(); + return userRepository.saveAndFlush(user); + } + +// private void updateUser(MinUserinfo user, AbstractOAuth2UserInfo userInfo) { +// if (Objects.nonNull(userInfo.getName()) && !Objects.equals(userInfo.getName(), user.getUserName())) { +// user.setUserName(userInfo.getName()); +// } +// +// if (Objects.nonNull(userInfo.getImageUrl()) && !Objects.equals(userInfo.getImageUrl(), user.getProfileImageUrl())) { +// user.setProfileImageUrl(userInfo.getImageUrl()); +// } +// } +} diff --git a/src/main/java/com/xit/biz/ctgy/auth/service/impl/CustomMinUserDetailsService.java b/src/main/java/com/xit/biz/ctgy/auth/service/impl/CustomMinUserDetailsService.java new file mode 100644 index 0000000..31afddb --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/auth/service/impl/CustomMinUserDetailsService.java @@ -0,0 +1,25 @@ +package com.xit.biz.ctgy.auth.service.impl; + +import com.xit.biz.ctgy.auth.UserMinPrincipal; +import com.xit.biz.ctgy.entity.MinUserinfo; +import com.xit.biz.ctgy.repository.IMinUserinfoRepository; +import com.xit.core.constant.ErrorCode; +import com.xit.core.exception.CustomBaseException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomMinUserDetailsService implements UserDetailsService { + + private final IMinUserinfoRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + MinUserinfo user = userRepository.findByUserid(username).orElseThrow(() -> new CustomBaseException(ErrorCode.USER_NOT_FOUND)); + return UserMinPrincipal.create(user); + } +} diff --git a/src/main/java/com/xit/biz/ctgy/controller/MinCivBoard680Controller.java b/src/main/java/com/xit/biz/ctgy/controller/MinCivBoard680Controller.java index 7dba9ac..94b40c6 100644 --- a/src/main/java/com/xit/biz/ctgy/controller/MinCivBoard680Controller.java +++ b/src/main/java/com/xit/biz/ctgy/controller/MinCivBoard680Controller.java @@ -1,7 +1,5 @@ package com.xit.biz.ctgy.controller; -import com.xit.biz.cmm.dto.CmmUserDto; -import com.xit.biz.cmm.dto.struct.CmmUserMapstruct; import com.xit.biz.ctgy.dto.MinCivBoard680Dto; import com.xit.biz.ctgy.dto.struct.MinCivBoard680Mapstruct; import com.xit.biz.ctgy.service.IMinCivBoard680Service; @@ -23,18 +21,19 @@ import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@Tag(name = "OAuth2LocalController", description = "인증 관리") +@Tag(name = "MinCivBoard680Controller", description = "게시글 관리") @RestController -@RequestMapping("/api/v1/biz/board") +@RequestMapping("/api/v1/ctgy/board") @Validated @RequiredArgsConstructor public class MinCivBoard680Controller { - private final IMinCivBoard680Service minCivBoard680Service; + private final IMinCivBoard680Service service; - private MinCivBoard680Mapstruct minCivBoard680Mapstruct = Mappers.getMapper(MinCivBoard680Mapstruct.class); + private final MinCivBoard680Mapstruct mapstruct = Mappers.getMapper(MinCivBoard680Mapstruct.class); - @Operation(summary = "게시판 목록 조회" , description = "게시판 목록 조회") + // TODO :: 파라메터 정의 필요 + @Operation(summary = "게시글 목록 조회" , description = "게시글 목록 조회") @Parameters({ @Parameter(in = ParameterIn.QUERY, name = "ciTitle", description = "제목", required = false, example = " "), @Parameter(in = ParameterIn.QUERY, name = "ciName", description = "이름", required = false, example = " "), @@ -48,7 +47,7 @@ public class MinCivBoard680Controller { final MinCivBoard680Dto minCivBoard680Dto, @Parameter(hidden = true) final Pageable pageable) { - return RestResponse.of(minCivBoard680Service.findMinCivBoard680s(minCivBoard680Mapstruct.toEntity(minCivBoard680Dto), pageable)); + return RestResponse.of(service.findMinCivBoard680s(mapstruct.toEntity(minCivBoard680Dto), pageable)); } } diff --git a/src/main/java/com/xit/biz/ctgy/controller/MinUserinfoController.java b/src/main/java/com/xit/biz/ctgy/controller/MinUserinfoController.java new file mode 100644 index 0000000..f8e68b7 --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/controller/MinUserinfoController.java @@ -0,0 +1,56 @@ +package com.xit.biz.ctgy.controller; + +import com.xit.biz.ctgy.dto.MinCivBoard680Dto; +import com.xit.biz.ctgy.dto.MinUserinfoDto; +import com.xit.biz.ctgy.dto.struct.MinCivBoard680Mapstruct; +import com.xit.biz.ctgy.dto.struct.MinUserinfoMapstruct; +import com.xit.biz.ctgy.service.IMinCivBoard680Service; +import com.xit.biz.ctgy.service.IMinUserinfoService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.mapstruct.factory.Mappers; +import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "MinUserinfoController", description = "사용자 관리") +@RestController +@RequestMapping("/api/v1/ctgy/user") +@Validated +@RequiredArgsConstructor +public class MinUserinfoController { + + private final IMinUserinfoService service; + + private final MinUserinfoMapstruct mapstruct = Mappers.getMapper(MinUserinfoMapstruct.class); + + // TODO :: 파라메터 정의 필요 + @Operation(summary = "사용자 목록 조회" , description = "사용자 목록 조회") + @Parameters({ + @Parameter(in = ParameterIn.QUERY, name = "userid", description = "사용자ID", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "name", description = "이름", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "page", description = "페이지", required = true, example = "1"), + @Parameter(in = ParameterIn.QUERY, name = "size", description = "페이지당갯수", required = true, example = "10") + }) + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findMinUserinfos( + @Parameter(hidden = true) + @ModelAttribute("minUserinfoDto") + final MinUserinfoDto minUserinfoDto, + @Parameter(hidden = true) + final Pageable pageable) { + return RestResponse.of(service.findMinUserinfos(mapstruct.toEntity(minUserinfoDto), pageable)); + } + +} diff --git a/src/main/java/com/xit/biz/ctgy/controller/OAuth2MinController.java b/src/main/java/com/xit/biz/ctgy/controller/OAuth2MinController.java new file mode 100644 index 0000000..0933238 --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/controller/OAuth2MinController.java @@ -0,0 +1,171 @@ +package com.xit.biz.ctgy.controller; + +import com.xit.biz.ctgy.dto.LoginMinRequestDto; +import com.xit.biz.ctgy.auth.service.IAuthMinService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import com.xit.core.oauth2.api.dto.TokenRequestDto; +import com.xit.core.oauth2.oauth.JwtTokenProvider; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +@Tag(name = "OAuth2MinController", description = "인증 관리") +@RestController +@RequestMapping("/api/v1/ctgy/auth") +@Validated +@RequiredArgsConstructor +public class OAuth2MinController { + + + private final IAuthMinService authMinService; + + + /** + *
+     * 로그인 요청을 처리(Redis에 저장)하고 토큰(Access + Refresh) return
+     *
+     * 1. Login ID, PW 로 인증 정보 객체 UsernamePasswordAuthenticationToken 생성
+     * 2. AuthenticationManager에 authenticate 메소드의 파라미터로 넘겨, 검증 후 Authentication(사용자ID가 들어있다) return
+     *    AuthenticationManager --> 스프링 시큐리티의 실제 인증이 이루어지는 곳
+     *                              authenticate 메소드 하나만 정의되어 있는 인터페이스
+     *                              Builder 에서 UserDetails 의 유저 정보가 서로 일치하는지 검사
+     * 3. 인증정보로 JWT 토큰 생성
+     * 4. Refresh 토큰 저장 : : Redis에 저장
+     * 5. 토큰(Access + Refresh) return
+     * 
+ * @see AuthenticationManagerBuilder + * @see JwtTokenProvider + * + * @param loginRequestDto LoginMinRequestDto + * @param request HttpServletRequest + * @param response HttpServletResponse + * @param session Session + * @return ResponseEntity + */ + @Operation(summary = "login" , description = "login") +// @Parameters({ +// @Parameter(in = ParameterIn.QUERY, name = "providerType", description = "ProviderType", required = true, example = "GOOGLE"), +// @Parameter(in = ParameterIn.QUERY, name = "userId", description = "사용자ID", required = true, example = "minuk926926"), +// @Parameter(in = ParameterIn.QUERY, name = "password", description = "비밀번호", required = true, example = "minuk926926") +// }) + @PostMapping("/login") + public ResponseEntity login( + //@Validated + @Valid + @RequestBody + final LoginMinRequestDto loginRequestDto, + HttpServletRequest request, + HttpServletResponse response, + HttpSession session + //Errors errors + + ) { + + return RestResponse.of( + authMinService.login( + loginRequestDto, + request, + response, + session + ) + ); + + } + + /** + *
+     * JWT token 재발급
+     * access token - header, refresh - TokenRequestDto 에 담아 요청
+     *
+     * 1. access token의 유효기간이 남아 있는 경우 ErrorCode.NOT_EXPIRED_TOKEN_YET
+     * 2. refresh token의 유효기간이 재 발급 기준에 부합된 경우 refresh token도 재발급
+     * 3. 2번 항묵에 미 해당시 refresh token 값은 null로
+     * 
+ * + * @param tokenRequestDto TokenRequestDto + * @param request HttpServletRequest + * @return ResponseEntity IRestResponse + */ + @Operation(summary = "token 재발급 요청(header & DTO 사용)" , description = "token 재발급 :: accessToken - header, refreshToken - dto") + @Parameter(in = ParameterIn.QUERY, name = "refreshToken", description = "refresh token", required = true, example = " ") + @PostMapping("/jwt/reissue") + public ResponseEntity reissueFromHeader( + @Parameter(hidden = true) + @NotNull + @RequestBody + final TokenRequestDto tokenRequestDto, + HttpServletRequest request) { + return RestResponse.of(authMinService.reissue(tokenRequestDto, request, null)); + } + + /** + *
+     * JWT token 재발급
+     * access token - header, refresh - cookie 에 담아 요청
+     *
+     * 1. access token의 유효기간이 남아 있는 경우 ErrorCode.NOT_EXPIRED_TOKEN_YET
+     * 2. refresh token의 유효기간이 재 발급 기준에 부합된 경우 refresh token도 재발급
+     * 3. 2번 항묵에 미 해당시 refresh token 값은 null로
+     * 
+ * + * @param request HttpServletRequest + * @param response HttpServletResponse + * @return ResponseEntity IRestResponse + */ + @Operation(summary = "token 재발급(header & cookie 사용)" , description = "토큰 재발급 :: accessToken - header, refreshToken - cookie") + //@Parameter(in = ParameterIn.HEADER, name = "Authorization", description = "access token", required = true, example = " ") + //@Parameter(in = ParameterIn.COOKIE, name = "refreshToken", description = "refresh token", required = true, example = " ") + @PostMapping("/jwt/reissue/cookie") + public ResponseEntity reissueFromCookie(HttpServletRequest request, HttpServletResponse response) { + return RestResponse.of(authMinService.reissue(null, request, response)); + } + + + + /** + *
+     * JWT token 재발급
+     * access token - TokenRequestDto, refresh - TokenRequestDto 에 담아 요청
+     *
+     * 1. access token의 유효기간이 남아 있는 경우 ErrorCode.NOT_EXPIRED_TOKEN_YET
+     * 2. refresh token의 유효기간이 재 발급 기준에 부합된 경우 refresh token도 재발급
+     * 3. 2번 항묵에 미 해당시 refresh token 값은 null로
+     * 
+ * + * @param tokenRequestDto TokenRequestDto + * @return ResponseEntity IRestResponse + */ + @Operation(summary = "token 재발급(DTO 사용)" , description = "token 재발급 :: accessToken - dto, refreshToken - dto") + @PostMapping("/jwt/reissue/dto") + public ResponseEntity reissueFromParam( + @NotNull + @RequestBody + final TokenRequestDto tokenRequestDto) { + return RestResponse.of(authMinService.reissue(tokenRequestDto, null, null)); + } + + @Operation(summary = "토큰 체크" , description = "access token 체크") + @PostMapping("/jwt/validate") + public ResponseEntity validate(@RequestParam final String accessToken, @RequestParam boolean isExceptionThrow) { + return RestResponse.of(authMinService.validationToken(accessToken, isExceptionThrow)); + } + + @Operation(summary = "토큰 정보 확인" , description = "토큰 정보 확인") + @PostMapping("/jwt/info") + public ResponseEntity findAccessTokenInfo(@RequestParam final String accessToken) { + return RestResponse.of(authMinService.findAccessTokenInfo(accessToken)); + } +} diff --git a/src/main/java/com/xit/biz/ctgy/dto/LoginMinRequestDto.java b/src/main/java/com/xit/biz/ctgy/dto/LoginMinRequestDto.java new file mode 100644 index 0000000..74a7d51 --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/dto/LoginMinRequestDto.java @@ -0,0 +1,61 @@ +package com.xit.biz.ctgy.dto; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import com.xit.core.oauth2.oauth.entity.RoleType; +import com.xit.core.support.valid.Enums; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.crypto.password.PasswordEncoder; + +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +/** + * Validation (@NotBlank) class -> hibernate class 사용 + */ +@Schema(name = "LoginRequestDto", description = "로그인 parameter DTO") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class LoginMinRequestDto { + + /** + * Enum type Validation -> controller에서 처리 + */ + @Schema(required = true, title = "Provider Type", example = "LOCAL", description = "Provider TYPE") + @Enumerated(EnumType.STRING) + @Enums(enumClass = ProviderType.class, ignoreCase = false, message = "{auth.user.pattern.ProviderType}") + private ProviderType providerType; + + @Schema(required = true, title = "사용자ID", example = "xitdev", description = "사용자 ID") + @Pattern(regexp = "[0-9a-zA-z]{6,20}", message = "{auth.user.pattern.id}") + @Size(min = 6, max = 20) + private String userid; + + @Schema(required = true, title = "비밀번호", example = "xitdev", description = "비밀 번호") + @NotNull//(message = "비밀번호는 필수 입니다(6 ~ 20자리)") + @Pattern(regexp = "[0-9a-zA-z!@#$%^&*]{6,20}", message = "{auth.user.pattern.password}") + @Size(min = 6, max = 20) + private String passwd; + + @Builder + public CmmUser toUser(PasswordEncoder passwordEncoder) { + return CmmUser.builder() + .providerType(providerType) + //.providerType(ProviderType.from(providerType)) + .userId(userid) + .password(passwordEncoder.encode(passwd)) + .roleType(RoleType.USER) + .build(); + } + + public UsernamePasswordAuthenticationToken toAuthentication() { + return new UsernamePasswordAuthenticationToken(userid, passwd); + } +} diff --git a/src/main/java/com/xit/biz/ctgy/dto/MinUserinfoDto.java b/src/main/java/com/xit/biz/ctgy/dto/MinUserinfoDto.java new file mode 100644 index 0000000..c2d2a82 --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/dto/MinUserinfoDto.java @@ -0,0 +1,66 @@ +package com.xit.biz.ctgy.dto; + +import com.xit.biz.ctgy.entity.MinSimsaUser680; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Schema(name = "MinUserinfoDto", description = "사용자 DTO") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class MinUserinfoDto implements Serializable { + private static final long SerialVersionUID = 1L; + + @Schema(required = true, title = "민원심사사용자코드", example = " ", description = "Input Description...") + private String userid; + + @Schema(required = true, title = "사용자암호", example = " ", description = "Input Description...") + private String passwd; + + @Schema(required = true, title = "사용자성명", example = " ", description = "Input Description...") + private String name; + + @Schema(required = false, title = "사용자주민번호", example = " ", description = "Input Description...") + private String regnum; + + @Schema(required = false, title = "전화번호", example = " ", description = "Input Description...") + private String mphone; + + @Schema(required = false, title = "이메일", example = " ", description = "Input Description...") + private String email; + + @Schema(required = true, title = "사용구분", example = " ", description = "Input Description...") + private String accesstype; + + @Schema(required = true, title = "단속조", example = " ", description = "Input Description...") + private String team; + + @Schema(required = false, title = "등록일시", example = " ", description = "Input Description...") + private java.sql.Date regdate; + + @Schema(required = false, title = "사용여부", example = " ", description = "Input Description...") + private String isenable; + + @Schema(required = false, title = "구청코드", example = " ", description = "Input Description...") + private String gu; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MinUserinfoDto that = (MinUserinfoDto) o; + return Objects.equals(userid, that.userid); + } + + @Override + public int hashCode() { + return Objects.hash(userid); + } +} diff --git a/src/main/java/com/xit/biz/ctgy/dto/struct/MinUserinfoMapstruct.java b/src/main/java/com/xit/biz/ctgy/dto/struct/MinUserinfoMapstruct.java new file mode 100644 index 0000000..b91f2ce --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/dto/struct/MinUserinfoMapstruct.java @@ -0,0 +1,12 @@ +package com.xit.biz.ctgy.dto.struct; + +import com.xit.biz.ctgy.dto.MinUserinfoDto; +import com.xit.biz.ctgy.entity.MinUserinfo; +import com.xit.core.support.jpa.mapstruct.IMapstruct; +import com.xit.core.support.jpa.mapstruct.MapStructMapperConfig; +import org.mapstruct.Mapper; + +@Mapper(config = MapStructMapperConfig.class) +public interface MinUserinfoMapstruct extends IMapstruct { + +} diff --git a/src/main/java/com/xit/biz/ctgy/entity/MinSimsa680.java b/src/main/java/com/xit/biz/ctgy/entity/MinSimsa680.java index 47d049e..239ea88 100644 --- a/src/main/java/com/xit/biz/ctgy/entity/MinSimsa680.java +++ b/src/main/java/com/xit/biz/ctgy/entity/MinSimsa680.java @@ -38,7 +38,7 @@ public class MinSimsa680 implements Serializable { @OneToMany(targetEntity = MinSimsaUser680.class, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "msu_maincode") - private final Set minSimsaUser680ss = new HashSet<>(); + private final Set minSimsaUser680s = new HashSet<>(); diff --git a/src/main/java/com/xit/biz/ctgy/entity/MinSimsaUser680.java b/src/main/java/com/xit/biz/ctgy/entity/MinSimsaUser680.java index 44d9637..ca70f44 100644 --- a/src/main/java/com/xit/biz/ctgy/entity/MinSimsaUser680.java +++ b/src/main/java/com/xit/biz/ctgy/entity/MinSimsaUser680.java @@ -1,48 +1,36 @@ package com.xit.biz.ctgy.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import javax.persistence.*; import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; @Schema(name = "MinSimsaUser680", description = "민원심사사용자매핑") -@NoArgsConstructor -@Data -@Entity @Table(name = "min_simsa_user680", schema = "", catalog = "") +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder public class MinSimsaUser680 implements Serializable { private static final long SerialVersionUID = 1L; @Id - private Long id; - @Schema(required = true, title = "민원심사사용자매핑코드", example = " ", description = "Input Description...") - @Column(name = "msu_code", nullable = false) + @Column(name = "msu_code", nullable = false, length = 10) private Long msuCode; - - - -// @Schema(required = true, title = "민원코드", example = " ", description = "Input Description...") -// //@Column(name = "MSU_MAINCODE", nullable = false) -// @ManyToOne(fetch = FetchType.LAZY) -// @JoinColumn(table = "MIN_SIMSA680", name = "MSU_MAINCODE", referencedColumnName = "") -//// private Long msuMaincode; -// private MinSimsa680 minSimsa680; - - - - + @Schema(required = true, title = "민원코드", example = " ", description = "Input Description...") + @Column(name = "msu_maincode", length = 10) + private Long msuMaincode; @Schema(required = true, title = "민원심사사용자코드", example = " ", description = "Input Description...") -// @Column(name = "MSU_USERID", nullable = false) - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "userid") -// private String msuUserid; - private MinUserinfo minUserinfo; + @Column(name = "msu_userid", nullable = false, length = 20) + private String msuUserid; @Schema(required = false, title = "결과코드", example = " ", description = "Input Description...") @Column(name = "msu_result", nullable = true) @@ -61,4 +49,14 @@ public class MinSimsaUser680 implements Serializable { private java.sql.Date msuIndate; + + + + + @JsonIgnore + @Setter + @ManyToOne //(fetch = FetchType.LAZY) + //@JoinColumn(name = "msu_userid", nullable = false) + private MinUserinfo minUserinfo; + } diff --git a/src/main/java/com/xit/biz/ctgy/entity/MinUserinfo.java b/src/main/java/com/xit/biz/ctgy/entity/MinUserinfo.java index 4e682dc..073dea5 100644 --- a/src/main/java/com/xit/biz/ctgy/entity/MinUserinfo.java +++ b/src/main/java/com/xit/biz/ctgy/entity/MinUserinfo.java @@ -1,51 +1,56 @@ package com.xit.biz.ctgy.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.*; import javax.persistence.*; import java.io.Serializable; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; @Schema(name = "MinUserinfo", description = "null") -@NoArgsConstructor -@Data -@Entity @Table(name = "min_userinfo", schema = "", catalog = "") +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder public class MinUserinfo implements Serializable { private static final long SerialVersionUID = 1L; @Id @Schema(required = true, title = "민원심사사용자코드", example = " ", description = "Input Description...") - @Column(name = "userid", nullable = false) + @Column(name = "userid", nullable = false, length = 20) private String userid; @Schema(required = true, title = "사용자암호", example = " ", description = "Input Description...") - @Column(name = "passwd", nullable = false) + @Column(name = "passwd", nullable = false, length = 48) private String passwd; @Schema(required = true, title = "사용자성명", example = " ", description = "Input Description...") - @Column(name = "name", nullable = false) + @Column(name = "name", nullable = false, length = 100) private String name; @Schema(required = false, title = "사용자주민번호", example = " ", description = "Input Description...") - @Column(name = "regnum", nullable = true) + @Column(name = "regnum", nullable = true, length = 20) private String regnum; @Schema(required = false, title = "전화번호", example = " ", description = "Input Description...") - @Column(name = "mphone", nullable = true) + @Column(name = "mphone", nullable = true, length = 20) private String mphone; @Schema(required = false, title = "이메일", example = " ", description = "Input Description...") - @Column(name = "email", nullable = true) + @Column(name = "email", nullable = true, length = 20) private String email; @Schema(required = true, title = "사용구분", example = " ", description = "Input Description...") - @Column(name = "accesstype", nullable = true) + @Column(name = "accesstype", nullable = true, length = 3) private String accesstype; @Schema(required = true, title = "단속조", example = " ", description = "Input Description...") - @Column(name = "team", nullable = true) + @Column(name = "team", nullable = true, length = 3) private String team; @Schema(required = false, title = "등록일시", example = " ", description = "Input Description...") @@ -53,12 +58,42 @@ public class MinUserinfo implements Serializable { private java.sql.Date regdate; @Schema(required = false, title = "사용여부", example = " ", description = "Input Description...") - @Column(name = "isenable", nullable = true) + @Column(name = "isenable", nullable = true, length = 1) private String isenable; @Schema(required = false, title = "구청코드", example = " ", description = "Input Description...") - @Column(name = "gu", nullable = true) + @Column(name = "gu", nullable = true, length = 3) private String gu; + + + + @Transient + @JsonIgnore + @OneToMany(targetEntity = MinSimsaUser680.class, mappedBy = "minUserinfo")//, cascade = CascadeType.ALL, orphanRemoval = true) + private final Set minSimsaUser680s = new HashSet<>(); + + public void addMinSimsaUser680(final MinSimsaUser680 minSimsaUser680){ + minSimsaUser680s.add(minSimsaUser680); + minSimsaUser680.setMinUserinfo(this); + } + + public void removeSimsaUser680(final MinSimsaUser680 minSimsaUser680){ + minSimsaUser680s.remove(minSimsaUser680); + minSimsaUser680.setMinUserinfo(null); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MinUserinfo that = (MinUserinfo) o; + return Objects.equals(userid, that.userid); + } + + @Override + public int hashCode() { + return Objects.hash(userid); + } } diff --git a/src/main/java/com/xit/biz/ctgy/repository/IMinUserinfoRepository.java b/src/main/java/com/xit/biz/ctgy/repository/IMinUserinfoRepository.java new file mode 100644 index 0000000..ce1798b --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/repository/IMinUserinfoRepository.java @@ -0,0 +1,18 @@ +package com.xit.biz.ctgy.repository; + +import com.xit.biz.ctgy.entity.MinUserinfo; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface IMinUserinfoRepository extends JpaRepository { + Optional findByUserid(String userid); + + @Query(value = "SELECT TRAFFIC.ECL_ENCRYPT(?1) AS passwd FROM DUAL C", nativeQuery = true) + String queryGetPasswdEncode(@Param("passwd") String passwd); + + @Query(value = "SELECT TRAFFIC.ECL_DECRYPT(MU.passwd) AS passwd FROM min_userinfo MU WHERE MU.userid = ?1", nativeQuery = true) + String queryGetPasswd(@Param("userid") String userid); +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/ctgy/service/IMinUserinfoService.java b/src/main/java/com/xit/biz/ctgy/service/IMinUserinfoService.java new file mode 100644 index 0000000..c376891 --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/service/IMinUserinfoService.java @@ -0,0 +1,10 @@ +package com.xit.biz.ctgy.service; + +import com.xit.biz.ctgy.entity.MinUserinfo; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface IMinUserinfoService { + + Page findMinUserinfos(MinUserinfo minUserinfo, Pageable pageable); +} diff --git a/src/main/java/com/xit/biz/ctgy/service/impl/MinCivBoard680Service.java b/src/main/java/com/xit/biz/ctgy/service/impl/MinCivBoard680Service.java index 75352a2..5c77b07 100644 --- a/src/main/java/com/xit/biz/ctgy/service/impl/MinCivBoard680Service.java +++ b/src/main/java/com/xit/biz/ctgy/service/impl/MinCivBoard680Service.java @@ -5,6 +5,7 @@ import com.xit.biz.ctgy.entity.MinCivBoard680; import com.xit.biz.ctgy.repository.IMinCivBoard680Repository; import com.xit.biz.ctgy.service.IMinCivBoard680Service; import com.xit.core.support.jpa.JpaUtil; +import lombok.AllArgsConstructor; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.Page; @@ -15,13 +16,10 @@ import org.springframework.transaction.annotation.Transactional; import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.contains; @Service +@AllArgsConstructor public class MinCivBoard680Service implements IMinCivBoard680Service { - private final IMinCivBoard680Repository minCivBoard680Repository; - - public MinCivBoard680Service(IMinCivBoard680Repository minCivBoard680Repository) { - this.minCivBoard680Repository = minCivBoard680Repository; - } + private final IMinCivBoard680Repository repository; @Transactional(readOnly = true) public Page findMinCivBoard680s(MinCivBoard680 minCivBoard680, Pageable pageable) { @@ -31,7 +29,7 @@ public class MinCivBoard680Service implements IMinCivBoard680Service { .withMatcher("ciTitle", contains()) .withMatcher("ciName", contains()); Example example = Example.of(minCivBoard680, exampleMatcher); - Page page = minCivBoard680Repository.findAll(example, pageable); + Page page = repository.findAll(example, pageable); // List userList = page.getContent(); return page; } diff --git a/src/main/java/com/xit/biz/ctgy/service/impl/MinUserinfoService.java b/src/main/java/com/xit/biz/ctgy/service/impl/MinUserinfoService.java new file mode 100644 index 0000000..d3352ec --- /dev/null +++ b/src/main/java/com/xit/biz/ctgy/service/impl/MinUserinfoService.java @@ -0,0 +1,37 @@ +package com.xit.biz.ctgy.service.impl; + +import com.xit.biz.ctgy.entity.MinUserinfo; +import com.xit.biz.ctgy.repository.IMinUserinfoRepository; +import com.xit.biz.ctgy.service.IMinUserinfoService; +import com.xit.core.support.jpa.JpaUtil; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.contains; + +@Service +public class MinUserinfoService implements IMinUserinfoService { + + private final IMinUserinfoRepository repository; + + public MinUserinfoService(IMinUserinfoRepository repository) { + this.repository = repository; + } + + @Transactional//(readOnly = true) + public Page findMinUserinfos(MinUserinfo minUserinfo, Pageable pageable) { + //Sort sort = Sort.by(Sort.Direction.ASC, "codeOrdr"); + pageable = JpaUtil.getPagingInfo(pageable); + ExampleMatcher exampleMatcher = ExampleMatcher.matchingAll() + .withMatcher("userid", contains()) + .withMatcher("name", contains()); + Example example = Example.of(minUserinfo, exampleMatcher); + Page page = repository.findAll(example, pageable); + // List userList = page.getContent(); + return page; + } +} diff --git a/src/main/java/com/xit/core/config/SecurityConfig.java b/src/main/java/com/xit/core/config/SecurityConfig.java index cf89ef1..9e8cf73 100644 --- a/src/main/java/com/xit/core/config/SecurityConfig.java +++ b/src/main/java/com/xit/core/config/SecurityConfig.java @@ -1,5 +1,9 @@ package com.xit.core.config; +import com.xit.biz.ctgy.auth.MinPasswordEncoder; +import com.xit.biz.ctgy.auth.service.impl.CustomMinOAuth2UserService; +import com.xit.biz.ctgy.auth.service.impl.CustomMinUserDetailsService; +import com.xit.biz.ctgy.repository.IMinUserinfoRepository; import com.xit.core.oauth2.api.repository.RefreshTokenRepository; import com.xit.core.oauth2.config.properties.AppProperties; import com.xit.core.oauth2.config.properties.CorsProperties; @@ -9,13 +13,10 @@ import com.xit.core.oauth2.oauth.handler.OAuth2AuthenticationFailureHandler; import com.xit.core.oauth2.oauth.handler.OAuth2AuthenticationSuccessHandler; import com.xit.core.oauth2.oauth.handler.TokenAccessDeniedHandler; import com.xit.core.oauth2.oauth.repository.OAuth2AuthorizationRequestBasedOnCookieRepository; -import com.xit.core.oauth2.oauth.service.CustomOAuth2UserService; -import com.xit.core.oauth2.oauth.service.CustomUserDetailsService; import com.xit.core.oauth2.oauth.JwtTokenProvider; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; @@ -101,11 +102,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { private final CorsProperties corsProperties; private final AppProperties appProperties; private final JwtTokenProvider authTokenProvider; - private final CustomUserDetailsService userDetailsService; - private final CustomOAuth2UserService customOAuth2UserService; + private final CustomMinUserDetailsService userDetailsService; + private final CustomMinOAuth2UserService customOAuth2UserService; private final TokenAccessDeniedHandler tokenAccessDeniedHandler; private final RefreshTokenRepository refreshTokenRepository; + private final IMinUserinfoRepository minUserinfoRepository; + /** * 1. configure( WebSecurity) : 서비스 전체에 영향을 미치는 설정 * - web.ignoring를 통한 security skip(resource file), debug 모드설정 등 @@ -192,8 +195,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { "/**/*.html", "/**/*.css", "/**/*.js").permitAll() - .antMatchers("/**/auth/**", "/**/oauth2/**", "/**/users/**", "/**/biz/**").permitAll() - .anyRequest().authenticated() + .antMatchers("/**/h2-console/**", "/**/auth/**", "/**/oauth2/**", "/**/users/**", "/**/biz/**").permitAll() + .anyRequest().permitAll() //authenticated() .and() //.anyRequest().permitAll() //.hasRole(RoleType.USER.getCode()) @@ -239,8 +242,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { * security 설정 시, 사용할 인코더 설정 * */ @Bean - public BCryptPasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); + public MinPasswordEncoder passwordEncoder() { + return new MinPasswordEncoder(minUserinfoRepository); } /* diff --git a/src/main/java/com/xit/core/config/support/SpringDocApiConfig.java b/src/main/java/com/xit/core/config/support/SpringDocApiConfig.java index 982669a..0f6bba0 100644 --- a/src/main/java/com/xit/core/config/support/SpringDocApiConfig.java +++ b/src/main/java/com/xit/core/config/support/SpringDocApiConfig.java @@ -68,7 +68,7 @@ public class SpringDocApiConfig { return GroupedOpenApi.builder() .group("Ctgy-biz-API") .pathsToMatch( - "/api/v1/biz/**" + "/api/v1/ctgy/**" ) .build(); } diff --git a/src/main/java/com/xit/core/init/XitFrameworkApplicationCommandLineRunner.java b/src/main/java/com/xit/core/init/XitFrameworkApplicationCommandLineRunner.java index e1829bf..38daf6b 100644 --- a/src/main/java/com/xit/core/init/XitFrameworkApplicationCommandLineRunner.java +++ b/src/main/java/com/xit/core/init/XitFrameworkApplicationCommandLineRunner.java @@ -2,16 +2,28 @@ package com.xit.core.init; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.DelegatingPasswordEncoder; +import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; +import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder; +import org.springframework.stereotype.Component; //@Order(1) -//@Component +@Component @Slf4j public class XitFrameworkApplicationCommandLineRunner implements CommandLineRunner { + @Override public void run(String... args) throws Exception { // log.info("====================================================================================="); // log.info("XitFrameworkApplicationCommandLineRunner Args: " + Arrays.toString(args)); // log.info("====================================================================================="); + + System.out.println(new BCryptPasswordEncoder().encode("gnadmin")); + //System.out.println(new SCryptPasswordEncoder().encode("gnadmin")); + //System.out.println(new DelegatingPasswordEncoder().encode("gnadmin", "")); + System.out.println(new Pbkdf2PasswordEncoder().encode("gnadmin")); + } } diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml index 377adb6..4482dfd 100644 --- a/src/main/resources/config/application-dev.yml +++ b/src/main/resources/config/application-dev.yml @@ -9,7 +9,7 @@ spring: sql: init: mode: always - platform: postgres + platform: oracle #schema-locations: classpath:/data/h2/schema.sql #data-locations: classpath:/data/h2/data.sql @@ -17,37 +17,29 @@ spring: # database setting # ================================================================================================================== datasource: - driver-class-name: org.postgresql.Driver - url: jdbc:postgresql://localhost:5432/xit_admin - username: admin - password: admin!@ + driver-class-name: oracle.jdbc.OracleDriver + url: jdbc:oracle:thin:@211.119.124.118:1521:bustms + username: traffic + password: xhdgkq0 hikari: driver-class-name: ${spring.datasource.driver-class-name} jdbc-url: ${spring.datasource.url} password: ${spring.datasource.password} username: ${spring.datasource.username} - + read-only: false + # ================================================================================================================== # JPA setting # ================================================================================================================== jpa: - database-platform: org.hibernate.dialect.PostgreSQLDialect + database-platform: org.hibernate.dialect.Oracle10gDialect #show-sql: false properties: hibernate: hbm2ddl: # create / create-drop / update / validate / none - auto: update + auto: none -# h2: -# console: -# enabled: true -# path: /h2-console - - # 미사용 -# thymeleaf: -# cache: false - # ================================================================================================================== # JPA logging lib setting # ================================================================================================================== diff --git a/src/main/resources/config/application-local.yml b/src/main/resources/config/application-local.yml index 3ab516e..4073b4a 100644 --- a/src/main/resources/config/application-local.yml +++ b/src/main/resources/config/application-local.yml @@ -8,8 +8,7 @@ spring: sql: init: mode: always -# platform: h2 - platform: oracle + platform: h2 #schema-locations: classpath:/data/h2/schema.sql #data-locations: classpath:/data/h2/data.sql @@ -17,14 +16,10 @@ spring: # database setting # ================================================================================================================== datasource: -# driver-class-name: org.h2.Driver -# url: jdbc:h2:mem:xitdb;MODE=Oracle;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE -# username: sa -# password: '' - driver-class-name: oracle.jdbc.OracleDriver - url: jdbc:oracle:thin:@211.119.124.118:1521:bustms - username: traffic - password: xhdgkq0 + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:xitdb;MODE=Oracle;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + username: sa + password: '' hikari: driver-class-name: ${spring.datasource.driver-class-name} jdbc-url: ${spring.datasource.url} @@ -36,25 +31,20 @@ spring: # JPA setting # ================================================================================================================== jpa: -# database-platform: org.hibernate.dialect.H2Dialect - database-platform: org.hibernate.dialect.Oracle10gDialect + database-platform: org.hibernate.dialect.H2Dialect #show-sql: false properties: hibernate: hbm2ddl: # create / create-drop / update / validate / none # auto: create-drop - auto: none + auto: create-drop h2: console: enabled: true path: /h2-console -# # 미사용 -# thymeleaf: -# cache: false - # ================================================================================================================== # JPA logging lib setting # ================================================================================================================== diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 969cdbe..975015f 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -8,7 +8,7 @@ - +