팀 프로젝트 - 로그인

chrkb1569·2022년 9월 20일
0

Spring

목록 보기
6/11

안녕하세요

오랜만에 또 글을 쓰네요ㅋㅋ

지난번에 Board Entity를 통하여 게시글 작성하는 것까지 작성했었는데요... 그 이후로 로그인 기능까지 담당하게 되어서 그 동안 Spring만 붙잡고 살았습니다..

학교 수업이랑 팀 프로젝트 2개를 병행하려니 죽을 맛이네요 ㅋㅋ

거기다가 로그인 기능 구현은 아직 연습중이여서 그런지 시간이 좀 더 많이 걸리는 작업이었습니다.

뭐 결론적으로는 방금 로그인 기능 구현까지 마친 뒤에 커밋하고 왔습니다.

작업하는 동안은 죽을 맛이였는데, 또 막상 구현한거 보면은 뿌듯하더라구요 ㅋㅋ

package com.example.community_board.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String username; // 사용자 이름

    @Column(nullable = false, unique = true)
    private String userId; // 사용자 ID

    @Column(nullable = false)
    private String password; // 사용자 비밀 번호

    @Column(nullable = false, unique = true)
    private String email; // 사용자 Email

    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private Authority authority; // 사용자 권한
}
package com.example.community_board.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

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

    @Id
    @Column(name = "keyValue")
    private String key;

    @Column(nullable = false)
    private String value;
}
package com.example.community_board.service;

import com.example.community_board.dto.user.*;
import com.example.community_board.entity.Authority;
import com.example.community_board.entity.RefreshToken;
import com.example.community_board.entity.User;
import com.example.community_board.exception.user.NotValidTokenException;
import com.example.community_board.exception.user.UserIdCollisionException;
import com.example.community_board.exception.user.UserNotFoundException;
import com.example.community_board.exception.user.UserWrongPasswordException;
import com.example.community_board.jwt.TokenProvider;
import com.example.community_board.repository.RefreshTokenRepository;
import com.example.community_board.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalTime;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {

    private final UserRepository userRepository;

    private final RefreshTokenRepository refreshTokenRepository;

    private final TokenProvider tokenProvider;

    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    private final AuthenticationManagerBuilder authenticationManagerBuilder;

    @Transactional // 회원 가입 로직
    public void signUp(UserSignUpRequestDto requestDto) {
        Optional<User> findUser = userRepository.findUserByUserId(requestDto.getUserId()); // 사용자 id를 통하여 검색

        if(findUser.isPresent()) {
            throw new UserIdCollisionException(); // 해당 유저가 존재한다는 뜻이므로, 오류 반환
        }

        User user; // 저장을 위한 User 객체 하나 생성

        if(requestDto.getUserId().equals("admin")) {
            user = User.builder() // 작성자의 ID값이 admin인 경우, 관리자 권한 획득
                    .username(requestDto.getUsername())
                    .userId(requestDto.getUserId())
                    .password(bCryptPasswordEncoder.encode(requestDto.getPassword().toLowerCase()))
                    .email(requestDto.getEmail())
                    .authority(Authority.ROLE_ADMIN)
                    .build();

            userRepository.save(user); // 관리자 권한을 가진 User Entity 저장
        }
        else {
            user = User.builder() // 그 외의 경우에는 일반 권한을 가진 유저로 회원가입을 진행하게됨
                    .username(requestDto.getUsername())
                    .userId(requestDto.getUserId())
                    .password(bCryptPasswordEncoder.encode(requestDto.getPassword().toLowerCase()))
                    .email(requestDto.getEmail())
                    .authority(Authority.ROLE_USER)
                    .build();

            userRepository.save(user); // 일반 권한을 가진 유저 객체 저장
        }
    }

    @Transactional // 로그인 로직
    public UserLoginResponseDto login(UserLoginRequestDto requestDto) {
        User findUser = userRepository.findUserByUserId(requestDto.getUserId())
                .orElseThrow(UserNotFoundException::new); // UserId를 통하여 User 검색

        if(bCryptPasswordEncoder.matches(requestDto.getPassword(), findUser.getPassword())) {

            UsernamePasswordAuthenticationToken authenticationToken =
                    requestDto.getAuthenticationToken();

            Authentication authentication = authenticationManagerBuilder
                    .getObject().authenticate(authenticationToken);

            TokenDto jwt = tokenProvider.createToken(authentication);

            RefreshToken refreshToken = RefreshToken.builder()
                    .key(authentication.getName())
                    .value(jwt.getRefreshToken())
                    .build();

            refreshTokenRepository.save(refreshToken);

            return new UserLoginResponseDto(jwt.getOriginToken(), jwt.getRefreshToken());
        }
        throw new UserWrongPasswordException();
    }

    @Transactional// 토큰 재발급 로직
    public TokenReissueDto reIssue(TokenReissueDto requestDto) {
        if(!tokenProvider.validateToken(requestDto.getRefreshToken())) {
            throw new NotValidTokenException();
        }

        Authentication authentication = tokenProvider.getAuthentication(requestDto.getOriginToken());

        RefreshToken refreshToken = refreshTokenRepository
                .findById(authentication.getName())
                .orElseThrow(() -> new RuntimeException("현재 로그인 하지 않은 회원입니다."));

        if(!refreshToken.getValue().equals(requestDto.getRefreshToken())) {
            throw new RuntimeException("토큰 정보가 일치하지 않습니다.");
        }

        TokenDto tokenDto = tokenProvider.createToken(authentication);

        RefreshToken refreshTokenValue = RefreshToken.builder()
                .key(tokenDto.getOriginToken())
                .value(tokenDto.getRefreshToken())
                .build();

        refreshTokenRepository.save(refreshTokenValue);

        return new TokenReissueDto(tokenDto.getOriginToken(), tokenDto.getRefreshToken());
    }

    @Transactional //email의 unique라는 속성을 활용하여 User Entity를 검색함
    public String findUserId(UserEmailRequestDto requestDto) {

        User findUser = userRepository.findUserByEmail(requestDto.getEmail()).orElseThrow(UserNotFoundException::new);

        return findUser.getUserId(); // 검색한 User Entity의 Userid만을 리턴해줌
    }

    @Transactional // 아이디 검색 로직과 마찬가지로, email의 unique라는 속성을 활용하여 User Entity를 검색함
    public String findUserPassword(UserEmailRequestDto requestDto) {

        User findUser = userRepository.findUserByEmail(requestDto.getEmail()).orElseThrow(UserNotFoundException::new);

        LocalTime tmp = LocalTime.now();

        String reGeneratedPassword = tmp.toString().replaceAll("[^0-9]", "");

        findUser.setPassword(bCryptPasswordEncoder.encode(reGeneratedPassword));

        return reGeneratedPassword;
    }

    @Transactional // UserPasswordEditRequestDto를 통하여 사용자의 현재 비밀번호와 바꿀 비밀번호를 입력받음.
    public void editPassword(UserPasswordEditRequestDto requestDto) {
        String nowLoginUserId = SecurityContextHolder.getContext()
                .getAuthentication().getName(); // 현재 사용자의 ID값을 받음

        User nowLoginUser = userRepository.findUserByUserId(nowLoginUserId)
                .orElseThrow(UserNotFoundException::new); // 사용자의 ID값은 unique 속성이 부여되어 있으므로, UserID를 통하여 사용자 검색

        log.info(nowLoginUser.getAuthority().toString());

        if(bCryptPasswordEncoder.matches(requestDto.getOriginPassword(), nowLoginUser.getPassword())) {
            nowLoginUser
                    .setPassword(bCryptPasswordEncoder.encode(requestDto.getRefreshPassword().toLowerCase())); // 검색한 사용자의 비밀번호와 입력받은 비밀번호를 비교
        }

        else throw new UserWrongPasswordException();

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        for(GrantedAuthority s : authentication.getAuthorities()) {
            log.info(s.getAuthority());
        }
    }

    @Transactional
    public void delegateUser(UserDelegateRequestDto requestDto) {
        User findUser = userRepository.findUserByUserId(requestDto.getUserId())
                .orElseThrow(UserNotFoundException::new);

        findUser.setAuthority(Authority.ROLE_MANAGER);
    }
}
package com.example.community_board.contoller;

