Skip to content

Commit

Permalink
Merge pull request #164 from Gamegoo-repo/feat/154
Browse files Browse the repository at this point in the history
[Feat/154] Security μ„€μ • 및 Jwt @AuthMember 연동
  • Loading branch information
Eunjin3395 authored Jan 17, 2025
2 parents 5840e01 + 79bd632 commit f082226
Show file tree
Hide file tree
Showing 15 changed files with 492 additions and 111 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.security:spring-security-crypto:5.8.0'

// swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'
Expand Down Expand Up @@ -68,7 +68,7 @@ dependencies {
def generated = 'src/main/generated'

// querydsl QClass 파일 생성 μœ„μΉ˜λ₯Ό 지정
tasks.withType(JavaCompile) {
tasks.withType(JavaCompile).configureEach {
options.getGeneratedSourceOutputDirectory().set(file(generated))
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.gamegoo.gamegoo_v2.account.auth.annotation.resolver;

import com.gamegoo.gamegoo_v2.account.auth.annotation.AuthMember;
import com.gamegoo.gamegoo_v2.core.exception.JwtAuthException;
import com.gamegoo.gamegoo_v2.account.auth.security.SecurityUtil;
import com.gamegoo.gamegoo_v2.core.exception.MemberException;
import com.gamegoo.gamegoo_v2.core.exception.common.ErrorCode;
import com.gamegoo.gamegoo_v2.account.member.domain.Member;
import com.gamegoo.gamegoo_v2.account.member.repository.MemberRepository;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
Expand All @@ -32,16 +33,13 @@ public boolean supportsParameter(MethodParameter parameter) {
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
public Object resolveArgument(@NonNull MethodParameter parameter, ModelAndViewContainer mavContainer,
@NonNull NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
Long currentMemberId = SecurityUtil.getCurrentMemberId();
return memberRepository.findById(currentMemberId)
.orElseThrow(() -> new MemberException(ErrorCode.MEMBER_NOT_FOUND));

Long memberId = (Long) request.getAttribute("memberId");
if (memberId == null) {
throw new JwtAuthException(ErrorCode.MEMBER_EXTRACTION_FAILED);
}

return memberRepository.findById(memberId).orElseThrow(() -> new JwtAuthException(ErrorCode.MEMBER_NOT_FOUND));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.gamegoo.gamegoo_v2.account.auth.domain;

public enum Role {
MEMBER, ADMIN
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.gamegoo.gamegoo_v2.account.auth.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gamegoo.gamegoo_v2.core.common.ApiResponse;
import com.gamegoo.gamegoo_v2.core.exception.common.ErrorCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.PrintWriter;

@Slf4j
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
response.setContentType("application/json; charset=UTF-8");
response.setStatus(403);
PrintWriter writer = response.getWriter();

// μ‹€νŒ¨ 응닡
ApiResponse<Object> apiResponse =
ApiResponse.builder()
.code(ErrorCode._FORBIDDEN.getCode())
.message(ErrorCode._FORBIDDEN.getMessage())
.data(null)
.status(ErrorCode._FORBIDDEN.getStatus())
.build();

try {
ObjectMapper objectMapper = new ObjectMapper();
String jsonResponse = objectMapper.writeValueAsString(apiResponse);
writer.write(jsonResponse);
} catch (NullPointerException e) {
log.error("응닡 λ©”μ‹œμ§€ μž‘μ„± μ—λŸ¬", e);
} finally {
if (writer != null) {
writer.flush();
writer.close();
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.gamegoo.gamegoo_v2.account.auth.security;

import com.gamegoo.gamegoo_v2.account.auth.domain.Role;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Getter
public class CustomUserDetails implements UserDetails {

Long memberId;
Role role; // Member, Admin

public CustomUserDetails(Long memberId, Role role) {
this.memberId = memberId;
this.role = role;
}


@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<String> roles = new ArrayList<>();
roles.add("ROLE_" + role);

return roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}

@Override
public String getPassword() {
return "";
}

// getUserId λ©”μ†Œλ“œ λŒ€μ²΄
@Override
public String getUsername() {
return memberId.toString();
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.gamegoo.gamegoo_v2.account.auth.security;

import com.gamegoo.gamegoo_v2.account.auth.domain.Role;
import com.gamegoo.gamegoo_v2.account.member.repository.MemberRepository;
import com.gamegoo.gamegoo_v2.account.member.domain.Member;
import com.gamegoo.gamegoo_v2.core.exception.JwtAuthException;
import com.gamegoo.gamegoo_v2.core.exception.common.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

private final MemberRepository memberRepository;

public UserDetails loadUserByMemberId(Long memberId) throws UsernameNotFoundException {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new JwtAuthException(ErrorCode.MEMBER_NOT_FOUND));

// νƒˆν‡΄ν•œ νšŒμ›μΈ 경우 μ—λŸ¬ λ°œμƒ
if (member.isBlind()) {
throw new JwtAuthException(ErrorCode.INACTIVE_MEMBER);
}

return new CustomUserDetails(member.getId(), Role.MEMBER);
}

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
throw new UnsupportedOperationException("loadUserByUsername(String username) is not supported. Use " +
"loadUserByMemberIdAndSocialId(Long socialId, String role) instead.");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.gamegoo.gamegoo_v2.account.auth.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gamegoo.gamegoo_v2.core.common.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.PrintWriter;

import static com.gamegoo.gamegoo_v2.core.exception.common.ErrorCode._UNAUTHORIZED;

@Slf4j
@Component
public class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setContentType("application/json; charset=UTF-8");
response.setStatus(401);
PrintWriter writer = response.getWriter();

// μ‹€νŒ¨ 응닡
ApiResponse<Object> apiResponse =
ApiResponse.builder()
.code(_UNAUTHORIZED.getCode())
.message(_UNAUTHORIZED.getMessage())
.data(null)
.status(_UNAUTHORIZED.getStatus())
.build();
try {
ObjectMapper objectMapper = new ObjectMapper();
String jsonResponse = objectMapper.writeValueAsString(apiResponse);
writer.write(jsonResponse);
} catch (NullPointerException e) {
log.error("응닡 λ©”μ‹œμ§€ μž‘μ„± μ—λŸ¬", e);
} finally {
if (writer != null) {
writer.flush();
writer.close();
}
}
}

}

Loading

0 comments on commit f082226

Please sign in to comment.