commit
This commit is contained in:
@@ -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<? extends GrantedAuthority> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,8 +7,11 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.spring.domain.user.entity.AgentUserToken;
|
||||
|
||||
|
||||
public interface AgentUserTokenRepository extends JpaRepository<AgentUserToken, UUID> {
|
||||
|
||||
Optional<AgentUserToken> findByIdAndReissueCountLessThan(UUID id, long count);
|
||||
|
||||
Optional<AgentUserToken> findByRefreshToken(String refreshToken);
|
||||
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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'");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) 클래스입니다.
|
||||
* <p>애플리케이션의 사용자 정보를 Spring Security에서 사용할 수 있는 형태로 변환합니다.</p>
|
||||
*
|
||||
* @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<? extends GrantedAuthority> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)));
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user