Skip to content

Commit

Permalink
refactor/#156 api 인가처리 (#161)
Browse files Browse the repository at this point in the history
* refactor: public endpoint 추가

* refactor: PreAuthorize를 이용한 ROLE 요청 제한

* refactor: ClubAdminController PreAuthorize 추가

* refactor: RestControllerAdvice를 이용한 Exception 커스텀

* refactor: 코드 컨벤션 준수를 위한 HttpStatus static import 변경

* refactor: User의 Role을 BaseRole로 변경 후 common으로 변경

* refactor: getAuthentication Role에 따른 authorities 분기 처리

* refactor: GlobalExceptionHandler로 ExceptionHandler 통합
  • Loading branch information
LeeShinHaeng authored Jan 6, 2025
1 parent 97e8181 commit 63e5fc9
Show file tree
Hide file tree
Showing 16 changed files with 150 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.springframework.http.HttpStatus.NO_CONTENT;

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -25,6 +26,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/abouts")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Tag(name = "About", description = "소개글 관리자 API")
public class AboutAdminController {
private final AboutAdminFacade aboutAdminFacade;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.springframework.http.HttpStatus.CREATED;

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -27,6 +28,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/clubs")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Tag(name = "Club", description = "동아리 관리자 API")
public class ClubAdminController {
private final ClubAdminFacade clubAdminFacade;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package kgu.developers.admin.comment.presentation;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -10,6 +11,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/comments")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Tag(name = "Comment", description = "댓글 관리자 API")
public class CommentAdminController {
private final CommentAdminFacade commentAdminFacade;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -28,6 +29,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/files")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Tag(name = "File", description = "파일 업로드 관리 API")
public class FileAdminController {
private final FileAdminFacade fileAdminFacade;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.springframework.http.HttpStatus.CREATED;

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -27,6 +28,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/labs")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Tag(name = "Lab", description = "연구실 관리자 API")
public class LabAdminController {
private final LabAdminFacade labAdminFacade;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.springframework.http.HttpStatus.CREATED;

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -28,6 +29,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/posts")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Tag(name = "Post", description = "게시글 관리자 API")
public class PostAdminController {
private final PostAdminFacade postAdminFacade;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.springframework.http.HttpStatus.CREATED;

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -27,6 +28,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/professors")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Tag(name = "Professor", description = "교수 관리자 API")
public class ProfessorAdminController {
private final ProfessorAdminFacade professorAdminFacade;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -22,6 +23,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/users")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Tag(name = "User", description = "회원 관리자 API")
public class UserAdminController {
private final UserAdminFacade userAdminFacade;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package kgu.developers.api.auth.application;

import java.time.Duration;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import kgu.developers.api.auth.presentation.exception.TokenNotFoundException;
import kgu.developers.api.auth.presentation.request.LoginRequest;
import kgu.developers.api.auth.presentation.request.RefreshTokenRequest;
Expand All @@ -17,6 +11,11 @@
import kgu.developers.domain.user.domain.User;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;

@Service
@Builder
Expand All @@ -35,8 +34,9 @@ public TokenResponse login(LoginRequest request) {
User user = userQueryService.getUserById(userId);
user.isPasswordMatching(password, passwordEncoder);

String refreshToken = tokenProvider.generateToken(user.getId(), Duration.ofDays(1));
String accessToken = tokenProvider.generateToken(user.getId(), Duration.ofHours(1));
String role = user.getRole().name();
String refreshToken = tokenProvider.generateToken(user.getId(), Duration.ofDays(1), role);
String accessToken = tokenProvider.generateToken(user.getId(), Duration.ofHours(1), role);

refreshTokenRepository.save(RefreshToken.of(userId, refreshToken));
return TokenResponse.of(accessToken, refreshToken);
Expand All @@ -50,8 +50,10 @@ public TokenResponse reissue(RefreshTokenRequest request) {
refreshTokenRepository.delete(refreshTokenEntity);

String userId = refreshTokenEntity.getUserId();
String refreshToken = tokenProvider.generateToken(userId, Duration.ofDays(1));
String accessToken = tokenProvider.generateToken(userId, Duration.ofHours(1));
User user = userQueryService.getUserById(userId);
String role = user.getRole().name();
String refreshToken = tokenProvider.generateToken(userId, Duration.ofDays(1), role);
String accessToken = tokenProvider.generateToken(userId, Duration.ofHours(1), role);
refreshTokenRepository.save(RefreshToken.of(userId, refreshToken));
return TokenResponse.of(accessToken, refreshToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,79 +4,89 @@
import static io.jsonwebtoken.SignatureAlgorithm.HS256;
import static javax.xml.crypto.dsig.SignatureProperties.TYPE;

import java.time.Duration;
import java.util.Collections;
import java.util.Date;
import java.util.Set;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.Builder;
import lombok.RequiredArgsConstructor;
import java.time.Duration;
import java.util.Collections;
import java.util.Date;
import java.util.Set;

@Service
@Builder
@RequiredArgsConstructor
public class TokenProvider {
private final JwtProperties jwtProperties;
private final JwtProperties jwtProperties;

public String generateToken(String userId, Duration expiredAt, String role) {
Date now = new Date();
return makeToken(new Date(now.getTime() + expiredAt.toMillis()), userId, role);
}

private String makeToken(Date expiry, String userId, String role) {
Date now = new Date();
return Jwts.builder()
.setHeaderParam(TYPE, JWT_TYPE)
.setIssuer(jwtProperties.getIssuer())
.setIssuedAt(now)
.setExpiration(expiry)
.setSubject(userId)
.claim("userId", userId)
.claim("role", role)
.signWith(HS256, jwtProperties.getSecretKey())
.compact();
}

public String generateToken(String userId, Duration expiredAt) {
Date now = new Date();
return makeToken(new Date(now.getTime() + expiredAt.toMillis()), userId);
}
public boolean validateToken(String token) {
try {
Jwts.parser()
.setSigningKey(jwtProperties.getSecretKey())
.parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}

private String makeToken(Date expiry, String userId) {
Date now = new Date();
return Jwts.builder()
.setHeaderParam(TYPE, JWT_TYPE)
.setIssuer(jwtProperties.getIssuer())
.setIssuedAt(now)
.setExpiration(expiry)
.setSubject(userId)
.claim("userId", userId)
.signWith(HS256, jwtProperties.getSecretKey())
.compact();
}
public Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
String role = claims.get("role", String.class);
Set<SimpleGrantedAuthority> authorities = getRoles(role);

public boolean validateToken(String token) {
try {
Jwts.parser()
.setSigningKey(jwtProperties.getSecretKey())
.parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
return new UsernamePasswordAuthenticationToken(
new org.springframework.security.core.userdetails.User(
claims.getSubject(),
"",
authorities
), token, authorities
);
}

public Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
Set<SimpleGrantedAuthority> authorities = Collections.singleton(
new SimpleGrantedAuthority("ROLE_USER")
);
return new UsernamePasswordAuthenticationToken(
new org.springframework.security.core.userdetails.User(
claims.getSubject(),
"",
authorities
), token, authorities
);
}
public Set<SimpleGrantedAuthority> getRoles(String role) {
if (role.equals("ADMIN")) {
return Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
if (role.equals("SUPER")) {
return Collections.singleton(new SimpleGrantedAuthority("ROLE_SUPER"));
}
return Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
}

public String getUserId(String token) {
Claims claims = getClaims(token);
return claims.get("userId", String.class);
}
public String getUserId(String token) {
Claims claims = getClaims(token);
return claims.get("userId", String.class);
}

private Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(jwtProperties.getSecretKey())
.parseClaimsJws(token)
.getBody();
}
private Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(jwtProperties.getSecretKey())
.parseClaimsJws(token)
.getBody();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
"/api/v1/auth/**",
"/api/v1/professors",
"/api/v1/abouts",
"/api/v1/clubs"
"/api/v1/clubs",
"/api/v1/posts/**",
"/api/v1/labs",
"/api/v1/comments",
};

CorsConfigurationSource corsConfigurationSource() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package kgu.developers.domain.user.domain;
package kgu.developers.common.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum Role {
public enum BaseRole {

USER("일반사용자"),
ADMIN("관리자"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kgu.developers.common.exception;

import static org.springframework.http.HttpStatus.FORBIDDEN;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum AdminExceptionCode implements ExceptionCode {
NOT_ADMIN(FORBIDDEN, "관리자 전용 API입니다."),
;

private final HttpStatus status;
private final String message;


@Override
public String getCode() {
return this.name();
}
}
Loading

0 comments on commit 63e5fc9

Please sign in to comment.