import com.example.community_board.dto.user.TokenReissueDto;
import com.example.community_board.dto.user.UserDelegateRequestDto;
import com.example.community_board.dto.user.UserEmailRequestDto;
import com.example.community_board.dto.user.UserLoginRequestDto;
import com.example.community_board.dto.user.UserPasswordEditRequestDto;
import com.example.community_board.dto.user.UserSignUpRequestDto;
import com.example.community_board.response.Response;
import com.example.community_board.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
public class UserController {

    private final UserService userService;

    @PostMapping("/signup")
    @ResponseStatus(HttpStatus.CREATED) // 회원 가입
    public void signup(@RequestBody @Valid UserSignUpRequestDto requestDto) {
        userService.signUp(requestDto);
    }

    @PostMapping("/signin")
    @ResponseStatus(HttpStatus.CREATED) // 로그인
    public Response login(@RequestBody @Valid UserLoginRequestDto requestDto) {
        return Response.success(userService.login(requestDto));
    }

    @PostMapping("/auth/reissue")
    @ResponseStatus(HttpStatus.OK) // 토큰 재발급
    public Response reissue(@RequestBody @Valid TokenReissueDto requestDto) {
        return Response.success(userService.reIssue(requestDto));
    }

    @GetMapping("/userid")
    @ResponseStatus(HttpStatus.OK) // 사용자 아이디 찾기
    public Response findUserId(@RequestBody @Valid UserEmailRequestDto requestDto) {
        return Response.success(userService.findUserId(requestDto));
    }

    @GetMapping("/password")
    @ResponseStatus(HttpStatus.OK) // 임시 비밀번호 발급
    public Response findUserPassword(@RequestBody @Valid UserEmailRequestDto requestDto) {
        return Response.success(userService.findUserPassword(requestDto));
    }

    @PutMapping("/auth/password")
    @ResponseStatus(HttpStatus.OK) // 비밀번호 변경
    public void editUserPassword(@RequestBody @Valid UserPasswordEditRequestDto requestDto) {
        userService.editPassword(requestDto);
    }

    @PutMapping("/admin")
    @ResponseStatus(HttpStatus.OK)
    public void delegate(@RequestBody @Valid UserDelegateRequestDto requestDto) {
        userService.delegateUser(requestDto);
    }
}

User Entity와 RefreshToken Entity를 사용하여 로그인 기능과 JWT 토큰에서의 재발급을 어떤식으로 활용할지 정하였습니다.

일단은 로그인까지 구현해보았고, 이제 JWT 토큰을 사용하여 사용자 인증을 수행할 수 있기 때문에 이를 활용하여 게시글 좋아요 기능까지 만들어보려고합니다.

화이팅!

0개의 댓글