이지은님의 블로그
250306 - Java Spring Java security 없이 세션, jwt를 사용해 인증과 인가 구현하기(session, jwt, cookie) 본문
250306 - Java Spring Java security 없이 세션, jwt를 사용해 인증과 인가 구현하기(session, jwt, cookie)
queenriwon3 2025. 3. 7. 04:06▷ 오늘 배운 것
인증과 인가 그리고 jwt에 관해 총정리한 문서를 작성해보려고 한다.(Security 없이)
<<목차>>
1. 로그인 인증
1) 세션사용
2) jwt사용
2. 인가
1) interceptor를 사용하는 방법
2) AOP를 사용하는 방법
1. 로그인 인증
1) 세션사용
처음에는 로그인 인증을 세션을 사용해서 로그인 인증을 해왔었다.
1️⃣ 사용자 정보를 세션에 저장하기
@PostMapping("/login")
public ResponseEntity<ApiResponseDto<?>> loginMember(
@RequestBody LoginRequestDto dto,
HttpServletRequest httpServletRequest
) {
HttpSession httpSession = httpServletRequest.getSession(false);
if (httpSession == null) {
httpSession = httpServletRequest.getSession();
}
Member findLoginMember = memberService.loginMember(dto.getEmail(), dto.getPassword());
if (findLoginMember != null) {
httpSession.setAttribute(LOGIN_MEMBER, new SessionMemberDto(
findLoginMember.getId(),
findLoginMember.getUsername(),
findLoginMember.getNickname(),
findLoginMember.getEmail()
));
httpSession.setMaxInactiveInterval(1800); // 세션 만료시간 30분
return ResponseEntity.ok(ok(null));
}
throw new NotFoundException.MemberNotFoundException(MEMBER_NOT_FOUND);
}
httpServletRequest.getSession(false)와 httpServletRequest.getSession()의 차이점에 대해 살펴봐야하는데,
다음과 같은 차이점이 있다.
httpServletRequest.getSession(false) | httpServletRequest.getSession() | |
세션이 있을 경우 | 기존 세션 반환 | 기존 세션 반환 |
세션이 없을 경우 | Null 반환 | 새로운 빈 세션 반환 |
로그인은 새로운 빈 세션에 사용자 정보값을 담아야한다. 결국 .getSession(true)를 가져와야한다는 뜻이다.
세션을 가져온 후 Service레이어에서 세션에 담을 사용자 정보를 가져온다. 보통 이메일을 통해 사용자를 조회하여 비밀번호가 일치하는지 확인한 후 사용자 정보를 가져온다.
httpSession.setAttribute(key, value);
그리고 가져온 사용자 정보를 DTO의 형태로 넣는다.
httpSession.setMaxInactiveInterval(1800);
세션 만료기간은 다음 메서드를 통해 넣을 수 있다 밀리초 단위로 1800밀리초 즉 30분동안 세션이 유효하다.
여기서 키 값을 LOGIN_MEMBER라는 상수로 설정했는데
public class EntityConstants {
private EntityConstants() {
}
public static final String LOGIN_MEMBER = "member";
}
여기서 문자열의 상수값은 Constants로 관리할 수 있다. 위의 세션의 이름은 “member”로 “member”를 키로 설정하여 세션을 저장할 수 있다. 또한 LOGIN_MEMBER이름으로 세션을 불려올 수도 있다.
2️⃣ 로그인 인증
interceptor에서 세션을 가져와서 로그인 사용자를 판단한다.
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// swagger 얼리리턴
String requestURI = request.getRequestURI();
if (requestURI.contains("/swagger-ui/") || requestURI.contains("/v3/api-docs")) {
return true;
}
/* try-catch 로 수정 */
try {
HandlerMethod handlerMethod = (HandlerMethod) handler;
if (isLoginRequired(handlerMethod)) {
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute(LOGIN_MEMBER) == null) {
throw new UnauthorizedException.LoginRequiredException(LOGIN_REQUIRED);
}
SessionMemberDto loginMember = (SessionMemberDto) session.getAttribute(LOGIN_MEMBER);
log.info("로그인한 사용자(id, userName, nickName, email) = {}, {}, {}, {}",
loginMember.getId(),
loginMember.getUsername(),
loginMember.getNickname(),
loginMember.getEmail()
);
return true;
}
return true;
} catch (ClassCastException e) {
throw new BadRequestException.InvalidUrlOrMethodException(URL_OR_METHOD_ERROR);
}
}
private boolean isLoginRequired(HandlerMethod handlerMethod) {
if (handlerMethod != null) {
return handlerMethod.hasMethodAnnotation(LoginRequired.class);
}
return false;
}
}
만약 @LoginRequired 어노테이션이 있는 컨트롤러의 경우 로그인이 필요한 메서드이므로,
로그인 유저인지 아닌지를 세션을 들고옴으로써 확인할 수 있다. 세션값이 있다면 로그인 유저, 세션값이 없다면 비로그인 유저의 접근이므로 차단한다.
SessionMemberDto loginMember = (SessionMemberDto) session.getAttribute(LOGIN_MEMBER);
.getAttribute()로 세션을 가져올 수 있다.
3️⃣ 세션으로 사용자정보 가져오기
@LoginRequired
@PostMapping
public ResponseEntity<ApiResponseDto<?>> createPost(
@SessionAttribute(name = LOGIN_MEMBER) SessionMemberDto session,
@Valid @RequestBody PostRequestDto requestDto
) {
log.info("게시물 생성 성공");
return ResponseEntity.ok(ok(postService.createPost(session, requestDto)));
}
@SessionAtrribute(name = “ “)을 통해서 사용자 정보값이 들어있는 세션을 가져올 수 있다.
이경우 사용자 정보를 굳이 userRepository에서 조회하지 않아도 세션에서 사용자 정보값을 가져올 수 있다.
그러나
세션안에 값이 수정될 수도 있다. 만약 유저 이름이 수정되어 DB에 저장될 경우 세션 역시 값을 변경해주어야한다.
더 좋은 방법이 있을수도 있지만
회원 정보 수정시 세션값을 수정하여 저장하도록 했다.
@LoginRequired
@PatchMapping("/profile")
public ResponseEntity<ApiResponseDto<?>> updateMemberProfile(
@SessionAttribute(name = LOGIN_MEMBER) SessionMemberDto session,
@Valid @RequestBody MemberUpdateProfileRequestDto dto,
HttpServletRequest httpServletRequest
){
if (dto.getNickname() != null) {
HttpSession httpSession = httpServletRequest.getSession(false);
httpSession.setAttribute(LOGIN_MEMBER, session.setNickname(dto.getNickname()));
}
log.info("본인 프로필 수정 성공");
return ResponseEntity.ok(ok(memberService.updateMemberProfile(session, dto)));
}
4️⃣ 로그아웃으로 세션 사용 만료
@LoginRequired
@GetMapping("/logout")
public ResponseEntity<ApiResponseDto<?>> logoutMember(HttpServletRequest httpServletRequest) {
HttpSession session = httpServletRequest.getSession(false);
if (session != null) {
session.invalidate();
}
log.info("로그아웃 성공");
return ResponseEntity.ok(ok(null));
}
세션 만료의 경우 그냥 세션을 가져와서 만료시키는 코드를 작성하면 된다.
session.invalidate();
2) jwt사용
1️⃣ JwtUtill
@Slf4j(topic = "JwtUtil")
@Component
public class JwtUtil {
private static final String BEARER_PREFIX = "Bearer ";
private static final long ACCESS_TOKEN_TIME = 60 * 60 * 1000L; // access 토큰 시간
@Value("${jwt.secret.key}")
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
/* Access Token 생성 */
public String createAccessToken(Long userId, String email, UserRole userRole) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(String.valueOf(userId))
.setClaims(createAccessTokenClaims(email, userRole))
.setExpiration(new Date(date.getTime() + ACCESS_TOKEN_TIME))
.setIssuedAt(date)
.signWith(key, signatureAlgorithm)
.compact();
}
/* 토큰 추출 */
public String substringToken(String tokenValue) {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
return tokenValue.substring(7);
}
throw new UnauthorizedException("Not Found Token");
}
/* 토큰 검증 및 사용자 정보 추출*/
public Claims extractClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
private static Map<String, Object> createAccessTokenClaims(String email, UserRole userRole) {
Map<String, Object> map = new HashMap<>();
map.put("email", email);
map.put("userRole", userRole);
return map;
}
}
이 jwtUtil설명은 아래 블로그에 자세하게 작성하였으니 생략하도록 하겠다.
https://queenriwon3.tistory.com/126
250304 - Java Spring JWT 로그인 인증방식 구현하기
▷ 오늘 배운 것jwt를 구현하는 방법에 대해 TIL을 작성해보고자 한다.Jwt를 사용하여 회원가입, 로그인, 로그아웃, 리플레시 토큰 발급을 구현해보자. >1. JwtUtil 1) 각 필드 소개 2) 생성
queenriwon3.tistory.com
좀더 추가설명을 해보자면, Access Token은 사용자 정보를 담는 대신에, Refresh Token은 토큰 재발급을 위한 것이므로, 사용자 정보를 많이 담을 필요는 없다. 따라서 어떤 유저인지 판단할 수 있는 id값, subject만 저장하는데
다음과 같이 Refresh Token을 생성해 줄 수도 있다.
/* Refresh Token 생성 */
public String createRefreshToken(Long userId, String email, UserRole userRole) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(String.valueOf(userId))
.setExpiration(new Date(date.getTime() + ACCESS_TOKEN_TIME))
.setIssuedAt(date)
.signWith(key, signatureAlgorithm)
.compact();
}
근데 보통 refresh token은 userId와 함께 DB에 저장된다. 그래서 jwt형태로 저장하는 것보다는 UUID를 사용하여 랜덤값을 생성해서 저장한다.
2️⃣ RefreshToken 엔티티
@Entity
@Getter
@NoArgsConstructor
@Table(name = "refresh_tokens")
public class RefreshToken extends Timestamped {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
private Long userId;
private String token;
@Enumerated(STRING)
private TokenStatus tokenStatus;
public RefreshToken(Long userId) {
this.userId = userId;
this.token = UUID.randomUUID().toString();
this.tokenStatus = TokenStatus.VALID;
}
public void updateTokenStatus(TokenStatus tokenStatus){
this.tokenStatus = tokenStatus;
}
}
RefreshToken엔티티를 생성할때 기본 생성값으로 UUID를 사용한다. 거기에 VALID 상태를 주어서 저장하면 기본적인 RefreshToken를 생성할 수 있다.

