Spring
Spring Boot
Auth

Auth

HTTP Session

HTTP Session을 파라미터로 받는 메서드가 호출될 때 Spring에서 자동으로 세션을 하나 생성해서 주입해준다.

@RestController
@RequestMapping("/api/account")
public class AccountApiController {
  @PostMapping("/login")
  public void login(
      @RequestBody LoginRequest loginRequest,
      HttpSession httpSession
  ) {
    ...other code
  }
}

setAttribute

세션에 정보를 저장하는 메서드이다.

if (userDto.getPassword().equals(password)) {
  // 세선에 정보를 저장한다.
  httpSession.setAttribute("user", userDto);
} else {}
예시
  public void login(LoginRequest loginRequest, HttpSession httpSession) {
    var id = loginRequest.getId();
    var password = loginRequest.getPassword();
 
    var optionalUser = userRepository.findById(id);
    // 아이디가 존재하는 경우
    if (optionalUser.isPresent()) {
      var userDto = optionalUser.get();
      // 패스워드가 일치하는 경우
      if (userDto.getPassword().equals(password)) {
        // 세선에 정보를 저장한다.
        httpSession.setAttribute("user", userDto);
      } else {
        throw new RuntimeException("Password Not Match");
      }
    } else {
      throw new RuntimeException("User Not Found");
    }
  }

세션을 저장하는데 성공하면 로그인에 실패하든 성공하든을 떠나서 세션을 저장한다.

getAttribute

저장된 세션을 가져오는 메서드

  @GetMapping("/me")
  public UserDto me (
      HttpSession httpSession
  ) {
    var userObject = httpSession.getAttribute("user");
 
    // 세션이 Expired된 경우
    if(userObject == null) {
      return null;
    }
    var userDto = (UserDto) userObject;
    return userDto;
  }

Cookie

로그인시 Cookie 생성

UserService.java
  public void login(
      LoginRequest loginRequest,
      HttpServletResponse httpServletResponse
  ) {
    var id = loginRequest.getId();
    var pw = loginRequest.getPassword();
    var optionalUser = userRepository.findByName(id);
    if(optionalUser.isPresent()) {
      var userDto = optionalUser.get();
 
      if(userDto.getPassword().equals(pw)) {
        // Cookie에 해당 정보를 저장
        var cookie = new Cookie("authorization-cookie", userDto.getId());
        // 우리 회사가 naver이면 naver.com을 넣어주면 된다.
        // 해당하는 도메인에서만 Cookie를 사용할 수 있다.
        ///! 0.0.0.0:8080으로 접속하면 안된다.
        cookie.setDomain("localhost");
        // Cookie를 사용할 수 있는 경로를 지정한다.
        cookie.setPath("/");
        // 몇 초 후에 쿠키를 지우는지를 설정해줄 수 있다.
        // -1을 주면 세션이 유지되는 동안만 사용하는 것이다.
        cookie.setMaxAge(-1);
 
        // JS에서 임의로 Cookie를 읽을 수 없다.
        cookie.setHttpOnly(true);
        // Https에서만 Cookie를 사용할 수 있다.
        // cookie.setSecure(true);
 
        httpServletResponse.addCookie(cookie);
      }
    } else {
      throw new RuntimeException("User Not Found");
    }
  }

쿠키 Annotation으로 가져오기

  @GetMapping("/me")
  public Optional<UserDto> me(
      HttpServletRequest httpServletRequest,
      /// Annotation으로 Cookie 가져오는 방법
      @CookieValue(required = false, name = "authorization-cookie") String authorizationCookie) {
 
    log.info("authorizationCookie : {}", authorizationCookie);
    var optionUserDto = userRepository.findById(authorizationCookie);
    return optionUserDto;
  }

쿠키를 수동으로 가져오기

  @GetMapping("/me")
  public Optional<UserDto> me(
      HttpServletRequest httpServletRequest){
    var cookies = httpServletRequest.getCookies();
 
    /// # 수동으로 Cookie 가져오는 방법
    if(cookies != null) {
      for (var cookie : cookies) {
        log.info("Key : {}, Value : {}", cookie.getName(), cookie.getValue());
      }
    }
  }

JWT

JWT Token Helper 작성

디펜던시 추가
  // JWT
  implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
	runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
	runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
JWT Token Helper
@Component
public class JwtTokenHelper implements TokenHelperInterface {
  @Value("${token.secret.key}")
  private String secretKey;
  @Value("${token.access-token.plus-hour}")
  private Long accessTokenPlushHour;
  @Value("${token.refresh-token.plus-hour}")
  private Long refreshTokenPlusHour;
  
 
  @Override
  public TokenDto issueAccessToken(Map<String, Object> data) {
    /// 만료시간 설정
    var expiredAtLocalDateTime = LocalDateTime.now().plusHours(accessTokenPlushHour);
    /// Date로 변환
    var expiredAt = Date.from(expiredAtLocalDateTime.atZone(ZoneId.systemDefault()).toInstant());
    /// 키 생성
    var keys = Keys.hmacShaKeyFor(secretKey.getBytes());
    /// 토큰 생성
    var jwtToken = Jwts.builder().signWith(keys, SignatureAlgorithm.HS256).setClaims(data).setExpiration(expiredAt).compact();
 
    return TokenDto.builder().token(jwtToken).expiredAt(expiredAtLocalDateTime).build();
  }
 
