From f626065b47fc77a08897f3338c5fd029ecc35138 Mon Sep 17 00:00:00 2001 From: mindol1004 Date: Wed, 2 Oct 2024 13:55:25 +0900 Subject: [PATCH] commit --- .../spring/domain/user/entity/AgentUser.java | 55 +++++++-- .../spring/domain/user/error/UserRule.java | 1 + .../repository/AgentUserTokenRepository.java | 3 + .../user}/service/UserPrincipalService.java | 11 +- .../user/service/UserRefreshTokenService.java | 44 +++++++ .../infra/security/domain/UserPrincipal.java | 115 ------------------ .../handler/SigninSuccessHandler.java | 10 +- .../infra/security/jwt/JwtTokenService.java | 33 +---- .../provider/UserAuthenticationProvider.java | 6 +- .../security/service/RefreshTokenService.java | 11 ++ 10 files changed, 122 insertions(+), 167 deletions(-) rename batch-quartz/src/main/java/com/spring/{infra/security => domain/user}/service/UserPrincipalService.java (62%) create mode 100644 batch-quartz/src/main/java/com/spring/domain/user/service/UserRefreshTokenService.java delete mode 100644 batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java create mode 100644 batch-quartz/src/main/java/com/spring/infra/security/service/RefreshTokenService.java diff --git a/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java b/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java index cab4506..8c31cc5 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/entity/AgentUser.java @@ -1,22 +1,24 @@ package com.spring.domain.user.entity; +import java.util.Arrays; +import java.util.Collection; import java.util.UUID; +import java.util.stream.Collectors; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; -import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; -import javax.persistence.OneToOne; import javax.persistence.Table; import org.hibernate.annotations.GenericGenerator; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -25,8 +27,7 @@ import lombok.NoArgsConstructor; @Table(name = "AGENT_USER") @Getter @NoArgsConstructor(access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class AgentUser { +public class AgentUser implements UserDetails { @Id @GeneratedValue(generator = "uuid2") @@ -34,9 +35,6 @@ public class AgentUser { @Column(name = "ID", nullable = false, columnDefinition = "BINARY(16)") private UUID id; - // @OneToOne(mappedBy = "agentUser", cascade = CascadeType.ALL, orphanRemoval = true) - // private AgentUserToken agentUserToken; - @Column(name = "USER_ID", nullable = false, length = 50) private String userId; @@ -58,4 +56,43 @@ public class AgentUser { this.userRole = userRole; } + @Override + public Collection getAuthorities() { + return Arrays.stream(AgentUserRole.values()) + .filter(role -> Arrays.asList(this.userRole).contains(role)) + .map(AgentUserRole::getRole) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + + @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 String getPassword() { + return this.userPassword; + } + + @Override + public String getUsername() { + return this.userId; + } + } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/error/UserRule.java b/batch-quartz/src/main/java/com/spring/domain/user/error/UserRule.java index 54251f4..964124f 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/error/UserRule.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/error/UserRule.java @@ -12,6 +12,7 @@ import lombok.RequiredArgsConstructor; public enum UserRule implements ErrorRule { USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다."), + USER_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "사용자 인증에 실패 하였습니다."), USER_ID_CONFLICT(HttpStatus.CONFLICT, "중복된 아이디 입니다."); private final HttpStatus status; diff --git a/batch-quartz/src/main/java/com/spring/domain/user/repository/AgentUserTokenRepository.java b/batch-quartz/src/main/java/com/spring/domain/user/repository/AgentUserTokenRepository.java index 8cf53f3..af1dbbf 100644 --- a/batch-quartz/src/main/java/com/spring/domain/user/repository/AgentUserTokenRepository.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/repository/AgentUserTokenRepository.java @@ -7,8 +7,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import com.spring.domain.user.entity.AgentUserToken; + public interface AgentUserTokenRepository extends JpaRepository { Optional findByIdAndReissueCountLessThan(UUID id, long count); + Optional findByRefreshToken(String refreshToken); + } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/service/UserPrincipalService.java b/batch-quartz/src/main/java/com/spring/domain/user/service/UserPrincipalService.java similarity index 62% rename from batch-quartz/src/main/java/com/spring/infra/security/service/UserPrincipalService.java rename to batch-quartz/src/main/java/com/spring/domain/user/service/UserPrincipalService.java index 16fcdf4..570bd3a 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/service/UserPrincipalService.java +++ b/batch-quartz/src/main/java/com/spring/domain/user/service/UserPrincipalService.java @@ -1,4 +1,4 @@ -package com.spring.infra.security.service; +package com.spring.domain.user.service; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -6,10 +6,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.spring.domain.user.entity.AgentUser; +import com.spring.domain.user.error.UserRule; import com.spring.domain.user.repository.AgentUserRepository; -import com.spring.infra.security.domain.UserPrincipal; -import com.spring.infra.security.error.SecurityExceptionRule; import lombok.RequiredArgsConstructor; @@ -22,9 +20,8 @@ public class UserPrincipalService implements UserDetailsService { @Transactional(readOnly = true) @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - AgentUser user = agentUserRepository.findByUserId(username) - .orElseThrow(() -> new UsernameNotFoundException(SecurityExceptionRule.USER_UNAUTHORIZED.getMessage())); - return UserPrincipal.valueOf(user); + return agentUserRepository.findByUserId(username) + .orElseThrow(() -> new UsernameNotFoundException(UserRule.USER_UNAUTHORIZED.getMessage())); } } diff --git a/batch-quartz/src/main/java/com/spring/domain/user/service/UserRefreshTokenService.java b/batch-quartz/src/main/java/com/spring/domain/user/service/UserRefreshTokenService.java new file mode 100644 index 0000000..527f161 --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/domain/user/service/UserRefreshTokenService.java @@ -0,0 +1,44 @@ +package com.spring.domain.user.service; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.spring.domain.user.entity.AgentUser; +import com.spring.domain.user.entity.AgentUserToken; +import com.spring.domain.user.error.UserNotFoundException; +import com.spring.domain.user.repository.AgentUserRepository; +import com.spring.domain.user.repository.AgentUserTokenRepository; +import com.spring.infra.security.service.RefreshTokenService; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class UserRefreshTokenService implements RefreshTokenService { + + private final AgentUserRepository agentUserRepository; + private final AgentUserTokenRepository agentUserTokenRepository; + + @Transactional + @Override + public void saveRefreshToken(UserDetails user, String refreshToken) { + AgentUser agentUser = agentUserRepository.findByUserId(user.getUsername()).orElseThrow(UserNotFoundException::new); + agentUserTokenRepository.findById(agentUser.getId()) + .ifPresentOrElse( + it -> it.updateRefreshToken(refreshToken), + () -> agentUserTokenRepository.save( + AgentUserToken.builder() + .agentUser(agentUser) + .refreshToken(refreshToken) + .build() + ) + ); + } + + @Override + public String findByRefreshToken(String refreshToken) { + throw new UnsupportedOperationException("Unimplemented method 'findByRefreshToken'"); + } + +} diff --git a/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java b/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java deleted file mode 100644 index 5b05732..0000000 --- a/batch-quartz/src/main/java/com/spring/infra/security/domain/UserPrincipal.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.spring.infra.security.domain; - -import java.util.Arrays; -import java.util.Collection; -import java.util.stream.Collectors; - -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import com.spring.domain.user.entity.AgentUser; -import com.spring.domain.user.entity.AgentUserRole; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -/** - * Spring Security의 UserDetails 인터페이스를 구현한 사용자 주체(Principal) 클래스입니다. - *

애플리케이션의 사용자 정보를 Spring Security에서 사용할 수 있는 형태로 변환합니다.

- * - * @author mindol - * @version 1.0 - */ -@Getter -@RequiredArgsConstructor -public final class UserPrincipal implements UserDetails { - - private final transient AgentUser agentUser; - - /** - * AgentUser 객체로부터 UserPrincipal 객체를 생성합니다. - * - * @param agentUser 변환할 AgentUser 객체 - * @return 생성된 UserPrincipal 객체 - */ - public static UserPrincipal valueOf(AgentUser agentUser) { - return new UserPrincipal(agentUser); - } - - /** - * 사용자의 권한 목록을 반환합니다. - * - * @return 사용자의 GrantedAuthority 컬렉션 - */ - @Override - public Collection getAuthorities() { - return Arrays.stream(AgentUserRole.values()) - .filter(role -> Arrays.asList(agentUser.getUserRole()).contains(role)) - .map(AgentUserRole::getRole) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()); - } - - /** - * 계정이 만료되지 않았는지 확인합니다. - * - * @return 계정 만료 여부 (true: 만료되지 않음) - */ - @Override - public boolean isAccountNonExpired() { - return true; - } - - /** - * 계정이 잠기지 않았는지 확인합니다. - * - * @return 계정 잠금 여부 (true: 잠기지 않음) - */ - @Override - public boolean isAccountNonLocked() { - return true; - } - - /** - * 자격 증명(비밀번호)이 만료되지 않았는지 확인합니다. - * - * @return 자격 증명 만료 여부 (true: 만료되지 않음) - */ - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - /** - * 계정이 활성화되어 있는지 확인합니다. - * - * @return 계정 활성화 여부 (true: 활성화됨) - */ - @Override - public boolean isEnabled() { - return true; - } - - /** - * 사용자의 비밀번호를 반환합니다. - * 이 구현에서는 null을 반환합니다. - * - * @return 사용자 비밀번호 (null) - */ - @Override - public String getPassword() { - return agentUser.getUserPassword(); - } - - /** - * 사용자의 로그인 ID를 반환합니다. - * - * @return 사용자 로그인 ID - */ - @Override - public String getUsername() { - return agentUser.getUserName(); - } - -} diff --git a/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java index 790966f..c7a07f9 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/handler/SigninSuccessHandler.java @@ -7,9 +7,9 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.RequestCache; @@ -19,8 +19,8 @@ import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; import com.spring.infra.security.config.SecurityURI; import com.spring.infra.security.dto.SignResponse; -import com.spring.infra.security.jwt.JwtTokenRule; import com.spring.infra.security.jwt.JwtTokenService; +import com.spring.infra.security.service.RefreshTokenService; import lombok.RequiredArgsConstructor; @@ -29,6 +29,7 @@ import lombok.RequiredArgsConstructor; public class SigninSuccessHandler implements AuthenticationSuccessHandler { private final JwtTokenService jwtTokenService; + private final RefreshTokenService refreshTokenService; private final ObjectMapper objectMapper; private RequestCache requestCache = new HttpSessionRequestCache(); @@ -39,13 +40,12 @@ public class SigninSuccessHandler implements AuthenticationSuccessHandler { Authentication authentication ) throws IOException, ServletException { - String accessToken = jwtTokenService.generateAccessToken(response, authentication); - jwtTokenService.generateRefreshToken(response, authentication); + jwtTokenService.generateAccessToken(response, authentication); + refreshTokenService.saveRefreshToken((UserDetails) authentication.getPrincipal(), jwtTokenService.generateRefreshToken(response, authentication)); SavedRequest savedRequest = requestCache.getRequest(request, response); String targetUrl = (savedRequest != null) ? savedRequest.getRedirectUrl() : SecurityURI.REDIRECT_URI.getUri(); - response.setHeader(HttpHeaders.AUTHORIZATION, JwtTokenRule.BEARER_PREFIX.getValue() + accessToken); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.getWriter().write(objectMapper.writeValueAsString(SignResponse.of(true, targetUrl))); diff --git a/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java b/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java index 843276d..9f6a13d 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/jwt/JwtTokenService.java @@ -4,7 +4,6 @@ import java.security.Key; import java.util.List; import java.util.stream.Collectors; -import javax.persistence.EntityNotFoundException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -15,17 +14,12 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.spring.domain.user.entity.AgentUser; -import com.spring.domain.user.entity.AgentUserToken; -import com.spring.domain.user.repository.AgentUserRepository; -import com.spring.domain.user.repository.AgentUserTokenRepository; -import com.spring.infra.security.domain.UserPrincipal; import com.spring.infra.security.error.SecurityAuthException; import com.spring.infra.security.error.SecurityExceptionRule; -import com.spring.infra.security.service.UserPrincipalService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -43,9 +37,7 @@ public class JwtTokenService { private final JwtTokenUtil jwtTokenUtil; private final JwtTokenGenerator jwtTokenGenerator; - private final UserPrincipalService userPrincipalService; - private final AgentUserRepository agentUserRepository; - private final AgentUserTokenRepository agentUserTokenRepository; + private final UserDetailsService userDetailsService; private final Key accessSecretKey; private final Key refreshSecretKey; private final long refreshExpiration; @@ -53,16 +45,12 @@ public class JwtTokenService { public JwtTokenService( JwtTokenUtil jwtTokenUtil, JwtTokenGenerator jwtTokenGenerator, - UserPrincipalService userPrincipalService, - AgentUserRepository agentUserRepository, - AgentUserTokenRepository agentUserTokenRepository, + UserDetailsService userDetailsService, JwtProperties jwtProperties ) { this.jwtTokenUtil = jwtTokenUtil; this.jwtTokenGenerator = jwtTokenGenerator; - this.userPrincipalService = userPrincipalService; - this.agentUserRepository = agentUserRepository; - this.agentUserTokenRepository = agentUserTokenRepository; + this.userDetailsService = userDetailsService; this.accessSecretKey = jwtTokenUtil.getSigningKey(jwtProperties.getAccessToken().getSecret()); this.refreshSecretKey = jwtTokenUtil.getSigningKey(jwtProperties.getRefreshToken().getSecret()); this.refreshExpiration = jwtProperties.getRefreshToken().getExpiration(); @@ -91,17 +79,6 @@ public class JwtTokenService { @Transactional public String generateRefreshToken(HttpServletResponse response, Authentication authentication) { String refreshToken = jwtTokenGenerator.generateRefreshToken(authentication); - - UserPrincipal user = (UserPrincipal) authentication.getPrincipal(); - AgentUser agentUser = agentUserRepository.findById(user.getAgentUser().getId()).orElseThrow(() -> new EntityNotFoundException("AgentUser not found")); - agentUserTokenRepository.findById(agentUser.getId()) - .ifPresentOrElse( - it -> it.updateRefreshToken(refreshToken), - () -> agentUserTokenRepository.save( - AgentUserToken.builder().agentUser(agentUser).refreshToken(refreshToken).build() - ) - ); - ResponseCookie cookie = setTokenToCookie(JwtTokenRule.REFRESH_PREFIX.getValue(), refreshToken, refreshExpiration); response.addHeader(JwtTokenRule.JWT_ISSUE_HEADER.getValue(), cookie.toString()); return refreshToken; @@ -196,7 +173,7 @@ public class JwtTokenService { */ @Transactional(readOnly = true) public Authentication getAuthentication(String token) { - UserDetails principal = userPrincipalService.loadUserByUsername(getUserPk(token)); + UserDetails principal = userDetailsService.loadUserByUsername(getUserPk(token)); return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities()); } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/provider/UserAuthenticationProvider.java b/batch-quartz/src/main/java/com/spring/infra/security/provider/UserAuthenticationProvider.java index 7081f5a..116f20d 100644 --- a/batch-quartz/src/main/java/com/spring/infra/security/provider/UserAuthenticationProvider.java +++ b/batch-quartz/src/main/java/com/spring/infra/security/provider/UserAuthenticationProvider.java @@ -5,13 +5,13 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import com.spring.infra.security.error.SecurityAuthException; import com.spring.infra.security.error.SecurityExceptionRule; -import com.spring.infra.security.service.UserPrincipalService; import lombok.RequiredArgsConstructor; @@ -20,14 +20,14 @@ import lombok.RequiredArgsConstructor; public class UserAuthenticationProvider implements AuthenticationProvider { private final PasswordEncoder passwordEncoder; - private final UserPrincipalService userPrincipalService; + private final UserDetailsService userDetailsService; @Transactional(readOnly = true) @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = String.valueOf(authentication.getCredentials()); - UserDetails user = userPrincipalService.loadUserByUsername(username); + UserDetails user = userDetailsService.loadUserByUsername(username); if (isNotMatches(password, user.getPassword())) { throw new SecurityAuthException(SecurityExceptionRule.USER_NOT_PASSWORD.getMessage()); } diff --git a/batch-quartz/src/main/java/com/spring/infra/security/service/RefreshTokenService.java b/batch-quartz/src/main/java/com/spring/infra/security/service/RefreshTokenService.java new file mode 100644 index 0000000..43e0bac --- /dev/null +++ b/batch-quartz/src/main/java/com/spring/infra/security/service/RefreshTokenService.java @@ -0,0 +1,11 @@ +package com.spring.infra.security.service; + +import org.springframework.security.core.userdetails.UserDetails; + +public interface RefreshTokenService { + + void saveRefreshToken(UserDetails user, String refreshToken); + + String findByRefreshToken(String refreshToken); + +}