토큰을 저장한 DB상태이다.
개인적으로는 7일을 체크해서 자동으로 INVALIDATED되도록 하고 싶은데, Redis TTL를 사용하여 DB에 넣지 않으니, 이 상태가 최선이라고 생각한다.
3️⃣ 로그인과 refresh Token을 쿠키에 저장
로그인을 할 때 이메일과 비밀번호를 입력하여 토큰을 출력한다.
// Controller
@PostMapping("/login")
public Response<AuthAccessResponse> login(@Valid @RequestBody AuthLoginRequest authLoginRequest, HttpServletResponse response) {
AuthTokenResponse authTokenResponse = authService.login(authLoginRequest);
setRefreshTokenCookie(response, authTokenResponse.getRefreshToken());
AuthAccessResponse authAccessResponse = new AuthAccessResponse(authTokenResponse.getAccessToken());
return Response.of(authAccessResponse);
}
// Service
@Transactional
public AuthTokenResponse login(AuthLoginRequest request) {
User user = userService.findUserByEmailOrElseThrow(request.getEmail());
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
throw new UnauthorizedException("잘못된 비밀번호입니다.");
}
return getTokenResponse(user);
}
/* Access Token, Refresh Token 생성 및 저장 */
private AuthTokenResponse getTokenResponse(User user) {
String accessToken = tokenService.createAccessToken(user);
String refreshToken = tokenService.createRefreshToken(user);
return new AuthTokenResponse(accessToken, refreshToken);
}
/* Access Token 생성 */
public String createAccessToken(User user) {
return jwtUtil.createAccessToken(user.getId(), user.getEmail(), user.getUserRole());
}
/* Refresh Token 생성 */
public String createRefreshToken(User user) {
RefreshToken refreshToken = refreshTokenRepository.save(new RefreshToken(user.getId()));
return refreshToken.getToken();
}
유저를 가져와서 로그인에 대한 인증을 끝내고 사용자 정보를 저장하는 토큰을 생성한다.
Postman 결과로 Access Token을 응답한다.