  @Override
  public TokenDto issueRefreshToken(Map<String, Object> data) {
    var expiredAtLocalDateTime = LocalDateTime.now().plusHours(refreshTokenPlusHour);
    var expiredAt = Date.from(expiredAtLocalDateTime.atZone(ZoneId.systemDefault()).toInstant());
    var keys = Keys.hmacShaKeyFor(secretKey.getBytes());
    
    // 토큰 생성
    var jwtToken = Jwts.builder().signWith(keys, SignatureAlgorithm.HS256).setClaims(data).setExpiration(expiredAt).compact();
 
    return TokenDto.builder().token(jwtToken).expiredAt(expiredAtLocalDateTime).build();
  }
 
  @Override
  public Map<String, Object> validationTokenWithThrow(String token) {
    var key = Keys.hmacShaKeyFor(secretKey.getBytes());
 
    var parser = Jwts.parserBuilder().setSigningKey(key).build();
 
 
    try {
      var result = parser.parseClaimsJws(token);
      var tokenHash = new HashMap<String, Object>(result.getBody());
      // System.out.println("keys : " + tokenHash.keySet());
      // System.out.println("value : " + tokenHash.values());
 
      return tokenHash;
    } catch(Exception e) {
      if(e instanceof io.jsonwebtoken.security.SignatureException) {
        throw new ApiException(TokenError.INVALID_TOKEN, e);
      } else if (e instanceof ExpiredJwtException) {
        throw new ApiException(TokenError.EXPIRED_TOKEN, e);
      } else {
        ///# 알 수 없는 에러 
        throw new ApiException(TokenError.TOEKN_EXCEPTION, e);
      }
    }
  }
}

JWT Token Service

@RequiredArgsConstructor
@Service
public class TokenService {
  private final TokenHelperInterface tokenHelperInterface;
 
  // 토큰 발급
  public TokenDto issueAccessToken(Long userId) {
    var data = new HashMap<String, Object>();
    data.put("userId", userId);
    return tokenHelperInterface.issueAccessToken(data);
  }
 
  // 리프레쉬 토큰 발급
  public TokenDto issueRefreshToken(Long userId) {
    var data = new HashMap<String, Object>();
    data.put("userId", userId);
    return tokenHelperInterface.issueRefreshToken(data);
  }
 
  // 토큰 to User ID
  public Long validationToken(String token) {
    var map = tokenHelperInterface.validationTokenWithThrow(token);
    System.out.println(map.keySet());
    var userId = map.get("userId");
 
    Objects.requireNonNull(userId, ()-> {
      throw new ApiException(ErrorCode.NULL_POINT);
    });
 
    return Long.parseLong(userId.toString());
  }
}

Token Response & Converter

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TokenDto {
  private String token;
  private LocalDateTime expiredAt;
}
TokenConverter
@RequiredArgsConstructor
@Converter
public class TokenConverter {
  public TokenResponse toResponse(
    TokenDto accessToken,
    TokenDto refreshToken
  ) {
    Objects.requireNonNull(accessToken, () -> {throw new ApiException(ErrorCode.NULL_POINT);});
    Objects.requireNonNull(refreshToken, () -> {throw new ApiException(ErrorCode.NULL_POINT); });
    return TokenResponse.builder()
      .accessToken(accessToken.getToken())
      .accessTokenExpiredAt(accessToken.getExpiredAt())
      .refreshToken(refreshToken.getToken())
      .refreshTokenExpiredAt(refreshToken.getExpiredAt())
      .build();
  }
}

TokenBusiness

@Business
@RequiredArgsConstructor
public class TokenBusiness {
  private final TokenService tokenService; 
  private final TokenConverter tokenConverter;
 
  /** 
   * 1. User Entity id 추출
   * 2. access, refresh 발행
   * 3. converter -> token response로 변경
   */
  public TokenResponse issueToken(UserEntity userEntity) {
    return Optional.ofNullable(userEntity).map(ue -> {
      var userId = ue.getId();
 
      var accessToken = tokenService.issueAccessToken(userId);
      var refreshToken = tokenService.issueRefreshToken(userId);
 
      return tokenConverter.toResponse(accessToken, refreshToken);
    }).orElseThrow(()-> new ApiException(ErrorCode.NULL_POINT));
  }
 
  public Long validationAccessToken(String accessToken) {
    var userId = tokenService.validationToken(accessToken);
    return userId;
  }
}

UserBusiness

@RequiredArgsConstructor
@Business
public class UserBusiness {
  private final UserService userService;
  private final UserConverter userConverter;
  private final TokenBusiness tokenBusiness;
 
  ...
 
  /**
   * 1. email, password를 받아서 사용자 체크
   * 2. user entity 로그인 확인
   * 3. 토큰 생성
   * 4. 토큰 리턴
   */
  public TokenResponse login(
      UserLoginRequest request) {
    /// 들어온 email & password가 일치하는지 확인 
    var userEntity = userService.login(request.getEmail(), request.getPassword());
 
    var tokenResponse = tokenBusiness.issueToken(userEntity);
 
    return tokenResponse;
  }
 
  ...
}

UserOpenApiController

UserOpenApiController에서 UserResponse가 아니라 TokenResponse를 return 하는 것으로 변경한다.

@RequiredArgsConstructor 
@RestController
@RequestMapping("/open-api")
public class UserOpenApiController {
  private final UserBusiness userBusiness;
 
  // 사용자 가입 요청
  @PostMapping("/register")
  public Api<UserResponse> register(
    @RequestBody
    @Valid
    UserRegisterRequest request) {
    var response = userBusiness.register(request);
 
    return Api.OK(response);
  }
 
  @PostMapping("/login")
  public Api<TokenResponse> login(
    @Valid
    @RequestBody
    UserLoginRequest request
  ) {
    var response = userBusiness.login(request);
 
    return Api.OK(response);
  }
}