본문 바로가기
Spring/Security

[JWT] 회원가입 / 로그인 / 토큰 발급 (1)

by 행운의나무 2022. 10. 28.
728x90
반응형

목표

  • 회원가입 기능
  • 로그인 기능
  • 로그인 시 JWT 토큰 발급
  • JWT 인증, 인가 필터 생성
  • JWT의 회원 정보 가져오기

환경

  • java 17
  • spring boot 2.7
  • gradle 7.5

환경 설정

1. build.gradle

- Web, Security, Lombok, JPA, mysql-connector, jwt 의존성을 추가합니다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'

	// JWT
	implementation 'com.auth0:java-jwt:4.2.0'
    }

2. application.yml

server:
  port: 8080
  servlet:
    context-path: /
    encoding:
      charset: UTF-8
      enabled: true
      force: true

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/dbname?serverTimezone=Asia/Seoul
    username: username #유저네임
    password: password #암호

  jpa:
    hibernate:
      ddl-auto: update #create update none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true

# 시크릿 키값
jwt:
  secret: "Vwgff4uvzQ4pes0Zt7sDNtL6pxGIkg2k95ZIrVhvlGmxcDRq9O1fnLN2lEzItsNE4w_lQ3f7xd09ukYxzIYS6InrYfg4ed2BSP0wmZ2RJEswzDsCLNqwRRXW780o1TYbuQpiXuUN0TnwXzb2l4YnNcXLHyBBJoIU17y1H56Aq1-ABW6MREvcFvlW-oUcMw92R0piQK4hO_Xo8AFIDnbeAqQUQ2Q91iQZRTtiNrV9Gv_pF_f1LF9OLDnvmTTy7Av7yFRstie90G9ABYsFTrxywHLzA-QMDYOeUOk8wq6TfxKbULK8PqWR__s1ebFlA3bFO1shhUdffA"

- JpaConfig

@EnableJpaAuditing
public class JpaConfig {

}

- SecurityConfig

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig {


    // 회원의 패스워드 암호화
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){

        return new BCryptPasswordEncoder();
    }

    // 시큐리티 필터 설정
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
                .cors().disable()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .formLogin().disable()
                .httpBasic().disable() // http의 기본 인증. ID, PW 인증방식
                .authorizeHttpRequests()
                .anyRequest().permitAll();


        return http.build();

    }
}

Entity 생성 및 Repository 생성

- User

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String username;
    private String email;
    private String password;
    private String roles; // USER, ADMIN

    private LocalDateTime createdDate;

    public List<String> getRolesList(){
        if(this.roles.length() > 0){
            return Arrays.asList(this.roles.split(","));
        }
        return new ArrayList<>();
    }

}

- UserRepository
JPA를 이용해 User 테이블에 접근할 수 있는 인터페이스를 생성합니다.

public interface UserRepository extends JpaRepository<User, Long> {
    User findByEmail(String email);
}

회원가입 기능 개발

DTO 객체 생성

- JoinRequestDto
회원 가입 시 요청될 객체를 생성합니다.

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class JoinRequestDto {

    private String email;
    private String username;
    private String password;
}

contorller

@RestController
@RequestMapping
@RequiredArgsConstructor
public class UserController {


    private final UserService userService;
    
    @PostMapping("/api/join")
    public String join(@RequestBody JoinRequestDto joinRequestDto) {

        return userService.join(joinRequestDto);
    }
}

service

 

회원가입 API 호출
데이터베이스 회원정보저장


로그인 기능 개발

DTO 객체 생성

- LoginRequestDto
로그인 시 요청될 객체를 생성합니다.

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LoginRequestDto {

    private String email;
    private String password;
}

contorller

@RestController
@RequestMapping
@RequiredArgsConstructor
public class UserController {


    private final UserService userService;

    @PostMapping("/api/join")
    public String join(@RequestBody JoinRequestDto joinRequestDto) {

        return userService.join(joinRequestDto);
    }

    @PostMapping("/api/login")
    public String login(@RequestBody LoginRequestDto loginRequestDto) {
        return userService.login(loginRequestDto);
    }
}

service

@Service
@RequiredArgsConstructor
public class UserService {

    private final BCryptPasswordEncoder passwordEncoder;
    private final UserRepository userRepository;


    public String join(JoinRequestDto joinRequestDto) {
		// ...
        return "회원가입";
    }

    public String login(LoginRequestDto loginRequestDto) {
        String email = loginRequestDto.getEmail();
        String rawPassword = loginRequestDto.getPassword();

        User byEmail = userRepository.findByEmail(email);

        // 비밀번호 일치 여부 확인
        if(passwordEncoder.matches(rawPassword, byEmail.getPassword())){
            return "로그인 성공";
        }

        return "로그인 실패";
    }
}

 

로그인 성공


로그인 시 JWT 토큰 발급

JwtProvider

JWT 라이브러리를 활용해 Provider를 생성하여 JWT 토큰 관련 메소드를 관리합니다. 
토큰 생성(generate)
유효성 검증(valid)

@RequiredArgsConstructor
public class JwtProvider {

    private final UserRepository userRepository;

    static Long EXPIRE_TIME = 60L * 60L * 1000L; // 만료 시간 1시간
    