여기서 아래 블로그에 상세히 썼듯이, 리플래시 토큰과 액세스 토큰은 그 역할이 다르기 때문에 따로 저장 및 관리를 해야한다.
https://queenriwon3.tistory.com/127
250305 - Java Spring 아웃소싱 프로젝트 구현과 트러블 슈팅: Filter와 OncePerRequestFilter의 차이점, 테스
▷ 오늘 배운 것아웃소싱 팀프로젝트를 진행하면서, 새로 배우게 되거나 트러블 슈팅을 한 내용을 작성해보려고 한다. >1. Filter와 OncePerRequestFilter의 차이점2. 쿠키로 토큰을 관리해보자. 1)
queenriwon3.tistory.com
그래서 refresh token은 최대한 탈취가능성이 낮은 http-only방식을 사용하여 쿠키에 저장하는 것이 좋다. 그래서 쿠키에 어떻게 저장하는가는 다음과 같다.
private void setRefreshTokenCookie(HttpServletResponse response, String refreshToken) {
Cookie cookie = new Cookie("refreshToken", refreshToken);
cookie.setMaxAge(7 * 24 * 60 * 60);
cookie.setSecure(true);
cookie.setHttpOnly(true);
cookie.setPath("/");
response.addCookie(cookie);
}
쿠키의 유효시간을 설정하고, 쿠키에 refresh token을 저장한다.

