백오피스 만들기 프로젝트 (2): 사용자Role, 로그아웃

김재현·2023년 12월 6일
0

TIL

목록 보기
50/88
post-thumbnail
  • 프로젝트명 : "가시죠, 백오피스 만들기 프로젝트"
  • 프로젝트 소개 : 사용자 Role에 따라 인가할 수 있는 관리자 기능을 만드는 프로젝트입니다.
  • 사용 기술: #Java #Spring Boot #JPA #MySQL #Redis
    GitHub: https://github.com/k-jaehyun/IForest.git

백오피스란 서비스를 제공하기 위하여 상품을 등록하고 마케팅을 설정하고 결제와 매출, 수익 등을 관리하는 서비스를 제공하는 페이지를 뜻한다.

간단하게 관리자 페이지로 이해 할 수 있는데, 그것을 위한 사용자 Role 생성을 진행했다.

그것에 더해 로그아웃 기능을 구현했다. 이전 프로젝트에서는 Redis 를 이용하여 토큰을 저장했는데, 이번엔 협업을 위해 Redis를 따로 설치 할 필요가 없도록 MySQL에 테이블을 따로 만들어서 구현했다.


사용자 Role

사용자를 일반유저와 관리자로 구분하기 위해 다음과 같이 UserRoleEnum을 만들고, 이것을 User의 테이블에 포함시켰다.

UserRoleEnum

public enum UserRoleEnum {

    User(Authority.USER),
    ADMIN(Authority.ADMIN);

    private final String authority;

    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    public String getAuthority() {
        return this.authority;
    }

    public static class Authority {
        public static final String USER = "ROLE_USER";
        public static final String ADMIN = "ROLE_ADMIN";
    }
}

User 엔터티

			...
            
    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING) 
    private UserRoleEnum role;
    		...

@Enumerated

  • @Enumerated은 Enum Type을 DB 컬럼에 저장할 때 사용하는 애너테이션이다.
  • EnumType.STRING: Enum의 이름(열거형 상수의 이름)을 DB에 그대로 저장한다는 것이다. 이 옵션을 빼면 DB의 User테이블에 그저 숫자로 표시가 된다.
  • EnumType.ORDINAL: 열거형 상수의 순서(0부터 시작하는 정수)를 데이터베이스에 저장한다. 이것이 디폴트 옵션이기 때문에 옵션을 제거했을 때 null에 0, 유저에 1, 관리자는 2 로 표시되는걸 확인 할 수 있었다.

Admin Password

우리 팀에서 간단하게 그린 와이어프레임에서는 가입 할 때 Admin Password를 입력한다면 관리자로 가입되는 것이다.(그림 참고)

그렇다면 AdminPW 부분에 아무것도 입력하지 않는다면 일반유저, AdminPW에 관리자 암호를 입력하여 맞다면 관리자로 등록되도록 설계했다.

Service signup 메서드

UserRoleEnum role = UserRoleEnum.User;
if (adminPW!=null && !adminPW.isEmpty()) { 
     if (!jwtUtil.validateAdminPW(adminPW)) {
     	throw new IllegalArgumentException("관리자 암호가 틀려 등록이 불가합니다.");
     }
     role = UserRoleEnum.ADMIN;
}

관리자 암호를 보내주지 않거나 빈칸인 경우에는 if문에 들어가지 않아 일반 유저로 등록되고, 관리자 암호를 보내준경우 validate를 진행하고 Role을 바꿔줬다.


로그아웃 구현

우선 로그인 할 때 토큰이 생성되며, 토큰값이 테이블에 저장되어야했다.
그렇기 때문에 토큰 엔터티를 따로 만들어줬다. user와 1대1 관계이기 때문에 @OneToOne을 사용하고 FK가 토큰 테이블에 있도록 ERD를 수정했기에 아래와 같이 만들었다.

로그아웃 메서드

    public void logout(HttpServletRequest request) {
        String bearerToken = request.getHeader(JwtUtil.AUTHORIZATION_HEADER);
        String token = bearerToken.substring(7);
        Token tokenObject = tokenRepository.findByTokenValue(token).orElseThrow(()-> 
        							new IllegalArgumentException("해당 토큰이 존재하지 않습니다."));
        tokenObject.setUser(null);  // Detach 전에 User 객체를 null로 설정
        tokenRepository.delete(tokenObject);
    }

토큰값을 받아오고 토큰테이블에서 토큰을 삭제하는데, 여기서 문제가 있었다.

1. 토큰을 삭제하면 User에 영향이 있지 않을까?

FK가 Token에 있기에 영향이 없을 것이라 판단이 되지만 혹시 모를 버그를 방지하기위해 @OneToOne(cascade = CascadeType.DETACH) 옵션을 Token 테이블의 User 필드에 추가해줬다.
이 효과를 순서와 함께 살펴보면,

  • tokenRepository.delete(tokenObject)가 실행되면 Token 엔터티는 영속성 컨텍스트에서 DETACH된다.
  • 그 후 Token 엔터티와 User 엔터티 간의 관계에서도 DETACH가 발생한다.(이게 중요)
  • 실제로 데이터베이스에서 토큰 삭제.

안심하고 토큰 삭제를 진행할 수 있었다.

하지만 이번엔 로그인 과정에서 버그가 생겼다.

2. 중복 로그인 문제 발생

중복 로그인 제한이 없었기 때문에 로그인을 시도할 때 마다 서로 다른 토큰이 발행되는 문제가 있었다.

미리 설정해뒀던 토큰 엔터티의 User 필드를 이용,
발급된 토큰이 있는지 확인하고 에러메세지를 반환하는 if문을 삽입하여 해결 할 수 있었다.

if (!tokenRepository.findByUser(user).isEmpty()) {
	Token token = tokenRepository.findByUser(user).orElseThrow(()-> 
    						new IllegalArgumentException("존재하는데 존재하지 않는 발생할리 없는 에러"));
	String beareredToken = jwtUtil.BEARER_PREFIX+token.getTokenValue();
	response.setHeader(JwtUtil.AUTHORIZATION_HEADER,beareredToken);
	return ResponseEntity.status(HttpStatus.BAD_REQUEST.value()).body(
    						new CommonResponseDto("이미 로그인 되어 있습니다.",HttpStatus.BAD_REQUEST.value()));
}

관련 포스팅

Previouse Post

Following Post

profile
I live in Seoul, Korea, Handsome

0개의 댓글