diff --git a/src/main/java/com/unit/daybook/domain/auth/service/AuthService.java b/src/main/java/com/unit/daybook/domain/auth/service/AuthService.java index bf38ee8..e802f77 100644 --- a/src/main/java/com/unit/daybook/domain/auth/service/AuthService.java +++ b/src/main/java/com/unit/daybook/domain/auth/service/AuthService.java @@ -1,5 +1,8 @@ package com.unit.daybook.domain.auth.service; +import java.time.Duration; +import java.time.LocalDateTime; + import org.springframework.stereotype.Service; import com.unit.daybook.domain.auth.common.OAuthInfoResponse; diff --git a/src/main/java/com/unit/daybook/domain/auth/service/AuthTokensGenerator.java b/src/main/java/com/unit/daybook/domain/auth/service/AuthTokensGenerator.java index 790c660..aad10ac 100644 --- a/src/main/java/com/unit/daybook/domain/auth/service/AuthTokensGenerator.java +++ b/src/main/java/com/unit/daybook/domain/auth/service/AuthTokensGenerator.java @@ -1,5 +1,8 @@ package com.unit.daybook.domain.auth.service; +import java.time.Duration; +import java.time.LocalDateTime; + import org.springframework.stereotype.Component; import com.unit.daybook.domain.auth.dto.response.SocialLoginResponse; diff --git a/src/main/java/com/unit/daybook/domain/auth/service/TokenService.java b/src/main/java/com/unit/daybook/domain/auth/service/TokenService.java index f5ec894..bffa183 100644 --- a/src/main/java/com/unit/daybook/domain/auth/service/TokenService.java +++ b/src/main/java/com/unit/daybook/domain/auth/service/TokenService.java @@ -33,7 +33,7 @@ public class TokenService { private static final Long DEFAULT_EXPIRATION_MINUTES = 60L; - @Value("${jwt.secret-key}") + @Value("${util.jwt.secretKey}") private String secretKey; private static SecretKey createSecretKey(String key) { @@ -42,7 +42,7 @@ private static SecretKey createSecretKey(String key) { } public static String parseTokenByRequest(HttpServletRequest request) { - final String authHeader = request.getHeader("authorization"); + final String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { return authHeader.substring(7); } @@ -126,12 +126,12 @@ public CustomUserDetails getUserDetailsByToken(String token) { // token정보 이외에 추가로 정보가 필요해서 DB의 데이터를 조회해서 userDetails정보를 만드려면 UserDetailsService 인터페이스를 캐시서비스 또는 유저서비스에서 구현해서 userDetails를 리턴하게 하면 된다. // 꼭 UserDetailsService 인터페이스를 구현하지 않아도 되고 DB를 조회해서 userDetails에 데이터를 넣어서 리턴해주게 만들면된다. - Long userId = Long.parseLong(claims.get("memberId").toString()); - String userSnsId = claims.get("snsId") != null ? claims.get("snsId").toString() : null; - String userEmail = claims.get("email") != null ? claims.get("email").toString() : null; - String userNickname = claims.get("nickname").toString(); + Long memberId = Long.parseLong(claims.get("memberId").toString()); + String snsId = claims.get("snsId") != null ? claims.get("snsId").toString() : null; + String email = claims.get("email") != null ? claims.get("email").toString() : null; + String nickname = claims.get("nickname").toString(); - return new CustomUserDetails(userId, userSnsId, userEmail, userNickname); + return new CustomUserDetails(memberId, snsId, email, nickname); } @@ -162,13 +162,21 @@ private String generateRefreshToken(Map claims, SecretKey key) { * @return */ private Map getClaims(String jwt) { - if (jwt == null || jwt.isEmpty()) { + try { + if (jwt == null || jwt.isEmpty()) { + return null; + } + SecretKey key = createSecretKey(secretKey); + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(jwt) + .getBody(); + } catch (Exception e) { + // 예외 처리 + e.printStackTrace(); return null; } - return Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(jwt) - .getBody(); } + } diff --git a/src/main/java/com/unit/daybook/domain/member/controller/.gitkeep b/src/main/java/com/unit/daybook/domain/member/controller/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/java/com/unit/daybook/domain/member/controller/MemberController.java b/src/main/java/com/unit/daybook/domain/member/controller/MemberController.java new file mode 100644 index 0000000..806e225 --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/member/controller/MemberController.java @@ -0,0 +1,28 @@ +package com.unit.daybook.domain.member.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.unit.daybook.domain.common.annotation.LoginUsers; +import com.unit.daybook.domain.member.domain.Member; +import com.unit.daybook.domain.member.service.MemberService; +import com.unit.daybook.global.config.security.CustomUserDetails; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/member") +public class MemberController { + + private final MemberService memberService; + + @GetMapping + public Member memberFindOne( + @LoginUsers CustomUserDetails userDetails + ) { + return memberService.findOneMember(userDetails.getMemberId()); + } + +} diff --git a/src/main/java/com/unit/daybook/domain/member/service/MemberService.java b/src/main/java/com/unit/daybook/domain/member/service/MemberService.java new file mode 100644 index 0000000..ccd3109 --- /dev/null +++ b/src/main/java/com/unit/daybook/domain/member/service/MemberService.java @@ -0,0 +1,26 @@ +package com.unit.daybook.domain.member.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.unit.daybook.domain.member.domain.Member; +import com.unit.daybook.domain.member.repository.MemberRepository; +import com.unit.daybook.global.config.security.CustomUserDetails; +import com.unit.daybook.global.error.exception.CustomException; +import com.unit.daybook.global.error.exception.ErrorCode; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional +public class MemberService { + + private final MemberRepository memberRepository; + + @Transactional(readOnly = true) + public Member findOneMember(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND)); + } +} diff --git a/src/main/java/com/unit/daybook/global/config/WebMvcConfig.java b/src/main/java/com/unit/daybook/global/config/WebMvcConfig.java new file mode 100644 index 0000000..bc633b2 --- /dev/null +++ b/src/main/java/com/unit/daybook/global/config/WebMvcConfig.java @@ -0,0 +1,36 @@ +package com.unit.daybook.global.config; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.unit.daybook.global.config.resolver.LoginUserDetailsResolver; + +import lombok.RequiredArgsConstructor; + +@Configuration +@RequiredArgsConstructor +public class WebMvcConfig implements WebMvcConfigurer { + + private final LoginUserDetailsResolver loginUserDetailsResolver; + + @Override + public void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.add(loginUserDetailsResolver); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods(Arrays.stream(HttpMethod.values()) + .map(HttpMethod::name) + .toArray(String[]::new)) + .allowedHeaders("*"); + } +} diff --git a/src/main/java/com/unit/daybook/global/config/security/CustomUserDetails.java b/src/main/java/com/unit/daybook/global/config/security/CustomUserDetails.java index ac66317..fb83617 100644 --- a/src/main/java/com/unit/daybook/global/config/security/CustomUserDetails.java +++ b/src/main/java/com/unit/daybook/global/config/security/CustomUserDetails.java @@ -18,12 +18,12 @@ public class CustomUserDetails implements UserDetails { private final String nickname; private final List authorityList; - public CustomUserDetails(Long memberId, String snsId, String email, String userNickname) { + public CustomUserDetails(Long memberId, String snsId, String email, String nickname) { this.memberId = memberId; this.snsId = snsId; this.authorityList = new ArrayList<>(); this.email = email; - this.nickname = userNickname; + this.nickname = nickname; } @Override @@ -61,8 +61,5 @@ public boolean isEnabled() { return true; } - public void setUserId(Long userId) { - this.memberId = userId; - } } diff --git a/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java b/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java index 13aa7bf..295d7a8 100644 --- a/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java +++ b/src/main/java/com/unit/daybook/global/error/exception/ErrorCode.java @@ -19,7 +19,10 @@ public enum ErrorCode { KAKAO_RESPONSE_NOT_FOUND(HttpStatus.UNAUTHORIZED, "카카오에 대한 응답값이 존재하지 않습니다."), CLAIMS_IS_NULL(HttpStatus.UNAUTHORIZED, "Claim이 null 값입니다."), INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않는 토큰입니다."), - ; + EXPIRED_REFRESH_TOKEN(HttpStatus.FORBIDDEN, "리프레시 토큰이 만료되었습니다."), + + // Member + MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 회원입니다."); private final HttpStatus status; private final String message; } diff --git a/src/main/java/com/unit/daybook/global/util/JwtUtil.java b/src/main/java/com/unit/daybook/global/util/JwtUtil.java new file mode 100644 index 0000000..ef9fe54 --- /dev/null +++ b/src/main/java/com/unit/daybook/global/util/JwtUtil.java @@ -0,0 +1,91 @@ +package com.unit.daybook.global.util; + +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Base64; +import java.util.Map; + +import javax.crypto.SecretKey; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; + +@Component +public class JwtUtil { + + @Value("${util.jwt.secretKey}") + private String jwtSecretKey; + + @Value("${util.jwt.refreshKey}") + private String jwtReFreshKey; + private byte[] secretBytes; + private byte[] refreshBytes; + + /** + * JWT 생성 + * + * @param claims + * @return + */ + public String generateToken(Map claims) { + return Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS256, secretBytes) + .compact(); + } + + public String generateRefreshToken(Map claims) { + return Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS256, refreshBytes) + .compact(); + } + + public String generateToken(Map header, Map claims, Key key) { + return Jwts.builder() + .setHeader(header) + .setClaims(claims) + .signWith(SignatureAlgorithm.valueOf(header.get(JwsHeader.ALGORITHM).toString()), key) + .compact(); + } + + /** + * JWT 검증 및 데이터 가져오기 + * + * @param jwt + * @return + */ + public Map getClaims(String jwt) { + try { + if (jwt == null || jwt.isEmpty()) { + return null; + } + Claims claims = Jwts.parser() + .setSigningKey(jwtSecretKey) + .parseClaimsJws(jwt) + .getBody(); + return claims; + } catch (Exception e) { + // 예외 처리 + e.printStackTrace(); + return null; + } + } + + public Map getClaimsForReFresh(String jwt) { + if (jwt == null || jwt.isEmpty()) { + return null; + } + return Jwts.parser() + .setSigningKey(jwtReFreshKey) + .parseClaimsJws(jwt) + .getBody(); + } +}