인증 API 구현
UserController
package com.jh.jwt.controller;
import com.jh.jwt.service.UserService;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.apache.tomcat.util.http.parser.Authorization;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/login")
public ResponseEntity<LoginResponseDto> login(@RequestBody LoginRequestDto dto) {
return ResponseEntity.ok().body(userService.login(dto.getUsername(), dto.getPassword()));
}
@PostMapping("/sign")
public Long sign(@RequestBody RequestSignupDto dto) {
return ResponseEntity.ok().body(userService.join(dto.getUsername(), dto.getPassword(), dto.getAuth())).getBody();
}
@PostMapping("/refresh")
public ResponseEntity<LoginResponseDto> refresh(HttpServletRequest request) {
return ResponseEntity.ok().body(userService.refresh(request));
}
@PostMapping("/logout")
public ResponseEntity<String> logout(HttpServletRequest request) {
return ResponseEntity.ok().body(userService.logout(request));
}
@Data
public static class RefreshTokenRequestDto {
private String token;
}
@Data
public static class LoginRequestDto {
private String username;
private String password;
}
@Data
public static class RequestSignupDto {
String username;
String password;
String auth;
}
}
- 기본적으로 로그인, 회원가입, 리프레시 기능이 있음
- 리프레시는 accessToken이 만료되었을 때 호출하는 API
- Dto는 요청 받을 때 필요한 타입들을 명시해둠
LoginResponseDto
package com.jh.jwt.controller;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class LoginResponseDto {
private String accessToken;
private String refreshToken;
public LoginResponseDto(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
- 로그인이나 리프레시가 성공적으로 수행되었을 때 반환되는 형태
UserService
package com.jh.jwt.service;
import com.jh.jwt.controller.LoginResponseDto;
import com.jh.jwt.controller.UserController;
import com.jh.jwt.domain.Authority;
import com.jh.jwt.domain.CustomUserDetails;
import com.jh.jwt.domain.User;
import com.jh.jwt.repository.UserRepository;
import com.jh.jwt.utils.RedisUtil;
import com.jh.jwt.utils.TokenProvider;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final RedisUtil redisUtil;
protected final Logger logger = LoggerFactory.getLogger(UserService.class);
private final PasswordEncoder passwordEncoder;
private final TokenProvider tokenProvider;
@Transactional
public Long join(String username, String password, String auth){
User user = User.builder()
.username(username)
.password(passwordEncoder.encode(password))
.authority(Authority.valueOf(auth))
.build();
return userRepository.save(user);
}
@Transactional
public LoginResponseDto login(String username, String password){
// 회원 validation
List<User> users = userRepository.findByUsername(username);
if (users.isEmpty()) {
throw new UsernameNotFoundException("존재하지 않는 회원입니다.");
}
// 비밀번호 체크
if (!passwordEncoder.matches(password, users.get(0).getPassword())) {
throw new BadCredentialsException("비밀번호가 틀렸습니다.");
}
// username과 password를 통하여 UsernamePasswordAuthenticationToken을 생성
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
logger.debug("JwtAuthenticationFilter : 토큰생성완료");
// authenticationToken을 통하여 Authentication을 생성
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
// authentication을 통해 미리 정의했던 CustomUserDetails형태의 principal을 생성
CustomUserDetails principal = (CustomUserDetails) authentication.getPrincipal();
// principal을 이용하여 Jwt토큰 생성
String accessToken = tokenProvider.createAccessJwt(principal);
String refreshToken = tokenProvider.createRefreshJwt(principal);
users.get(0).setRefreshToken(refreshToken);
return LoginResponseDto.builder().accessToken(accessToken).refreshToken(refreshToken).build();
}
// AccessToken이 만료되었지만 refreshToken이 유효하다면 Token들을 재발급 해준다.
public LoginResponseDto refresh(HttpServletRequest request) {
// 헤더에서 refreshToken정보를 꺼내온다.
String refreshToken = tokenProvider.resolveToken(request);
// refreshToken 유효성 검사
boolean validate = tokenProvider.validateRefreshToken(refreshToken);
// user 정보로 정의했던 CustomUserDetails를 만들어 Jwt토큰 생성
String username = tokenProvider.getRefreshUsername(refreshToken);
List<User> user = userRepository.findByUsername(username);
if (!user.get(0).getRefreshToken().equals(refreshToken)){
throw new BadCredentialsException("잘못된 접근입니다.");
}
CustomUserDetails cud = new CustomUserDetails(user.get(0));
String newAccessToken = tokenProvider.createAccessJwt(cud);
String newRefreshToken = tokenProvider.createRefreshJwt(cud);
return LoginResponseDto.builder().accessToken(newAccessToken).refreshToken(newRefreshToken).build();
}
// 레디스 블랙리스트에 accessToken값을 저장
// accessToken이 만료되지 않았을 때 로그아웃하면 재사용이 불가능 하도록 Redis에 저장해둠
public String logout(HttpServletRequest request) {
String accessToken = tokenProvider.resolveToken(request);
String username = tokenProvider.getUsername(accessToken);
List<User> user = userRepository.findByUsername(username);
user.get(0).setRefreshToken("");
redisUtil.setBlackList(accessToken, "accessToken", 5);
return "로그아웃";
}
}
UserRepository
package com.jh.jwt.repository;
import com.jh.jwt.domain.User;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Repository
public class UserRepository{
@PersistenceContext
private EntityManager em;
// 데이터베이스에 사용자 저장
public Long save(User user) { em.persist(user); return user.getId();}
// 사용자이름을 통해 사용자 조회
public List<User> findByUsername(String username) {
return em.createQuery("select u from User u where u.username = :username", User.class)
.setParameter("username", username)
.getResultList();
}
// 사용자 id를 통해 사용자 조회
public User findOne(Long id) { return em.find(User.class, id);}
}
로그아웃과 관련된 내용은 다음 게시글에 Redis 설정부터 올리겠습니다.
'Spring > Server' 카테고리의 다른 글
Spring JPA + JWT로 로그인 구현하기(5) - 예외처리 (1) | 2023.10.20 |
---|---|
Spring JPA + JWT로 로그인 구현하기(5) - 로그아웃 구현(Redis) (1) | 2023.10.05 |
Spring JPA + JWT로 로그인 구현하기(3) - Configuration 구현 (0) | 2023.08.18 |
Spring JPA + JWT로 로그인 구현하기(2) - 엔티티 구현 (0) | 2023.08.05 |
Spring JPA + JWT로 로그인 구현하기(1) - 기본 설정 (0) | 2023.08.04 |