개발/Spring & Springboot

[SpringBoot] JWT Tutorial 6 - DTO, Repository, 로그인

뚜키 💻 2022. 5. 24. 00:32
반응형

## 개인기록용

# Spring Boot JWT Tutorial 학습

https://github.com/jennie267/jwt-tutorial

 

[SpringBoot] JWT Tutorial 1 - JWT

[SpringBoot] JWT Tutorial 2 - 프로젝트 생성

[SpringBoot] JWT Tutorial 3 - Security 설정

[SpringBoot] JWT Tutorial 4 - Data 설정

[SpringBoot] JWT Tutorial 5 - JWT 코드, Security 설정 추가

>  [SpringBoot] JWT Tutorial 6 - DTO, Repository, 로그인

[SpringBoot] JWT Tutorial 7 - 회원가입, 권한검증


 

- 외부와의 통신에 사용할 DTO 클래스 생성

- Repository 관련 코드 생성

- 로그인 API, 관련 로직 생성

 

1. 외부와의 통신에 사용할 DTO 클래스 생성

 DTO 패키지 만들고

LoginDto 생성 : 로그인시 사용

- Lombok 어노테이션 추가

- @Valid 관련 어노테이션 추가 (@NotNull, @Size)

 

LoginDto.java

package study.cherry.jwttutorial.dto;

import lombok.*;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginDto {

    @NotNull
    @Size(min = 3, max = 50)
    private String username;

    @NotNull
    @Size(min = 3, max = 100)
    private String password;
}

 

2. Token 정보를 Response할때 사용할 TokenDto 생성

 

TokenDto.java

package study.cherry.jwttutorial.dto;

import lombok.*;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TokenDto {

    private String token;
}

 

3. 회원가입에 사용할 UserDto 생성

- @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) : Write할때만 접근 허용. 응답 결과를 생성할 때는 해당 필드는 제외된다

 

UserDto.java

package study.cherry.jwttutorial.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserDto {

    @NotNull
    @Size(min = 3, max = 50)
    private String username;

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    @NotNull
    @Size(min = 3, max = 100)
    private String password;

    @NotNull
    @Size(min = 3, max = 50)
    private String nickname;

}

 

4. Repository 패키지 추가, User Entity에 매핑되는 UserRepository 인터페이스 생성

- findOneWithAuthoritiesByUsername : username을 기준으로 User정보를 조회하는데 권한정보도 같이 가져오는 메서드

- @EntityGraph로 쿼리가 수행될 때 Eager조회로 authorities정보를 같이 가져온다

package study.cherry.jwttutorial.repository;

import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import study.cherry.jwttutorial.entity.User;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {

    @EntityGraph(attributePaths = "authorities")
    Optional<User> findOneWithAuthoritiesByUsername(String username);

}

 

 

5. CustomUserDetailsService 클래스 생성

- Spring Security에서 중요한 부분중 하나인 UserDetailsService를 구현한 클래스

- UserDetailService를 implements하고 UserRepository 주입

- loadUserByUsername 메서드를 override해서 로그인시에 DB에서 유저정보와 권한정보 가져오게함

이 정보 기반으로 userdetails.User 객체를 생성해서 리턴

 

CustomUserDetailsService.java

package study.cherry.jwttutorial.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import study.cherry.jwttutorial.entity.User;
import study.cherry.jwttutorial.repository.UserRepository;

import java.util.List;
import java.util.stream.Collectors;

@Component("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    @Transactional
    public UserDetails loadUserByUsername(final String username) {
        return userRepository.findOneWithAuthoritiesByUsername(username)
                .map(user -> createUser(username, user))
                .orElseThrow(() -> new UsernameNotFoundException(username + " -> 데이터베이스에서 찾을 수 없습니다."));
    }

    private org.springframework.security.core.userdetails.User createUser(String username, User user) {
        if (!user.isActivated()) {
            throw new RuntimeException(username + " -> 활성화되어 있지 않습니다.");
        }
        List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
                .map(authority -> new SimpleGrantedAuthority(authority.getAuthorityName()))
                .collect(Collectors.toList());
        return new org.springframework.security.core.userdetails.User(user.getUsername(),
                user.getPassword(),
                grantedAuthorities);
    }
}

 

6. 로그인 API를 추가하기 위해 AuthController 클래스 추가

- ToeknProvider, AuthenticationManagerBuilder 주입

- 로그인 API 경로 : /api/authenticate (POST)

- LoginDto의 username, password를 파라미터로 받고 이걸 이용해서 UsernamePasswordAuthenticationToken 생성

- authenticationToken을 이용해서 Authentication 객체를 생성하려고 authenticate 메서드가 실행이 될 때 loadUserByUsername 메서드가 실행됨

- 이 결과값으로 Authentication 객체가 생성되고 이를 SecurityContext에 저장

- 그 인증정보를 가지고 TokenProvider에 있는 createToken메서드를 통해서 JWT Token 생성

- JWT Token을 Response Header에도 넣어주고 TokenDto를 이용해서 Response Body에도 넣어서 리턴

 

AuthController.java

package study.cherry.jwttutorial.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.context.SecurityContextHolder;
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 study.cherry.jwttutorial.dto.LoginDto;
import study.cherry.jwttutorial.dto.TokenDto;
import study.cherry.jwttutorial.jwt.JwtFilter;
import study.cherry.jwttutorial.jwt.TokenProvider;

import javax.validation.Valid;

@RestController
@RequestMapping("/api")
public class AuthController {
    private final TokenProvider tokenProvider;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;

    public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
        this.tokenProvider = tokenProvider;
        this.authenticationManagerBuilder = authenticationManagerBuilder;
    }

    @PostMapping("/authenticate")
    public ResponseEntity<TokenDto> authorize(@Valid @RequestBody LoginDto loginDto) {

        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());

        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = tokenProvider.createToken(authentication);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);

        return new ResponseEntity<>(new TokenDto(jwt), httpHeaders, HttpStatus.OK);
    }
}

 

 

7. 로그인 API 테스트 (Postman)

 - http://localhost:8080/api/authenticate  (POST)

- LoginDto 에 들어갈 username, password 넣어주기

- 결과값으로 token이 나오는지 확인

 

** Postman 전역변수 지정하기

- Tests 탭에서 지정할수있음

- Response의 데이터를 전역변수에 저장해서 다른 Request에서 사용할 수 있다

var jsonData = JSON.parse(responseBody)
pm.globals.set("jwt_tutorial_token", jsonData.token);

 


여기까지하면 이런 구조가 됨

 

 

 

 

 


 

Reference

Spring Boot JWT Tutorial

https://github.com/SilverNine/spring-boot-jwt-tutorial

 

GitHub - SilverNine/spring-boot-jwt-tutorial

Contribute to SilverNine/spring-boot-jwt-tutorial development by creating an account on GitHub.

github.com

@JsonProperty 를 이용한 접근 제어 - 응답값에 포함하지 않기 (https://eglowc.tistory.com/28)

반응형