- 프로젝트명 : "가시죠, 백오피스 만들기 프로젝트"
- 프로젝트 소개 : 사용자 Role에 따라 인가할 수 있는 관리자 기능을 만드는 프로젝트입니다.
- 사용 기술: #Java #Spring Boot #JPA #MySQL #Redis
GitHub: https://github.com/k-jaehyun/IForest.git
백오피스란 서비스를 제공하기 위하여 상품을 등록하고 마케팅을 설정하고 결제와 매출, 수익 등을 관리하는 서비스를 제공하는 페이지를 뜻한다.
간단하게 관리자 페이지로 이해 할 수 있는데, 그것을 위한 사용자 Role 생성을 진행했다.
그것에 더해 로그아웃 기능을 구현했다. 이전 프로젝트에서는 Redis 를 이용하여 토큰을 저장했는데, 이번엔 협업을 위해 Redis를 따로 설치 할 필요가 없도록 MySQL에 테이블을 따로 만들어서 구현했다.
사용자를 일반유저와 관리자로 구분하기 위해 다음과 같이 UserRoleEnum을 만들고, 이것을 User의 테이블에 포함시켰다.
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";
}
}
...
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
private UserRoleEnum role;
...
우리 팀에서 간단하게 그린 와이어프레임에서는 가입 할 때 Admin Password를 입력한다면 관리자로 가입되는 것이다.(그림 참고)
그렇다면 AdminPW 부분에 아무것도 입력하지 않는다면 일반유저, AdminPW에 관리자 암호를 입력하여 맞다면 관리자로 등록되도록 설계했다.
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);
}
토큰값을 받아오고 토큰테이블에서 토큰을 삭제하는데, 여기서 문제가 있었다.
FK가 Token에 있기에 영향이 없을 것이라 판단이 되지만 혹시 모를 버그를 방지하기위해 @OneToOne(cascade = CascadeType.DETACH)
옵션을 Token 테이블의 User 필드에 추가해줬다.
이 효과를 순서와 함께 살펴보면,
- tokenRepository.delete(tokenObject)가 실행되면 Token 엔터티는 영속성 컨텍스트에서 DETACH된다.
- 그 후 Token 엔터티와 User 엔터티 간의 관계에서도 DETACH가 발생한다.(이게 중요)
- 실제로 데이터베이스에서 토큰 삭제.
안심하고 토큰 삭제를 진행할 수 있었다.
하지만 이번엔 로그인 과정에서 버그가 생겼다.
중복 로그인 제한이 없었기 때문에 로그인을 시도할 때 마다 서로 다른 토큰이 발행되는 문제가 있었다.
미리 설정해뒀던 토큰 엔터티의 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()));
}