[SpringBoot] 스프링부트 로그인 구현- (4) DTO, Repository, 로그인

최가희·2022년 1월 22일
0

SpringBoot

목록 보기
4/13
post-thumbnail

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

1. dto 패키지 생성


2. LoginDto 클래스 작성

dto > LoginDto.java 생성

import lombok.*;

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

/* 롬복 어노테이션 */
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginDto {

    /* @Valid 관련 어노테이션 */
    @NotNull
    @Size(min = 3, max = 50)
    private String username;

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

3. TokenDto 클래스 작성

토큰 정보를 Response할 때 사용

dto > TokenDto.java 생성

import lombok.*;

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

    private String token;
}

4. UserDto 클래스 작성

회원가입 시에 사용

dto > UserDto.java 생성

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;
    
}


02. Repository 관련 코드 생성

1. repository 패키지 생성


2. UserRepository 인터페이스 생성

User 엔티티에 매핑됨

import com.example.tutorial.entity.User;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

// JpaRepository를 extends하면 findAll, save 등 Jpa의 기본 메소드 사용 가능
public interface UserRepository extends JpaRepository<User, Long> {
    // @EntityGraph는 쿼리가 수행될 때 Lazy 조회가 아닌
    // Eager 조회로 authorities 정보를 같이 가져오게 됨
    @EntityGraph(attributePaths = "authorities")
    // username을 기준으로 User 정보를 가져올 때 권한 정보도 같이 가져옴
    Optional<User> findOneWithAuthoritiesByUsername(String username);
}

3. CustomUserDetailsService 클래스 작성

service 디렉토리 생성 > CustomUserDetailsService.java 생성

import com.example.tutorial.entity.User;
import com.example.tutorial.repository.UserRepository;
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 java.util.List;
import java.util.stream.Collectors;

@Component("userDetailsService")
// 스프링 시큐리티에서 중요한 부분 중 하나인 UserDetailService를 implements
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    // UserRepository를 주입받음
    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    @Transactional
    // UserDetailService의 loadUserByUsername 메소드를 오버라이딩 해서
    // 로그인 시에 DB에서 유저 정보와 권한 정보를 가져오게 됨
    public UserDetails loadUserByUsername(final String username) {
        return userRepository.findOneWithAuthoritiesByUsername(username)
                .map(user -> createUser(username, user))
                .orElseThrow(() -> new UsernameNotFoundException(username + " -> 데이터베이스에서 찾을 수 없습니다."));
    }

    // loadUserByUsername에 의해 받은 정보를 기반으로 유저가 활성화 상태면,
    // 유저의 권한정보와 유저네임, 패스워드를 가지고 userdetails.User객체를 생성해서 리턴함
    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);
    }
}


03. 로그인 API, 관련 로직 생성

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

controller > AuthController.java 생성

import com.example.tutorial.dto.LoginDto;
import com.example.tutorial.dto.TokenDto;
import com.example.tutorial.jwt.JwtFilter;
import com.example.tutorial.jwt.TokenProvider;
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 javax.validation.Valid;

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

    // TokenProvider, AuthenticationManagerBuilder 주입 받음
    public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
        this.tokenProvider = tokenProvider;
        this.authenticationManagerBuilder = authenticationManagerBuilder;
    }

    // 로그인 API 경로 => /api/authenticate
    @PostMapping("/authenticate")
    // LoginDto의 username과 password를 파라미터로 받아
    public ResponseEntity<TokenDto> authorize(@Valid @RequestBody LoginDto loginDto) {

        // UsernamePasswordAuthenticationToken을 생성함
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());

        // authenticationToken을 이용해서 authenticate 메소드가 실행될 때,
        // CustomUserDetailsService에서 loadUserByUsername 메소드가 실행됨
        // 실행된 결괏값을 가지고 Authentication 객체를 생성함
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        // Authentication 객체를 SecurityContext에 저장하고 
        SecurityContextHolder.getContext().setAuthentication(authentication);

        // Authentication 객체로 createToken 메소드를 통해 JWT 토큰을 생성함 
        String jwt = tokenProvider.createToken(authentication);

        HttpHeaders httpHeaders = new HttpHeaders();
        // JWT 토큰은 Response Header에도 넣어주고,
        httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);

        // TokenDto를 이용해서 Response Body에도 넣어서 리턴함
        return new ResponseEntity<>(new TokenDto(jwt), httpHeaders, HttpStatus.OK);
    }
}

2. 포스트맨으로 로그인 API 테스트

http://localhost:8080/api/authenticate으로 usernamepassword를 body에 담아 post 요청을 보내면, 다음과 같이 토큰이 정상적으로 리턴된다.

+) 포스트맨의 유용한 기능
Tests 탭에서 Response의 데이터를 전역변수에 저장하면 다른 Request에서 사용할 수 있다.

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

responseBody 에 있는 내용을 파싱해서 포스트맨(pm)의 전역변수에 설정해서 다른 요청에서도 해당 변수에 있는 값을 끌어서 쓸 수 있게 된다.

0개의 댓글