[SpringBoot] 스프링부트 로그인 구현- (5) 회원가입, 권한 검증
01. 회원가입 API 생성
1. util 패키지 생성
2. 간단한 유틸리티 메소드를 만들기 위해 SecurityUtil 클래스 생성
util > SecurityUtil.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Optional;
public class SecurityUtil {
private static final Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
private SecurityUtil() {
}
/* SecurityContext의 Authentication 객체를 이용해 username을 리턴 */
public static Optional<String> getCurrentUsername() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
logger.debug("Security Context에 인증 정보가 없습니다.");
return Optional.empty();
}
String username = null;
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
username = springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
username = (String) authentication.getPrincipal();
}
return Optional.ofNullable(username);
}
}
SecurityContext에 Authentication 객체가 저장되는 시점은 JwtFilter의 doFilter 메소드에서 Request가 들어올 때이다.
3. UserService 클래스 작성
회원가입, 유저정보조회 등의 메소드를 만들기 위함
service > UserService.java
import java.util.Collections;
import java.util.Optional;
import com.example.tutorial.dto.UserDto;
import com.example.tutorial.entity.Authority;
import com.example.tutorial.entity.User;
import com.example.tutorial.repository.UserRepository;
import com.example.tutorial.util.SecurityUtil;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
// UserRepository, PasswordEncoder를 주입 받음
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
/* 회원 가입 로직 수행 */
@Transactional
public User signup(UserDto userDto) {
// 파라미터로 받은 UserDto의 username을 기준으로 해서
// 이미 DB에 존재하는지 찾음
if (userRepository.findOneWithAuthoritiesByUsername(userDto.getUsername()).orElse(null) != null) {
throw new RuntimeException("이미 가입되어 있는 유저입니다.");
}
// DB에 존재하지 않으면 Authority와
Authority authority = Authority.builder()
.authorityName("ROLE_USER") // (*) signup 메소드를 통해 가입한 회원은 USER ROLE을 가짐
.build(); // +) data.sql에서 자동생성되는 admin 계정은
// USER, ADMIN ROLE을 가지고 있었음
// User 정보를 생성해서
User user = User.builder()
.username(userDto.getUsername())
.password(passwordEncoder.encode(userDto.getPassword()))
.nickname(userDto.getNickname())
.authorities(Collections.singleton(authority))
.activated(true)
.build();
// UserRepository의 save 메소드를 통해 DB에 정보를 저장함
return userRepository.save(user);
}
/* 어떠한 username을 이든 username에 해당하는 유저 객체와 권한정보 가져옴 */
@Transactional(readOnly = true)
public Optional<User> getUserWithAuthorities(String username) {
return userRepository.findOneWithAuthoritiesByUsername(username);
}
/* 현재 SecurityContext에 저장된 username에 해당하는 유저 객체와 권한정보만 가져옴 */
@Transactional(readOnly = true)
public Optional<User> getMyUserWithAuthorities() {
return SecurityUtil.getCurrentUsername().flatMap(userRepository::findOneWithAuthoritiesByUsername);
}
}
02. 권한검증 확인
1. UserService의 메소드를 호출할 UserController 작성
controller > UserController.java 생성
import com.example.tutorial.dto.UserDto;
import com.example.tutorial.entity.User;
import com.example.tutorial.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
@RestController
@RequestMapping("/api")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/signup")
public ResponseEntity<User> signup(
@Valid @RequestBody UserDto userDto
) {
return ResponseEntity.ok(userService.signup(userDto));
}
@GetMapping("/user")
@PreAuthorize("hasAnyRole('USER','ADMIN')") // USER, ADMIN 권한 모두 허용
public ResponseEntity<User> getMyUserInfo() {
return ResponseEntity.ok(userService.getMyUserWithAuthorities().get());
}
@GetMapping("/user/{username}")
@PreAuthorize("hasAnyRole('ADMIN')") // ADMIN 권한만 호출 가능
public ResponseEntity<User> getUserInfo(@PathVariable String username) {
return ResponseEntity.ok(userService.getUserWithAuthorities(username).get());
}
}
2. 포스트맨, H2 콘솔을 이용한 3개의 API 테스트
- signup Request
http://localhost:8080/api/signup
으로 POST 요청을 보내면
회원가입 API에 대한 응답이 정상적으로 리턴된다.
가입정보를 H2 콘솔에서 확인해보면 cgh 유저가 잘 등록된 것을 볼 수 있다.
cgh 유저의 권한이 USER로 잘 설정되었다. admin 유저는 USER, ADMIN 두 가지 권한을 소유하고 있다.
- getUserInfo Request
먼저 Authorization 탭에서 token 정보를 넣어준다.
jwt_tutorial_token은 authenticate Request의 Tests 탭에서 response를 파싱해서 토큰 정보를 담아놓았던 변수이다. 이 변수를 다른 Request에서 사용할 수 있다.
http://localhost:8080/api/user/cgh
로 GET 요청을 보내면 admin 계정의 토큰으로 cgh 유저의 정보를 가져올 수 있음을 확인할 수 있다.
이번에는 cgh 유저의 토큰으로 같은 API를 재호출 해보자.
http://localhost:8080/api/authenticate
로 다음과 같이 POST 요청을 보내 cgh 유저의 토큰을 생성한다.
다시http://localhost:8080/api/user/cgh
로 GET 요청을 보내면 다음과 같이 403 Forbidden 에러가 리턴된다.
cgh 유저의 토큰으로는 이 API를 호출할 수 있는 권한이 없기 때문이다.
+) 403 Forbidden 에러가 리턴됨으로써 JwtAccessDeniedHandler가 잘 작동하였음을 알 수 있다.
- getMyUserInfo Request
USER 권한을 허용해줬던 API를 똑같이 cgh 유저의 토큰으로 호출해볼 것이다.
http://localhost:8080/api/user
로 GET 요청을 보내면 다음과 같이 호출이 잘 되는 것을 확인할 수 있다.
Author And Source
이 문제에 관하여([SpringBoot] 스프링부트 로그인 구현- (5) 회원가입, 권한 검증), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@cutehuman/SpringBoot-스프링부트-로그인-구현-5저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)