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);
}
}