From ac2f9dbff1913aea9dacf1db35a1bf07faaee8be Mon Sep 17 00:00:00 2001 From: dl-00-e8 Date: Mon, 29 Jan 2024 00:06:14 +0900 Subject: [PATCH] =?UTF-8?q?[#3]=20chore:=20PrincipalDetails=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20Authentication=EC=97=90=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EB=90=98=EB=8F=84=EB=A1=9D=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 5 +- .../server/domain/member/dto/MemberReq.java | 6 +- .../server/domain/member/dto/MemberRes.java | 7 ++ .../domain/member/enumerate/MemberType.java | 11 ++- .../member/repository/MemberRepository.java | 3 + .../global/security/PrincipalDetails.java | 84 +++++++++++++++++++ .../global/security/jwt/TokenProvider.java | 34 ++------ 7 files changed, 121 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/gongjakso/server/global/security/PrincipalDetails.java diff --git a/src/main/java/com/gongjakso/server/domain/member/controller/MemberController.java b/src/main/java/com/gongjakso/server/domain/member/controller/MemberController.java index 1923effa..579aa4d1 100644 --- a/src/main/java/com/gongjakso/server/domain/member/controller/MemberController.java +++ b/src/main/java/com/gongjakso/server/domain/member/controller/MemberController.java @@ -5,6 +5,7 @@ import com.gongjakso.server.domain.member.entity.Member; import com.gongjakso.server.domain.member.service.MemberService; import com.gongjakso.server.global.common.ApplicationResponse; +import com.gongjakso.server.global.security.PrincipalDetails; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -24,7 +25,7 @@ public class MemberController { private final MemberService memberService; @PutMapping("") - public ApplicationResponse update(@AuthenticationPrincipal Member member, @Valid @RequestBody MemberReq memberReq) { - return ApplicationResponse.ok(memberService.update(member, memberReq)); + public ApplicationResponse update(@AuthenticationPrincipal PrincipalDetails principalDetails, @Valid @RequestBody MemberReq memberReq) { + return ApplicationResponse.ok(memberService.update(principalDetails.getMember(), memberReq)); } } diff --git a/src/main/java/com/gongjakso/server/domain/member/dto/MemberReq.java b/src/main/java/com/gongjakso/server/domain/member/dto/MemberReq.java index d7be5d18..6441b892 100644 --- a/src/main/java/com/gongjakso/server/domain/member/dto/MemberReq.java +++ b/src/main/java/com/gongjakso/server/domain/member/dto/MemberReq.java @@ -1,6 +1,10 @@ package com.gongjakso.server.domain.member.dto; -public record MemberReq(String name, +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +@Builder +public record MemberReq(@NotNull String name, String status, String major, String job) { diff --git a/src/main/java/com/gongjakso/server/domain/member/dto/MemberRes.java b/src/main/java/com/gongjakso/server/domain/member/dto/MemberRes.java index 74167ed5..63ac3d7b 100644 --- a/src/main/java/com/gongjakso/server/domain/member/dto/MemberRes.java +++ b/src/main/java/com/gongjakso/server/domain/member/dto/MemberRes.java @@ -25,6 +25,13 @@ public static MemberRes of(Member member) { return MemberRes.builder() .memberId(member.getMemberId()) .email(member.getEmail()) + .name(member.getName()) + .profileUrl(member.getProfileUrl()) + .memberType(member.getMemberType()) + .loginType(member.getLoginType()) + .status(member.getStatus()) + .major(member.getMajor()) + .job(member.getJob()) .build(); } } diff --git a/src/main/java/com/gongjakso/server/domain/member/enumerate/MemberType.java b/src/main/java/com/gongjakso/server/domain/member/enumerate/MemberType.java index 18a8d9f9..7d0f0eab 100644 --- a/src/main/java/com/gongjakso/server/domain/member/enumerate/MemberType.java +++ b/src/main/java/com/gongjakso/server/domain/member/enumerate/MemberType.java @@ -1,5 +1,14 @@ package com.gongjakso.server.domain.member.enumerate; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor public enum MemberType { - GENERAL, ADMIN + GENERAL("ROLE_GENERAL", "일반"), + ADMIN("ROLE_ADMIN", "관리자"); + + private final String role; + private final String title; } diff --git a/src/main/java/com/gongjakso/server/domain/member/repository/MemberRepository.java b/src/main/java/com/gongjakso/server/domain/member/repository/MemberRepository.java index d806c39e..0b2b4a62 100644 --- a/src/main/java/com/gongjakso/server/domain/member/repository/MemberRepository.java +++ b/src/main/java/com/gongjakso/server/domain/member/repository/MemberRepository.java @@ -1,6 +1,7 @@ package com.gongjakso.server.domain.member.repository; import com.gongjakso.server.domain.member.entity.Member; +import com.gongjakso.server.domain.member.enumerate.MemberType; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; @@ -8,4 +9,6 @@ public interface MemberRepository extends JpaRepository { Optional findMemberByEmailAndDeletedAtIsNull(String email); + + Optional findMemberByEmailAndMemberTypeAndDeletedAtIsNull(String email, MemberType memberType); } diff --git a/src/main/java/com/gongjakso/server/global/security/PrincipalDetails.java b/src/main/java/com/gongjakso/server/global/security/PrincipalDetails.java new file mode 100644 index 00000000..fa9efddf --- /dev/null +++ b/src/main/java/com/gongjakso/server/global/security/PrincipalDetails.java @@ -0,0 +1,84 @@ +package com.gongjakso.server.global.security; + +import com.gongjakso.server.domain.member.entity.Member; +import lombok.Getter; +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.user.OAuth2User; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +public class PrincipalDetails implements UserDetails, OAuth2User { + + @Getter + private final Member member; + private Map attributes; + + // 일반 로그인 + public PrincipalDetails(Member member) { + this.member = member; + } + + // OAuth 로그인 + public PrincipalDetails(Member member, Map attributes) { + this.member = member; + this.attributes = attributes; + } + + // 권한 정보 반환 (GENERAL, ADMIN 중 하나) + @Override + public Collection getAuthorities() { + Collection authorities = new ArrayList(); + authorities.add(new SimpleGrantedAuthority(member.getMemberType().getRole())); + + return authorities; + } + + // 사용자의 비밀번호 반환 + @Override + public String getPassword() { + return member.getPassword(); + } + + // 사용자의 이름 반환 + @Override + public String getUsername() { + return member.getName(); + } + + // 계정이 잠기지 않았으므로 true 반환 + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + // 패스워드가 만료되지 않았으므로 true 반환 + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + // 계속 사용 가능한 것이기에 true 반환 + @Override + public boolean isEnabled() { + return true; + } + + @Override + public String getName() { + return null; + } + + @Override + public Map getAttributes() { + return attributes; + } +} diff --git a/src/main/java/com/gongjakso/server/global/security/jwt/TokenProvider.java b/src/main/java/com/gongjakso/server/global/security/jwt/TokenProvider.java index 74676db3..c6f7bc24 100644 --- a/src/main/java/com/gongjakso/server/global/security/jwt/TokenProvider.java +++ b/src/main/java/com/gongjakso/server/global/security/jwt/TokenProvider.java @@ -3,6 +3,9 @@ import com.gongjakso.server.domain.member.entity.Member; import com.gongjakso.server.domain.member.enumerate.MemberType; import com.gongjakso.server.domain.member.repository.MemberRepository; +import com.gongjakso.server.global.exception.ApplicationException; +import com.gongjakso.server.global.exception.ErrorCode; +import com.gongjakso.server.global.security.PrincipalDetails; import com.gongjakso.server.global.security.jwt.dto.TokenDto; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; @@ -13,19 +16,12 @@ import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.security.Key; -import java.util.Arrays; -import java.util.Collection; import java.util.Date; -import java.util.stream.Collectors; @Component @RequiredArgsConstructor @@ -35,7 +31,6 @@ public class TokenProvider { private String secretKey; private Key key; private final MemberRepository memberRepository; - private final RedisTemplate redisTemplate; // ATK 만료시간: 1일 private static final long accessTokenExpirationTime = 7 * 24 * 60 * 60 * 1000L; @@ -122,14 +117,9 @@ public boolean validateToken(String token) { */ public TokenDto accessTokenReissue(String token) { String email = getEmail(token); - MemberType type = getType(token); - - Member member = memberRepository.findMemberByEmailAndDeletedAtIsNull(email).orElseThrow(RuntimeException::new); // Exception은 실제 개발에서는 커스텀 필요 - String storedRefreshToken = (String) redisTemplate.opsForValue().get(email + type.toString()); // Key는 email + role로 저장되어 있으며, value가 해당 정보에 대한 refreshToken임. - if(storedRefreshToken == null || !storedRefreshToken.equals(token)) { - throw new RuntimeException(); - } + MemberType memberType = getType(token); + Member member = memberRepository.findMemberByEmailAndMemberTypeAndDeletedAtIsNull(email, memberType).orElseThrow(() -> new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); String accessToken = createAccessToken(member); // 해당 부분에 refreshToken의 만료기간이 얼마 남지 않았을 때, 자동 재발급하는 로직을 추가할 수 있음. @@ -147,17 +137,11 @@ public TokenDto accessTokenReissue(String token) { */ public Authentication getAuthentication(String token) { String email = getEmail(token); - MemberType type = getType(token); - - Member member = memberRepository.findMemberByEmailAndDeletedAtIsNull(email).orElseThrow(RuntimeException::new); // Exception은 실제 개발에서는 커스텀 필요 - Collection authorities = - Arrays.stream(member.getMemberType().toString().split(",")) - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toList()); - - UserDetails details = new org.springframework.security.core.userdetails.User(email, "", authorities); + MemberType memberType = getType(token); + Member member = memberRepository.findMemberByEmailAndMemberTypeAndDeletedAtIsNull(email, memberType).orElseThrow(() -> new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); + PrincipalDetails principalDetails = new PrincipalDetails(member); - return new UsernamePasswordAuthenticationToken(details, "", authorities); + return new UsernamePasswordAuthenticationToken(principalDetails, "", principalDetails.getAuthorities()); } /**