-
Spring boot + React 시작하기 3 (로그인 및 회원가입, 회원 관리 만들기) - 로그인 + 토큰생성DEV/spring 2024. 2. 13. 14:23
1. Spring boot + React 시작하기 1 (로그인 및 회원가입, 회원 관리 만들기) - 기본설정
https://seokbong.tistory.com/246
2. Spring boot + React 시작하기 2 (로그인 및 회원가입, 회원 관리 만들기) - 회원 가입
https://seokbong.tistory.com/247
본 게시물의 흐름도 :
Controller 작성(AuthController) > DTO 작성(LoginDto) > gradle에 validation dependencies 추가 (로그인 필수 값 지정) > LoginDto의 email과 password에 @NotBlank 어노테이션 추가 > UserRepository 작성(email과 password로 값을 찾는 메서드 작성) > AuthService에 login 서비스 작성 > Controller의 @PostMapping("/login”)에 Service 작성 > Postman으로 확인 > + 토큰 생성
환경 : Mac OS, JDK17, Gradle, Spring 3.2.2
1. Controller 작성(AuthController)
AuthController에 login을 작성 (우선 return 값을 null로 지정)
AuthController.java
package com.ssg.demo.v2.ssgdemov2.Controller; import com.ssg.demo.v2.ssgdemov2.Dto.ResponseDto; import com.ssg.demo.v2.ssgdemov2.Dto.SignUpDto; import com.ssg.demo.v2.ssgdemov2.Dto.LoginDto; import com.ssg.demo.v2.ssgdemov2.Service.AuthService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired AuthService authService; @PostMapping("/signUp") public ResponseDto<?> signUp(@RequestBody SignUpDto requestBody) { ResponseDto<?> result = authService.signUp(requestBody); return result; } @PostMapping("/login") public ResponseDto<?> login(@RequestBody LoginDto requestBody) { return null; } }
2. LoginDto 작성
Login 및 LoginResponse를 위한 Dto 작성
LoginDto.java
package com.ssg.demo.v2.ssgdemov2.Dto; import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class LoginDto { @NotBlank // *spring-boot-starter-validation, 필수값 private String email; @NotBlank private String password; }
LoginResponseDto.java
package com.ssg.demo.v2.ssgdemov2.Dto; import com.ssg.demo.v2.ssgdemov2.Entity.UserEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class LoginResponseDto { private String token; private int exprTime; private UserEntity user; }
3. @NotBlank 어노테이션 사용을 위한 dependencies 추가
LoginDto에 @NotBlank 어노테이션을 사용하기 위해(필수값 입력) 하단 dependencies를 추가하자.
build.gradle
dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web-services' testImplementation 'org.springframework.boot:spring-boot-starter-test' // Lombok implementation 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' // Mysql implementation 'mysql:mysql-connector-java:8.0.28' // spring-boot-starter-data-jpa implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // validation << *이 항목을 추가 implementation 'org.springframework.boot:spring-boot-starter-validation' }
4. UserRepository 작성
email/passoword 찾는 메서드 작성
UserRepository.java
package com.ssg.demo.v2.ssgdemov2.Repository; import com.ssg.demo.v2.ssgdemov2.Entity.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository<UserEntity, String> { public boolean existsByEmailAndPassword(String email, String password); }
5. AuthService에 login 서비스 작성
로그인 id/password 일치 여부 확인 및 로그인 성공/실패 여부 return을 작성하자.
AuthService.java
package com.ssg.demo.v2.ssgdemov2.Service; import com.ssg.demo.v2.ssgdemov2.Dto.LoginResponseDto; import com.ssg.demo.v2.ssgdemov2.Dto.ResponseDto; import com.ssg.demo.v2.ssgdemov2.Dto.SignUpDto; import com.ssg.demo.v2.ssgdemov2.Dto.LoginDto; import com.ssg.demo.v2.ssgdemov2.Entity.UserEntity; import com.ssg.demo.v2.ssgdemov2.Repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AuthService { @Autowired UserRepository userRepository; public ResponseDto<?> signUp(SignUpDto dto) { // 지난 코드 생략... } public ResponseDto<LoginResponseDto> login(LoginDto dto) { String email = dto.getEmail(); String password = dto.getPassword(); try { // 사용자 id/password 일치하는지 확인 boolean existed = userRepository.existsByEmailAndPassword(email, password); if(!existed) { return ResponseDto.setFailed("입력하신 로그인 정보가 존재하지 않습니다."); } } catch (Exception e) { return ResponseDto.setFailed("데이터베이스 연결에 실패하였습니다."); } UserEntity userEntity = null; try { // 값이 존재하는 경우 사용자 정보 불러옴 (기준 email) userEntity = userRepository.findById(email).get(); } catch (Exception e) { return ResponseDto.setFailed("데이터베이스 연결에 실패하였습니다."); } userEntity.setPassword(""); String token = ""; int exprTime = 3600000; // 1h LoginResponseDto loginResponseDto = new LoginResponseDto(token, exprTime, userEntity); return ResponseDto.setSuccessData("로그인에 성공하였습니다.", loginResponseDto); } }
6. Controller 수정 (Service 등록)
Controller의 login 부분을 수정하자.
AuthController.java
package com.ssg.demo.v2.ssgdemov2.Controller; import com.ssg.demo.v2.ssgdemov2.Dto.ResponseDto; import com.ssg.demo.v2.ssgdemov2.Dto.SignUpDto; import com.ssg.demo.v2.ssgdemov2.Dto.LoginDto; import com.ssg.demo.v2.ssgdemov2.Service.AuthService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired AuthService authService; @PostMapping("/signUp") public ResponseDto<?> signUp(@RequestBody SignUpDto requestBody) { ResponseDto<?> result = authService.signUp(requestBody); return result; } @PostMapping("/login") public ResponseDto<?> login(@RequestBody LoginDto requestBody) { ResponseDto<?> result = authService.login(requestBody); return result; } }
7. Postman 확인
Postman으로 작동 확인하고 정상적으로 작동하는 경우 Front에서 해당 api를 호출하여 로그인을 작성하자.
+ 토큰 생성 예시
일반적으로 JWT(Json Web Token), 엑세스 토큰, 리프레쉬 토큰을 발급하여 사용한다. (Ref [2] 참고)
하단 코드는 우선 JWT 생성 예시이다.
토큰 관련된 정보는 최하단 Ref 링크에 잘 정리되어 있으니 참고하자.
build.gradle
plugins { id 'java' id 'org.springframework.boot' version '3.2.2' id 'io.spring.dependency-management' version '1.1.4' } group = 'com.ssg.demo.v2' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = '17' } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web-services' testImplementation 'org.springframework.boot:spring-boot-starter-test' // Lombok implementation 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' // Mysql implementation 'mysql:mysql-connector-java:8.0.28' // javax implementation 'javax.persistence:javax.persistence-api:2.2' // spring-boot-starter-data-jpa implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // validation implementation 'org.springframework.boot:spring-boot-starter-validation' // jwt token implementation 'com.nimbusds:nimbus-jose-jwt:9.31' // *jwt 토큰 생성을 위해 추가 } tasks.named('test') { useJUnitPlatform() }
Security 패키지 생성 후 TokenProvider.java 추가
TokenProvider.java
package com.ssg.demo.v2.ssgdemov2.Security; import org.springframework.stereotype.Service; import com.nimbusds.jose.*; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import com.nimbusds.jose.crypto.MACSigner; import com.nimbusds.jose.crypto.MACVerifier; import java.time.Instant; import java.util.Date; @Service public class TokenProvider { private static final String SECURITY_KEY = "inputYourSecurityKey"; // JWT 생성 메서드 public String createJwt(String email, int duration) { try { // 현재 시간 기준 1시간 뒤로 만료시간 설정 Instant now = Instant.now(); Instant exprTime = now.plusSeconds(duration); // JWT Claim 설정 // *Claim 집합 << 내용 설정 (페이로드 설정) // subject << "sub", issuer << "iss", expiration time << "exp" .... JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() .subject(email) .issueTime(Date.from(now)) .expirationTime(Date.from(exprTime)) .build(); // JWT 서명 SignedJWT signedJWT = new SignedJWT( new JWSHeader(JWSAlgorithm.HS256), // *헤더 설정 claimsSet ); // HMAC 서명을 사용하여 JWT 서명 JWSSigner signer = new MACSigner(SECURITY_KEY.getBytes()); // *서명 설정 signedJWT.sign(signer); return signedJWT.serialize(); } catch (JOSEException e) { return null; } } // JWT 검증 메서드 public String validateJwt(String token) { try { // 서명 확인을 통한 JWT 검증 SignedJWT signedJWT = SignedJWT.parse(token); JWSVerifier verifier = new MACVerifier(SECURITY_KEY.getBytes()); if (signedJWT.verify(verifier)) { return signedJWT.getJWTClaimsSet().getSubject(); } else { // 서명이 유효하지 않은 경우 return null; } } catch (Exception e) { return null; } } }
페이로드에 미리 정의된 Claim
- iss(issuer; 발행자),
- exp(expireation time; 만료 시간),
- sub(subject; 제목),
- iat(issued At; 발행 시간),
- jti(JWI ID)
AuthService.java
// 생략... int exprTime = 3600; // 1h String token = tokenProvider.createJwt(email, exprTime); if(token == null) { return ResponseDto.setFailed("토큰 생성에 실패하였습니다."); } // 생략...
Ref.
[1]. https://han-um.tistory.com/17
'DEV > spring' 카테고리의 다른 글
Spring Security 적용 후 403 에러... 그리고 "http.csrf.disable()" deprecated (0) 2024.02.19 Spring boot + React 시작하기 4 (비밀번호 암호화하여 DB에 적재하기 Spring Security) (0) 2024.02.19 Spring boot + React 시작하기 2 (로그인 및 회원가입, 회원 관리 만들기) - 회원 가입 (1) 2024.02.06 Spring boot + React 시작하기 1 (로그인 및 회원가입, 회원 관리 만들기) - 기본설정 (1) 2024.02.01 Spring Controller, Service, Mapper Sample (0) 2023.08.24