4️⃣ 로그인 인증
로그인 인증과 인가를 구분하기 위해 로그인 인증은 filter로 구현했다. (여기서는 요청당 하나의 filter만 거칠 수 있도록 OncePerRequestFilter를 사용했다.)
@Slf4j
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String url = request.getRequestURI();
// 회원가입 + (로그인, 토큰 재발급)은 비로그인도 접근 가능
if (url.equals("/auths/login") || url.equals("/auths/refresh")|| url.equals("/users/signup")) {
filterChain.doFilter(request, response);
return;
}
// 토큰 유무 확인 + filter 는 공통 예외처리 불가
String bearerJwt = request.getHeader("Authorization");
if (bearerJwt == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "JWT 토큰이 필요합니다.");
return;
}
String jwt = jwtUtil.substringToken(bearerJwt);
try {
Claims claims = jwtUtil.extractClaims(jwt);
// 유저정보 유무 확인 + filter 는 공통 예외처리 불가
if (claims == null) {
handleException(response, ErrorCode.BAD_REQUEST, "잘못된 JWT 토큰입니다.");
return;
}
request.setAttribute("userId", Long.parseLong(claims.getSubject()));
request.setAttribute("email", claims.get("email"));
request.setAttribute("userRole", claims.get("userRole"));
filterChain.doFilter(request, response);
} catch (Exception e) {
log.error("Invalid JWT token, 유효하지 않는 JWT 토큰 입니다.", e);
handleException(response, ErrorCode.AUTHORIZATION, "유효하지 않는 JWT 토큰입니다.");
}
}
private void handleException(HttpServletResponse response, ErrorCode errorCode, String message) throws IOException {
response.setContentType("application/json");
response.setStatus(errorCode.getStatus().value());
response.setCharacterEncoding("UTF-8");
ErrorResponse errorResponse = ErrorResponse.of(errorCode.getCode(), message);
response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse));
}
}
토큰이 있는지 없는지 확인함으로써 로그인, 비로그인을 확인할 수 있고, request.setAttribute(key, value)를 통해 토큰에 있는 값을 전달할 수 있다.
5️⃣ 토큰에서 사용자정보 가져오기
쿠키에서 사용자 정보를 가져오기 위해 argument resolver를 사용한다.
public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean hasAuthAnnotation = parameter.getParameterAnnotation(Auth.class) != null;
boolean isAuthUserType = parameter.getParameterType().equals(AuthUser.class);
if (hasAuthAnnotation != isAuthUserType) {
throw new UnauthorizedException("@Auth와 AuthUser 타입은 함께 사용되어야 합니다.");
}
return hasAuthAnnotation;
}
@Override
public Object resolveArgument(
@Nullable MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory
) {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
Long userId = (Long) request.getAttribute("userId");
String email = (String) request.getAttribute("email");
UserRole userRole = UserRole.of((String) request.getAttribute("userRole"));
return new AuthUser(userId, email, userRole);
}
}
https://queenriwon3.tistory.com/127
250305 - Java Spring 아웃소싱 프로젝트 구현과 트러블 슈팅: Filter와 OncePerRequestFilter의 차이점, 테스
▷ 오늘 배운 것아웃소싱 팀프로젝트를 진행하면서, 새로 배우게 되거나 트러블 슈팅을 한 내용을 작성해보려고 한다. >1. Filter와 OncePerRequestFilter의 차이점2. 쿠키로 토큰을 관리해보자. 1)
queenriwon3.tistory.com
쿠키를 쉽게 전달하기 위해서 argument resolver를 사용한다.
SupportParameter()메서드는 @Auth어노테이션이 붙어있고, 타입이 AuthUser이라면 값을 가져올 수 있도록 한다.
resolverArgument()에는 위 조건이 충족됐을 때, 쿠키를 가져오는 방법을 작성한다.
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
Long userId = (Long) request.getAttribute("userId");
6️⃣ refresh Token 만료로 로그아웃
로그아웃의 경우 쿠키값을 가져와 그 사용자 정보에 해당되는 리플래시 토큰을 만료하면서 구현할 수 있다.
// Controller
@GetMapping("/logout")
public Response<Void> logout(@Auth AuthUser authUser) {
authService.logout(authUser);
return Response.empty();
}
// Service
/* Refresh token 만료로 로그아웃 */
@Transactional
public void logout(AuthUser authUser) {
tokenService.revokeRefreshToken(authUser.getId());
}
/* Refresh Token 만료 */
public void revokeRefreshToken(Long userId) {
RefreshToken refreshToken = findRefreshTokenById(userId);
refreshToken.updateTokenStatus(INVALIDATED);
}
DB에 저장된 TokenStatus의 상태를 INVALIDATED로 업데이트 하는 방법으로 사용을 만료시킬 수 있다.
7️⃣ Access Token 재발급과 쿠키에서 토큰 가져오기
Access Token이 만료되면 401에러가 발생하면서 api사용이 제한된다. 따라서 refresh Token으로 토큰을 발급받아야한다. (Refresh Token을 발급 받는다면, refresh Token Rotation 방식이다.)
// Controller
@GetMapping("/refresh")
public Response<AuthAccessResponse> reissueAccessToken(@RefreshToken String refreshToken, HttpServletResponse response) {
AuthTokenResponse authTokenResponse = authService.reissueAccessToken(refreshToken);
setRefreshTokenCookie(response, authTokenResponse.getRefreshToken());
AuthAccessResponse authAccessResponse = new AuthAccessResponse(authTokenResponse.getAccessToken());
return Response.of(authAccessResponse);
}
여기서 refresh Token을 받아들이는 방식으로 argument resolver를 사용했다.
public class RefreshTokenArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean hasRefreshTokenAnnotation = parameter.getParameterAnnotation(RefreshToken.class) != null;
boolean isStringType = parameter.getParameterType().equals(String.class);
if (hasRefreshTokenAnnotation != isStringType) {
throw new UnauthorizedException("@RefreshToken과 String 타입은 함께 사용되어야 합니다.");
}
return hasRefreshTokenAnnotation;
}
@Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("refreshToken".equals(cookie.getName())) {
return cookie.getValue();
}
}
}
throw new UnauthorizedException("리프레시 토큰이 존재하지 않습니다. 다시 로그인 해주세요.");
}
}
@RefreshToken 과 타입이 String 일경우 실행되도록 했으며, 쿠키를 가져오는 로직을 작성했다.
// Service
/* Access Token, Refresh Token 재발급 */
@Transactional
public AuthTokenResponse reissueAccessToken(String refreshToken) {
User user = tokenService.reissueToken(refreshToken);
return getTokenResponse(user);
}
/* Access Token, Refresh Token 생성 및 저장 */
private AuthTokenResponse getTokenResponse(User user) {
String accessToken = tokenService.createAccessToken(user);
String refreshToken = tokenService.createRefreshToken(user);
return new AuthTokenResponse(accessToken, refreshToken);
}
/* Refresh Token 유효성 검사 */
public User reissueToken(String token) {
RefreshToken refreshToken = findByTokenOrElseThrow(token);
if (refreshToken.getTokenStatus() == INVALIDATED) {
throw new UnauthorizedException("사용이 만료된 refresh token 입니다.");
}
refreshToken.updateTokenStatus(INVALIDATED);
return userService.findUserByIdOrElseThrow(refreshToken.getUserId());
}
입력받은 Refresh Token안에는 유저의 Id가 있어 이를 불러와서 DB에서 Refresh Token을 찾는다. 이 Refresh Token가 유효한 상태인지 확인하고, 토큰을 재발급시킨다.
2. 인가
1) interceptor를 사용하는 방법
public class AuthRoleInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// @AuthPermission 있는지 없는지 확인후 없을시 바로 출력(비로그인자 접근가능 url + USER)
AuthPermission authPermission = handlerMethod.getMethodAnnotation(AuthPermission.class);
if (authPermission == null) {
return true;
}
// 토큰에서 유저 정보 가저옴
UserRole userRole = UserRole.of((String) request.getAttribute("userRole"));
if (userRole == null) {
throw new UnauthorizedException("로그인이 필요합니다.");
}
// 유저 권한 체크
if (authPermission.role() != userRole) {
throw new ForbiddenException("이용 권한이 없습니다.");
}
return true;
} catch (ClassCastException e) {
throw new BadRequestException("잘못된 요청값입니다.");
}
}
}
먼저 @AuthPermission이 있는지 없는지를 확인하고 없다면 바로 접근을 통과 시킨다. 만약 있다면 접근 권한이 제한되어있는 상태이므로, Filter를 지나온 requset에서 유저 정보를 가져온 다음 유저 권한을 확인한다.
만약 @AuthPermission에서 지정한 UserRole과 사용자의 UserRole이 같다면 통과 가능하다.
어노테이션은 다음과 같이 설정했으며(배열로 여러 권한을 설정할 수도 있다)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuthPermission {
UserRole role(); // 배열 대신 단일 값
}
다음과 같이 사용된다.(가게 작성은 가게 주인만 접근할 수 있도록 어노테이션 설정)
/* 가게 작성 */
@AuthPermission(role = UserRole.OWNER)
@PostMapping
public Response<Void> createStore(
@Auth AuthUser authUser,
@Valid @RequestBody StoreCreateRequest storeCreateRequest
) {
storeService.createStore(authUser, storeCreateRequest);
return Response.empty();
}
2) AOP를 사용하는 방법
@Aspect
@Component
public class AuthPermissionAspect {
// @AuthPermission 어노테이션이 붙은 메소드가 실행될 때마다 권한 체크
@Around("@annotation(authPermission)") // @AuthPermission 어노테이션을 찾음
public Object checkPermission(ProceedingJoinPoint joinPoint, AuthPermission authPermission) throws Throwable {
// 인증된 사용자 정보 가져오기
AuthUser authUser = (AuthUser) joinPoint.getArgs()[0]; // 첫 번째 인자가 AuthUser로 들어오는 경우
// 권한이 일치하지 않으면 예외를 발생시킴
if (authUser == null || !authUser.getUserRole().equals(authPermission.role())) {
throw new ForbiddenException("권한이 없습니다.");
}
// 권한 체크가 통과되면 원래의 메소드 실행
return joinPoint.proceed();
}
}
interceptor와 기능은 비숫하게 AOP를 작성할 수 있다. 개인적인 생각은 filter는 로그인 인증 필터로 사용하고, interceptor는 인가, AOP는 로깅하는데 사용하는 것이 좋다고 생각은 들지만 AOP 또한 횡단관심사를 사용한 인가를 구현할 수 있다.