인증 API 구현

UserController

package com.jh.jwt.controller;

import com.jh.jwt.service.UserService;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.apache.tomcat.util.http.parser.Authorization;
import org.springframework.http.ResponseEntity;
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.servlet.http.HttpServletRequest;

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

    private final UserService userService;

    @PostMapping("/login")
    public ResponseEntity<LoginResponseDto> login(@RequestBody LoginRequestDto dto) {
        return ResponseEntity.ok().body(userService.login(dto.getUsername(), dto.getPassword()));
    }

    @PostMapping("/sign")
    public Long sign(@RequestBody RequestSignupDto dto) {
        return ResponseEntity.ok().body(userService.join(dto.getUsername(), dto.getPassword(), dto.getAuth())).getBody();
    }

    @PostMapping("/refresh")
    public ResponseEntity<LoginResponseDto> refresh(HttpServletRequest request) {
        return ResponseEntity.ok().body(userService.refresh(request));
    }

    @PostMapping("/logout")
    public ResponseEntity<String> logout(HttpServletRequest request) {
        return ResponseEntity.ok().body(userService.logout(request));
    }

    @Data
    public static class RefreshTokenRequestDto {
        private String token;
    }

    @Data
    public static class LoginRequestDto {
        private String username;
        private String password;
    }

    @Data
    public static class RequestSignupDto {
        String username;
        String password;
        String auth;
    }
}
  • 기본적으로 로그인, 회원가입, 리프레시 기능이 있음
  • 리프레시는 accessToken이 만료되었을 때 호출하는 API
  • Dto는 요청 받을 때 필요한 타입들을 명시해둠

LoginResponseDto

package com.jh.jwt.controller;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class LoginResponseDto {
    private String accessToken;
    private String refreshToken;

    public LoginResponseDto(String accessToken, String refreshToken) {
        this.accessToken = accessToken;
        this.refreshToken = refreshToken;
    }
}
  • 로그인이나 리프레시가 성공적으로 수행되었을 때 반환되는 형태

UserService

package com.jh.jwt.service;

import com.jh.jwt.controller.LoginResponseDto;
import com.jh.jwt.controller.UserController;
import com.jh.jwt.domain.Authority;
import com.jh.jwt.domain.CustomUserDetails;
import com.jh.jwt.domain.User;
import com.jh.jwt.repository.UserRepository;
import com.jh.jwt.utils.RedisUtil;
import com.jh.jwt.utils.TokenProvider;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
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.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final AuthenticationManagerBuilder authenticationManagerBuilder;

    private final RedisUtil redisUtil;
    protected final Logger logger = LoggerFactory.getLogger(UserService.class);

    private final PasswordEncoder passwordEncoder;
    private final TokenProvider tokenProvider;

    @Transactional
    public Long join(String username, String password, String auth){
        User user = User.builder()
                .username(username)
                .password(passwordEncoder.encode(password))
                .authority(Authority.valueOf(auth))
                .build();
        return userRepository.save(user);
    }

    @Transactional
    public LoginResponseDto login(String username, String password){
        // 회원 validation
        List<User> users = userRepository.findByUsername(username);
        if (users.isEmpty()) {
            throw new UsernameNotFoundException("존재하지 않는 회원입니다.");
        }
        // 비밀번호 체크
        if (!passwordEncoder.matches(password, users.get(0).getPassword())) {
            throw new BadCredentialsException("비밀번호가 틀렸습니다.");
        }

        // username과 password를 통하여 UsernamePasswordAuthenticationToken을 생성
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        logger.debug("JwtAuthenticationFilter : 토큰생성완료");

        // authenticationToken을 통하여 Authentication을 생성
        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);

        // authentication을 통해 미리 정의했던 CustomUserDetails형태의 principal을 생성
        CustomUserDetails principal = (CustomUserDetails) authentication.getPrincipal();

        // principal을 이용하여 Jwt토큰 생성
        String accessToken = tokenProvider.createAccessJwt(principal);
        String refreshToken = tokenProvider.createRefreshJwt(principal);

        users.get(0).setRefreshToken(refreshToken);


        return LoginResponseDto.builder().accessToken(accessToken).refreshToken(refreshToken).build();
    }

    // AccessToken이 만료되었지만 refreshToken이 유효하다면 Token들을 재발급 해준다.
    public LoginResponseDto refresh(HttpServletRequest request) {
        // 헤더에서 refreshToken정보를 꺼내온다.
        String refreshToken = tokenProvider.resolveToken(request);

        // refreshToken 유효성 검사
        boolean validate = tokenProvider.validateRefreshToken(refreshToken);

        // user 정보로 정의했던 CustomUserDetails를 만들어 Jwt토큰 생성
        String username = tokenProvider.getRefreshUsername(refreshToken);
        List<User> user = userRepository.findByUsername(username);

        if (!user.get(0).getRefreshToken().equals(refreshToken)){
            throw new BadCredentialsException("잘못된 접근입니다.");
        }

        CustomUserDetails cud = new CustomUserDetails(user.get(0));

        String newAccessToken = tokenProvider.createAccessJwt(cud);
        String newRefreshToken = tokenProvider.createRefreshJwt(cud);
        return LoginResponseDto.builder().accessToken(newAccessToken).refreshToken(newRefreshToken).build();
    }

    // 레디스 블랙리스트에 accessToken값을 저장
    // accessToken이 만료되지 않았을 때 로그아웃하면 재사용이 불가능 하도록 Redis에 저장해둠
    public String logout(HttpServletRequest request) {
        String accessToken = tokenProvider.resolveToken(request);
        String username = tokenProvider.getUsername(accessToken);
        List<User> user = userRepository.findByUsername(username);

        user.get(0).setRefreshToken("");
        redisUtil.setBlackList(accessToken, "accessToken", 5);
        return "로그아웃";
    }
}

UserRepository

package com.jh.jwt.repository;

import com.jh.jwt.domain.User;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;

@Repository
public class UserRepository{
    @PersistenceContext
    private EntityManager em;

    // 데이터베이스에 사용자 저장
    public Long save(User user) { em.persist(user); return user.getId();}

    // 사용자이름을 통해 사용자 조회
    public List<User> findByUsername(String username) {
        return em.createQuery("select u from User u where u.username = :username", User.class)
                .setParameter("username", username)
                .getResultList();
    }

    // 사용자 id를 통해 사용자 조회
    public User findOne(Long id) { return em.find(User.class, id);}
}

로그아웃과 관련된 내용은 다음 게시글에 Redis 설정부터 올리겠습니다.

+ Recent posts