#4 로그인 개발

안떽왕·2023년 10월 19일
0

seamless 프로젝트

목록 보기
4/5

DTO 작성

public class LoginRequestDto {
    @Email
    @NotBlank(message = "이메일을 입력해주세요")
    private String email;

    @NotBlank(message = "비밀번호를 입력해주세요")
    private String password;
    
    # getter setter 메서드 생략..
}

로그인할때 사용할 DTO를 작성했습니다.

로그인 시에는 이메일과 비밀번호만 필요하기에 해당 필드를 작성해주고 이메일에 유효성검사를 할 수있는 어노테이션인 @Email과 null과 빈값을 허용하지 않는 @NotBlank 어노테이션을 달아주었습니다.

jwt 토큰

build.gradle 의존성 확인

dependencies {
	implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
	implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
	implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
}

사용할 jwt라이브러리와 버전을 잘 확인해야합니다.

시크릿 키 생성

jwt 시크릿키는 특정 비트길이 이상의 글자 수와 특정문자는 사용할 수 없는 제약이 걸려있어 외울 수 있는 비밀번호를 만들기란 어려운 일입니다.

public class SecretKeyGenerator {

    public static void main(String[] args) {
        String secretKey = generateSecureSecretKey();
        System.out.println(secretKey);
    }

    public static String generateSecureSecretKey() {
        SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS512);
        return Base64.getEncoder().encodeToString(key.getEncoded());
    }
}

저는 SecretKeyGenerator라는 클래스를 별도로 만들어서 안전한 암호키를 생성받아 진행하였습니다.

HS512알고리즘으로 키를 생성하고 Base64로 인코딩 된 안전한 암호키를 생성할 수 있습니다.

jwt토큰 설정

@Component
public class TokenUtil {
    @Value("${jwt.secret}")
    private String SECRET_KEY;
}

TokenUtil이라는 클래스를 만들고 jwt토큰을 발행하기 위해 @Value 어노테이션을 사용해 main/src/java/resources/application.propertiesjwt.secret이라는 변수를 현재 클래스의 SECRET_KEY라는 변수에 값으로 만들어주었습니다.

application.properties

jwt.secret=<시크릿 키 입력>

예시

jwt.secret=6QnEsd2344RLsSiiIK+Q3443fgsdfgsdfg2vTyI453445igDaKasdfnL5tzcHFY/B43s12d4==

토큰 생성

    private String createToken(UserEntity userEntity, long duration) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + duration);

        return Jwts.builder()
                .setSubject(Long.toString(userEntity.getId()))
                .claim("email", userEntity.getEmail())
                .setIssuedAt(now)  // 발행일
                .setExpiration(expiryDate)  // 만료일
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }

유저정보와 기간을 인자로 받는 createToken메서드를 만들었습니다.

인자로 받은 기간은 현재시간과 합산해 어느 시점에 토큰이 만료될지를 결정해줍니다.

유저정보의 경우 토큰의 payload에 기입할 내용에 쓰이는데 저는 여기서 유저의 id값과 이메일을 payload에 입력해주었습니다.

토큰 발급형태 지정

    public static class TokenResponse {
        private String accessToken;
        private String refreshToken;

        public TokenResponse(String accessToken, String refreshToken) {
            this.accessToken = accessToken;
            this.refreshToken = refreshToken;
        }

로그인을 하고 토큰을 발급해줄때 프론트에서 사용하기 용이하도록 발급 형태를 만들었습니다.

프론트에서 확인해보면 아래의 형태를 갖게 됩니다.

{
	accessToken: accessToken,
	refreshToken: refreshToken
}

토큰 발급

    public TokenResponse generateToken(UserEntity userEntity) {
        String accessToken = createToken(userEntity, 15 * 60 * 1000);  // 15분
        String refreshToken = createToken(userEntity, 7 * 24 * 60 * 60 * 1000);  // 7일

        return new TokenResponse(accessToken, refreshToken);
    }

generateToken 이라는 메서드를 만들어 accessToken과 refreshToken변수에 각 토큰별 만료시간을 지정하고 createToken 메서드를 사용해 토큰을 만들어 반환합니다.

이 메서드의 리턴 타입은 방금 전에 만들었던 TokenResponse객체입니다.

Service 작성

이전에 만들었던 UserService 파일로 돌아와서 작성해주었습니다.

의존성 주입

public class UserService {
    private final UserRepository userRepository;
    private final TokenUtil tokenUtil; <-- 추가
    private final PasswordEncoder passwordEncoder;
    
    @Autowired
    public UserService(UserRepository userRepository, TokenUtil tokenUtil, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.tokenUtil = tokenUtil;	<-- 추가
        this.passwordEncoder = passwordEncoder;
    }

UserService에 방금 만들었던 TokenUtil의 의존성을 추가해 주었습니다.
UserService메서드가 갖는 인자에도 반드시 기입해주어야 합니다.

login 메서드 작성

    public TokenUtil.TokenResponse login(String email, String password) throws AuthenticationException {
        Optional<UserEntity> usersOptional = userRepository.findByEmail(email);

        if (usersOptional.isEmpty()) {
            throw new AuthenticationException("해당 이메일이 존재하지 않습니다.");
        }

        UserEntity userEntity = usersOptional.get();

        if (!passwordEncoder.matches(password, userEntity.getPassword())) {
            throw new AuthenticationException("비밀번호가 일치하지 않습니다.");
        }
        return tokenUtil.generateToken(userEntity);
    }

TokenResponse를 객체 타입으로 갖는 login 메서드입니다.

인자로 받은 이메일이 데이터베이스에 존재하는지 확인하고 해시화 된 비밀번호를 인코딩해 인자로 받은 비밀번호와 대조하게 됩니다.

해당 유효성검사가 통과되면 토큰을 발급하도록 만들었습니다.

Controller 작성

마찬가지로 이전에 작성한 UserController파일에서 작업했습니다.

	@PostMapping("/login")
    public ResponseEntity<TokenUtil.TokenResponse> login(@RequestBody LoginRequestDto loginRequestDto) throws AuthenticationException {
        TokenUtil.TokenResponse tokenResponse = userService.login(loginRequestDto.getEmail(), loginRequestDto.getPassword());
        return ResponseEntity.ok(tokenResponse);
    }

유저가 입력한 정보를 가져와 서비스에 있는 로그인 메서드에 넘겨주고 리턴으로 받은 토큰을 넘겨줍니다.

profile
이제 막 개발 배우는 코린이

0개의 댓글