회원 - 업종 단방향 관계 매핑
auto incresemental id 컬럼을 추가했다. 사업자번호를 기본키로 쓰는 것보다 이게 왜 나은 지는 아직도 납득 못하겠지만 보통 이렇게 한다고 하니까 고쳤다.
사용자를 조회할 때는 업종을 조회하는 경우가 있지만 업종을 조회할 때는 사용자를 조회하는 경우가 없다. 따라서 단방향 매핑만 해도 된다고 생각한다.
// ...
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int userId;
@Column(name = "company_number", unique = true, length = 10)
private String companyNumber;
// ...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "sectorId")
private SectorEntity sector;
// ...
}
처음에는 UserEntity랑 SectorEntity의 auto increase 컬럼 이름(DB 말고 스프링에서)을 id로 해놨었는데, 중복된다고 에러 나서 userId, sectorId 이런 식으로 바꿨다.
public class SectorEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private int sectorId;
@Column(name = "name", nullable = false)
private String name;
}
회원가입
Request 객체의 sectorId로 Sector 객체를 조회한다. 존재하지 않는 sectorId일 경우 예외를 던진다.
기본 프로필 이미지는 application.properties에 저장해놨다.
그리고 원래 setter를 사용했었는데 그러면 안 된다길래 builder로 바꿨다.
public class UserService {
@Value("${spring.user.defaultImage}")
String defaultImgUrl;
// ...
public UserEntity buildUser(RegisterRequest registerRequest) {
SectorEntity sectorEntity = sectorRepository.findById(registerRequest.getSectorId()).orElseThrow(NotExistSectorException::new);
return UserEntity.builder()
.companyNumber(registerRequest.getCompanyNumber())
.password(passwordEncoder.encode(registerRequest.getPassword()))
.businessName(registerRequest.getBusinessName())
.contact(registerRequest.getContact())
.address(registerRequest.getAddress())
.sector(sectorEntity)
.img(defaultImgUrl)
.isMarketing(registerRequest.getIsMarketing())
.build();
}
public void createUser(RegisterRequest registerRequest) {
checkCompanyNumberDuplicate(registerRequest.getCompanyNumber());
UserEntity userEntity = buildUser(registerRequest);
userRepository.save(userEntity);
}
// ...
}
사업자 번호 중복 체크
DTO
@Data
public class CompanyNumberRequest {
@NotBlank(message = "사업자번호 필요")
String companyNumber;
}
Controller
@PostMapping("/company-number")
public ResponseEntity<SuccessResponse> checkCompanyNumberDuplication(@RequestBody @Valid CompanyNumberRequest companyNumberRequest) {
userService.checkCompanyNumberDuplicate(companyNumberRequest.getCompanyNumber());
return ResponseEntity.status(HttpStatus.OK).body(new SuccessResponse());
}
액세스 토큰 재발급
JWT 디코딩
getClaims에서 디코딩을 하고, getCompanyNumberByToken에서 토큰에 적힌 사업자번호를 반환한다.
@Component
public class TokenProvider {
// ...
public Claims getClaims(String token) {
try {
return jwtParser.parseClaimsJws(token).getBody();
} catch(SecurityException | MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException ex) {
throw new JwtException("유효하지 않은 토큰입니다.");
} catch(JwtException ex) {
throw new JwtException(ex.getMessage());
}
}
public String getCompanyNumberFromToken(String token) {
return (String) getClaims(token).get("companyNumber");
}
}
TokenService
1. JWT를 디코딩하여 사업자번호를 가져온다. 이때 JWT에 문제가 있을 경우(기간 만료, 조작 등등) 예외를 던진다.
2. DB에 저장되어 있는 refresh 토큰과 사용자로부터 받은 토큰이 일치하는지 확인한다.
3. 새로운 access 토큰을 발급한다.
public class TokenService {
// ...
public void compareRefreshToken(String refreshToken, String companyNumber) {
TokenEntity tokenEntity = tokenRepository.findByUserCompanyNumber(companyNumber).orElseThrow(InvalidRefreshTokenException::new);
if(!refreshToken.equals(tokenEntity.getRefreshToken())) {
throw new InvalidRefreshTokenException();
}
}
public AccessTokenResponse createNewAccessToken(String refreshToken) {
// JWT 디코딩
String companyNumber = tokenProvider.getCompanyNumberFromToken(refreshToken);
// DB에 저장된 refresh token과 일치하는지 확인
compareRefreshToken(refreshToken, companyNumber);
// 새로운 access token 생성
UserEntity userEntity = userRepository.findByCompanyNumber(companyNumber).orElseThrow(() -> new NotFoundException("존재하지 않는 사용자입니다."));
String accessToken = tokenProvider.createAccessToken(userEntity);
return AccessTokenResponse.builder()
.accessToken(accessToken)
.build();
}
}
UserController
refresh 토큰은 헤더에서 추출한다. @RequestHeader의 required를 true로 하니까 에러 메시지를 내가 직접 지정하는 방법을 모르겠어서 아래처럼 구현했다.
public class UserController {
// ...
@PostMapping("/refresh")
public ResponseEntity<AccessTokenResponse> getNewAccessToken(
@RequestHeader(value = "refresh", required = false, defaultValue = "")
String refreshToken
) {
if(refreshToken.isEmpty()) {
throw new MissingRefreshHeaderException();
}
return ResponseEntity.status(HttpStatus.OK).body(tokenService.createNewAccessToken(refreshToken));
}
}
주저리
나도 Exception 이름 기깔나게 짓고 싶다
내일은 authuorization을 해보자~
'웹 프로그래밍' 카테고리의 다른 글
[Spring Boot] Licruit - 로그인 (0) | 2024.09.12 |
---|---|
[Spring Boot] Licruit - 에러 처리 (1) | 2024.09.11 |
[Spring Boot] Licruit - 회원가입(2) (0) | 2024.09.11 |
[Spring Boot] Licruit - 회원가입 (0) | 2024.09.10 |
[Spring Boot] API 생성 (0) | 2024.07.08 |