Skip to content

Commit

Permalink
feat: JWT 회원 검증 로직 구현 #8
Browse files Browse the repository at this point in the history
  • Loading branch information
PgmJun committed Jan 18, 2024
1 parent 60b0517 commit 0f2687e
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.nice.petudio.api.member.service;

import com.nice.petudio.domain.member.Member;
import com.nice.petudio.domain.member.repository.MemberRepository;
import com.nice.petudio.global.exception.NotFoundException;
import com.nice.petudio.global.exception.error.ErrorCode;

public class MemberServiceUtils {

public static Member findMemberById(MemberRepository memberRepository, Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_MEMBER_EXCEPTION,
String.format("====> 존재하지 않는 memberId(%d) 입니다.", memberId)));
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/nice/petudio/domain/member/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "members")
@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseEntity {
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/nice/petudio/global/auth/admin/Admin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.nice.petudio.global.auth.admin;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Role이 Admin 인지 확인하기 위한 애노테이션
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Admin {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.nice.petudio.global.auth.admin;

import com.nice.petudio.domain.member.MemberRole;
import com.nice.petudio.global.auth.handler.AuthCheckHandler;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

@RequiredArgsConstructor
@Component
public class AdminAuthInterceptor implements HandlerInterceptor {

private final AuthCheckHandler authHandler;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Optional<Admin> adminAuth = Optional.ofNullable(
handlerMethod.getMethodAnnotation(Admin.class));

if (adminAuth.isEmpty()) {
return true;
}
authHandler.validateAuthority(request, List.of(MemberRole.ADMIN));
return true;
}
return true;
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/nice/petudio/global/auth/auth/Auth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.nice.petudio.global.auth.auth;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 서비스의 회원이 맞는지 확인하기 위한 애노테이션
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.nice.petudio.global.auth.auth;

import com.nice.petudio.domain.member.MemberRole;
import com.nice.petudio.global.auth.handler.AuthCheckHandler;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

@RequiredArgsConstructor
@Component
public class AuthInterceptor implements HandlerInterceptor {

private final AuthCheckHandler authCheckHandler;
public static final String MEMBER_ID = "memberId";

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if(handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Optional<Auth> auth = Optional.ofNullable(
handlerMethod.getMethodAnnotation(Auth.class));

if(auth.isEmpty()) {
return true;
}
Long memberId = authCheckHandler.validateAuthority(request, List.of(MemberRole.values()));
request.setAttribute(MEMBER_ID, memberId);
return true;
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.nice.petudio.global.auth.handler;

import com.nice.petudio.api.member.service.MemberServiceUtils;
import com.nice.petudio.domain.member.Member;
import com.nice.petudio.domain.member.MemberRole;
import com.nice.petudio.domain.member.repository.MemberRepository;
import com.nice.petudio.global.jwt.JwtTokenService;
import com.nice.petudio.global.exception.ForbiddenException;
import com.nice.petudio.global.exception.UnauthorizedException;
import com.nice.petudio.global.exception.ValidationException;
import com.nice.petudio.global.exception.error.ErrorCode;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

@RequiredArgsConstructor
@Component
public class AuthCheckHandler {

private final JwtTokenService jwtTokenService;
private final MemberRepository memberRepository;

public final String AUTH_HEADER = "Authorization";
public final String TOKEN_PREFIX = "Bearer ";
private Long memberId;

public Long validateAuthority(HttpServletRequest request, List<MemberRole> requiredRoles) {
String jwtAccessToken = getJwtAccessTokenFromHttpHeader(request);
if (hasAuthority(jwtAccessToken, requiredRoles)) {
return memberId;
}
throw new ForbiddenException(ErrorCode.FORBIDDEN_EXCEPTION, "");
}

private String getJwtAccessTokenFromHttpHeader(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTH_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(TOKEN_PREFIX.length());
}
throw new ValidationException(ErrorCode.INVALID_TOKEN_EXCEPTION, "");
}

public boolean hasAuthority(String jwtAccessToken, List<MemberRole> requiredRoles) {
if (jwtTokenService.validateToken(jwtAccessToken)) {
Optional<Long> memberId = jwtTokenService.parseMemberId(jwtAccessToken);
if(memberId.isPresent()) {
Member member = MemberServiceUtils.findMemberById(memberRepository, memberId.get());
this.memberId = memberId.get();
return isRoleMatch(member, requiredRoles);
}
}
throw new UnauthorizedException(ErrorCode.UNAUTHORIZED_TOKEN_EXCEPTION, "");
}

private static boolean isRoleMatch(Member member, List<MemberRole> requiredRoles) {
return requiredRoles.contains(member.getRole());
}

}
19 changes: 19 additions & 0 deletions src/main/java/com/nice/petudio/global/auth/resolver/MemberId.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.nice.petudio.global.auth.resolver;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* JWT를 파싱하여 MemberId를 흭득하도록 유도하는 애노테이션
* Auth 애노테이션과 함께 사용한다.
*
* ex)
* void methodName(@MemberId final Long memberId) {}
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MemberId {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.nice.petudio.global.auth.resolver;

import static com.nice.petudio.global.auth.auth.AuthInterceptor.MEMBER_ID;

import com.nice.petudio.global.auth.auth.Auth;
import com.nice.petudio.global.exception.InternalServerException;
import com.nice.petudio.global.exception.error.ErrorCode;
import java.util.Optional;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@Component
public class MemberIdResolver implements HandlerMethodArgumentResolver {

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(MemberId.class) && Long.class.equals(parameter.getParameterType());
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Optional<Auth> auth = Optional.ofNullable(parameter.getMethodAnnotation(Auth.class));
if (auth.isEmpty()) {
throw new InternalServerException(ErrorCode.INTERNAL_SERVER_EXCEPTION,
"@MemberId 애노테이션을 적용한 메서드에 Auth 관련 애노테이션이 존재하지 않습니다.");
}

Optional<Object> object = Optional.ofNullable(webRequest.getAttribute(MEMBER_ID, 0));
return object.orElseThrow(() ->
new InternalServerException(ErrorCode.INTERNAL_SERVER_EXCEPTION,
"memberId를 확인할 수 없습니다. Auth 관련 로직을 확인해주세요."));

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.nice.petudio.global.util.redis;
package com.nice.petudio.global.config.redis.constant;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.nice.petudio.global.auth.jwt;
package com.nice.petudio.global.jwt;

import com.nice.petudio.global.util.jwt.JwtKey;
import com.nice.petudio.global.util.redis.RedisKey;
import com.nice.petudio.global.jwt.constant.JwtKey;
import com.nice.petudio.global.config.redis.constant.RedisKey;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.nice.petudio.global.util.jwt;
package com.nice.petudio.global.jwt.constant;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand Down

0 comments on commit 0f2687e

Please sign in to comment.