본문 바로가기
프로그래밍/Java & Spring

Spring Boot를 사용하여 JWT 기반 인증 기능을 구현하는 방법

by 방구석개발자 2024. 8. 21.
반응형

지난 포스팅에서 jwt가 무엇인지 살펴보았습니다. (https://roomconerdeveloper.tistory.com/183)

 

이제 jwt를 발급하는 기능을  SpringBoot로 구현해 보겠습니다.

 

소스코드는 github에 있습니다.

https://github.com/lsm7179/spring-eCommerce-study/tree/step1

 

GitHub - lsm7179/spring-eCommerce-study

Contribute to lsm7179/spring-eCommerce-study development by creating an account on GitHub.

github.com

 

사용한 버전 및 의존성

사용한 버전 다음과 같습니다.

* SpringBoot: 3.3.2

* Java: 22

gradle 의존성

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.2'
    id 'io.spring.dependency-management' version '1.1.6'
}

group = 'my.study'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(22)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'io.jsonwebtoken:jjwt:0.12.6'
    runtimeOnly 'com.h2database:h2'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
    useJUnitPlatform()
}

 

 

로그인을 한후 토큰을 반환하는 간단한 시퀀스 다이어그램입니다.

 

Code

Controller

@RestController
public class MemberController {

    private final MemberService memberService;

    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @PostMapping("/join")
    public ResponseEntity<Long> join(@RequestBody final JoinRequest joinRequest) {
        Long memberId = memberService.join(joinRequest);
        return ResponseEntity
                .created(URI.create("/login"))
                .body(memberId);
    }

    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestBody final LoginRequest loginRequest) {
        return ResponseEntity.ok(memberService.login(loginRequest));
    }

}

 

Serivce 계층

@Service
@Transactional(readOnly = true)
public class MemberService {

    private final MemberRepository memberRepository;
    private final JwtGenerator jwtGenerator;

    public MemberService(MemberRepository memberRepository, JwtGenerator jwtGenerator) {
        this.memberRepository = memberRepository;
        this.jwtGenerator = jwtGenerator;
    }

    @Transactional
    public Long join(JoinRequest joinRequest) {
        Member createMember = memberRepository.save(joinRequest.toEntity());
        return createMember.getId();
    }


    public String login(LoginRequest loginRequest) {
        Member member = memberRepository.findByEmailAndPassword(loginRequest.getEmail(), loginRequest.getPassword())
                .orElseThrow(() -> new IllegalArgumentException("이메일과 비밀번호를 확인해주세요."));

        return jwtGenerator.generateToken(member);
    }
}

 

 

 

repository는 간단한 단일멤버조회만 있습니다.

 

JwtGenerater

토큰을 발급하는 기능을 하는 jwtgenerater 입니다.

나중에 토큰 확인하는 기능도 추가할 예정입니다.

@Component
public class JwtGenerator {

    private final String secretKeyValue;
    private final long expiration;
    private final SecretKey secretKey;

    public JwtGenerator(@Value("${jwt.secret}") String secretKey
            , @Value("${jwt.expiration}") long expiration) {
        this.secretKeyValue = secretKey;
        this.expiration = expiration;
        this.secretKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey));
    }

    public String generateToken(Member member) {
        return Jwts.builder()
                .claim("id", member.getId())
                .claim("email", member.getEmail())
                .claim("name", member.getName())
                .issuedAt(new Date(System.currentTimeMillis()))
                .expiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(secretKey, Jwts.SIG.HS512)
                .compact();
    }

}

 

test 코드

package my.study.springecommercestudy.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@SpringBootTest
@AutoConfigureMockMvc
class MemberControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private ObjectMapper objectMapper;

    @DisplayName("회원가입")
    @Test
    @Order(0)
    void create() throws Exception {
        Map<String, String> joinRequest = new HashMap<>();

        joinRequest.put("email", "test@test.com");
        joinRequest.put("password", "1234!");
        joinRequest.put("name", "이승민");

        MvcResult mvcResult = mockMvc.perform(post("/join")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(joinRequest)))
                .andExpect(status().isCreated())
                .andDo(print())
                .andReturn();
        String response = mvcResult.getResponse().getContentAsString();

        assertThat(response).isNotBlank();
    }

    @DisplayName("로그인")
    @Test
    @Order(1)
    void join() throws Exception {
        Map<String, String> joinRequest = new HashMap<>();

        joinRequest.put("email", "test@test.com");
        joinRequest.put("password", "1234!");

        MvcResult mvcResult = mockMvc.perform(post("/login")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(joinRequest)))
                .andExpect(status().isOk())
                .andDo(print())
                .andReturn();
        String response = mvcResult.getResponse().getContentAsString();

        assertThat(response).isNotBlank();
    }
}

 

이렇게 jwt 발급기능을 구현했습니다.

다음 포스트에서 발급한 토큰 유효성검사와 토큰으로 member 정보를 받아서 처리하는 기능을 개발해보겠습니다.

반응형

댓글