    @Value("${jwt.secret}")
    private String secretKey;
    

    private Algorithm getSign(){
        return Algorithm.HMAC512(secretKey);
    }
    //객체 초기화, secretKey를 Base64로 인코딩한다.
    @PostConstruct
    protected void init() {
        this.secretKey = Base64.getEncoder().encodeToString(this.secretKey.getBytes());
    }


    // Jwt 토큰 생성
    public String generateJwtToken(Long id, String email, String username){

        Date tokenExpiration = new Date(System.currentTimeMillis() + (EXPIRE_TIME));


        String jwtToken = JWT.create()
                .withSubject(email) //토큰 이름
                .withExpiresAt(tokenExpiration)
                .withClaim("id", id)
                .withClaim("email", email)
                .withClaim("username", username)
                .sign(this.getSign());

        return jwtToken;
    }

    /**
     * 토큰 검증
     *  - 토큰에서 가져온 email 정보와 DB의 유저 정보 일치하는지 확인
     *  - 토큰 만료 시간이 지났는지 확인
     * @param jwtToken
     * @return 유저 객체 반환
     */
    public User validToken(String jwtToken){
        try {

            String email = JWT.require(this.getSign())
                    .build().verify(jwtToken).getClaim("email").asString();

            // 비어있는 값이다.
            if (email == null){
                return null;
            }

            // EXPIRE_TIME이 지나지 않았는지 확인
            Date expiresAt = JWT.require(this.getSign()).acceptExpiresAt(EXPIRE_TIME).build().verify(jwtToken).getExpiresAt();
            if (!this.validExpiredTime(expiresAt)) {
                // 만료시간이 지났다.
                return null;
            }

            User tokenUser = userRepository.findByEmail(email);

            return tokenUser;

        } catch (Exception e){
            e.printStackTrace();
            return null;
        }

    }

    // 만료 시간 검증
    private boolean validExpiredTime(Date expiresAt){
        // LocalDateTime으로 만료시간 변경
        LocalDateTime localTimeExpired = expiresAt.toInstant().atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime();

        // 현재 시간이 만료시간의 이전이다
         return LocalDateTime.now().isBefore(localTimeExpired);

    }
}

SecurityConfig

JwtProvider를 Bean으로 등록합니다.

public class SecurityConfig {

    private final UserRepository userRepository;
    @Bean
    public JwtProvider jwtTokenProvider() {
        return new JwtProvider(userRepository);
    }
    // ...
}

service

로그인 성공 시 JWT 토큰을 생성하여 반환해줍니다.

  public String login(LoginRequestDto loginRequestDto) {
        String email = loginRequestDto.getEmail();
        String rawPassword = loginRequestDto.getPassword();

        User byEmail = userRepository.findByEmail(email);

        // 비밀번호 일치 여부 확인
        if(passwordEncoder.matches(rawPassword, byEmail.getPassword())){

            // JWT 토큰 반환
            String jwtToken = jwtProvider.generateJwtToken(byEmail.getId(), byEmail.getEmail(), byEmail.getUsername());

			return "로그인 성공 " + jwtToken;
        }

        return "로그인 실패";
    }

JWT 토큰을 반환하는 로그인 기능

이것으로 JWT를 발급하는 로그인 기능을 완성했습니다.

다음은 JWT의 인증, 인가 필터를 만들고 JWT에서 유저정보를 반환하는 기능을 개발합니다.

2022.10.28 - [Spring/Security] - [JWT] JWT 인증, 인가 필터 생성 / 회원 정보 가져오기 / 인증 객체로 로그인 처리 (2)

 

[JWT] JWT 인증, 인가 필터 생성 / 회원 정보 가져오기 / 인증 객체로 로그인 처리 (2)

목표 회원가입 기능 로그인 기능 로그인 시 JWT 토큰 발급 JWT 인증, 인가 필터 생성 JWT의 회원 정보 가져오기 인증 객체로 로그인 처리 이전 글 - 회원가입 / 로그인 / JWT 토큰 발급 2022.10.28 - [Spring/

twer.tistory.com

 

함께보면 도움되는 글

2022.10.21 - [Spring/Security] - [Security] 스프링 시큐리티의 아키텍처(구조) 및 흐름

 

[Security] 스프링 시큐리티의 아키텍처(구조) 및 흐름

Spring Security 스프링 시큐리티리란? 어플리케이션의 보안(인증 및 권한)을 담당하는 프레임워크 Spring Security를 사용하지 않으면 자체적으로 세션을 체크해야 한다. redirect를 일일이 설정해주어야

twer.tistory.com

2022.09.05 - [Spring/Security] - [Security] WebSecurityConfigurerAdapter Deprecated

 

[Security] WebSecurityConfigurerAdapter Deprecated

버전 Java 11 Spring boot 2.7.3 Deprecated Spring Security 5.7.0-M2부터 Deprcated 되었다. (SpringBoot 기준 2.7 이후) WebSecurityConfigurerAdapter : WebSecurityConfigurer의 인스턴스를 생성하여 Abstrac..

twer.tistory.com

쿠팡으로 연결 클릭

 

제주삼다수 그린

COUPANG

www.coupang.com

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음

반응형