ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot JWT Tutorial (4)권한 다른 API
    Spring/SpringSecurity 2022. 4. 13. 01:17

    ===

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-jwt/dashboard

     

    [무료] Spring Boot JWT Tutorial - 인프런 | 강의

    Spring Boot, Spring Security, JWT를 이용한 튜토리얼을 통해 인증과 인가에 대한 기초 지식을 쉽고 빠르게 학습할 수 있습니다., - 강의 소개 | 인프런...

    www.inflearn.com

     

    ===

    - 회원가입 API 생성

    - 권한검증 확인

     

    ===

    간단한 유틸리티 메소드를 만들기 위해 SecurityUtil 클래스를 생성

    public class SecurityUtil {
    
       private static final Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
    
       private SecurityUtil() {
       }
    
       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);
       }
    }

    getCurrentUsername이라는 메소드를 하나 갖고 있는 클래스

    SecurityContext에서 Authentication객체를 꺼내와서 이걸 통해 username을 return해주는 간단한 util성 메소드

     

    *SecurityContext에 Authentication객체가 저장되는 시점은 

    우리가 전에 만들었던 JwtFilter의 doFilter메소드를 보면

    request가 들어오는 시점에 SecurityContext에 Authentication객체가 저장이 됨. 이때 저장된게 꺼내지게 되는것

     

    ===

    회원가입, 유저정보조회 등의 메소드를 만들기 위해 UserService 클래스

    @Service
    public class UserService {
        private final UserRepository userRepository;
        private final PasswordEncoder passwordEncoder;
    
        public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
            this.userRepository = userRepository;
            this.passwordEncoder = passwordEncoder;
        }
    
        @Transactional
        public UserDto signup(UserDto userDto) {
            if (userRepository.findOneWithAuthoritiesByUsername(userDto.getUsername()).orElse(null) != null) {
                throw new DuplicateMemberException("이미 가입되어 있는 유저입니다.");
            }
    
            Authority authority = Authority.builder()
                    .authorityName("ROLE_USER")
                    .build();
    
            User user = User.builder()
                    .username(userDto.getUsername())
                    .password(passwordEncoder.encode(userDto.getPassword()))
                    .nickname(userDto.getNickname())
                    .authorities(Collections.singleton(authority))
                    .activated(true)
                    .build();
    
            return UserDto.from(userRepository.save(user));
        }
    
        @Transactional(readOnly = true)
        public UserDto getUserWithAuthorities(String username) {
            return UserDto.from(userRepository.findOneWithAuthoritiesByUsername(username).orElse(null));
        }
    
        @Transactional(readOnly = true)
        public UserDto getMyUserWithAuthorities() {
            return UserDto.from(SecurityUtil.getCurrentUsername().flatMap(userRepository::findOneWithAuthoritiesByUsername).orElse(null));
        }
    }

    UserRepository, PasswordEncoder를 주입받음

    signup메소드는 회원가입 로직 수행 메소드

    파라미터로 받은 userDto안에 username을 기준으로 이미 DB에 이 username으로 저장돼 있는지 먼저 찾아보고

     

    없으면 권한정보를 만들고

     

    권한정보를 넣어 유저정보를 만듦

    그리고 userRepository의 save로 DB저장

     

    이 유저(signup메소드를 통해 가입한 회원)는 ROLE_USER라는 권한 하나 갖고 있다!

     

    data.sql에서 자동 생성되는 admin 계정은

    권한을 두개 갖고 있다. ROLE_USER와 ROLE_ADMIN

     

    이 차이를 통해 추후에 권한 검증 부분을 테스트

     

    ===

    그리고 유저 권한정보를 갖고 오는 메소드가 2개

    위에건 username을 받아서 username에 해당하는 user객체와 권한정보를 갖고 오는 메소드

    아래건 현재 시큐리티 컨텍스트에 저장이 돼 있는 username에 해당하는 유저정보와 권한정보만 받아갈 수 있다.

    이 두가지 메소드의 허용권한을 다르게 해서 권한 검증에 대한 부분을 테스트할 것

     

    ===

    UserService의 메소드들을 호출할 UserController생성

    @RestController
    @RequestMapping("/api")
    public class UserController {
        private final UserService userService;
    
        public UserController(UserService userService) {
            this.userService = userService;
        }
    
        @GetMapping("/hello")
        public ResponseEntity<String> hello() {
            return ResponseEntity.ok("hello");
        }
    
        @PostMapping("/test-redirect")
        public void testRedirect(HttpServletResponse response) throws IOException {
            response.sendRedirect("/api/user");
        }
    
        @PostMapping("/signup")
        public ResponseEntity<UserDto> signup(
                @Valid @RequestBody UserDto userDto
        ) {
            return ResponseEntity.ok(userService.signup(userDto));
        }
    
        @GetMapping("/user")
        @PreAuthorize("hasAnyRole('USER','ADMIN')")
        public ResponseEntity<UserDto> getMyUserInfo(HttpServletRequest request) {
            return ResponseEntity.ok(userService.getMyUserWithAuthorities());
        }
    
        @GetMapping("/user/{username}")
        @PreAuthorize("hasAnyRole('ADMIN')")
        public ResponseEntity<UserDto> getUserInfo(@PathVariable String username) {
            return ResponseEntity.ok(userService.getUserWithAuthorities(username));
        }
    }

    /signup는 UserDto객체를 받아서 우리가 만들었던 userService의 signup메소드를 수행

     

    /user는 @PreAuthorize 를 통해 USER, ADMIN 이 두 롤 모두 호출할 수 있는 API

     

    /user/{username}은

    ADMIN만 호출할 수 있다

    =>UserService에서 만들었던

     

    이 3개의 API를 테스트해보기

     

    ===

    정상리턴

    H2 Console보면

    이렇게 하는구만?

     

    ===

    권한이 다른 두 계정을 갖고, 허용권한이 달랐던 두개의 API 테스트

    헤더에 토큰을 안넣어서

     

    라고 하면

    자동으로 채워져 있는데

    authenticate request의 tests탭에서 response를 파싱해서 token필드에 있는 값을 jwt_tutorial_token이라는 변수에 담았었다. 이 변수를 다른 Request에서 사용할 수 있다.

     

    다시 보내면

    토큰이 admin계정의 토큰이었어서 다른 계정의 정보를 가져올 수 있었다.

     

    ===

    회원계정으로 보내기

     

    기존 로그인 API를 복사

    send눌러서 토큰을 받으면

    여기 써져있던(▽복사한 API이니 이미 써져있다) 변수에 담김

     

    이제

    이걸로 요청해보면 403 Forbidden. 저기 Token에 일반 회원것이 들어있어. 권한없음.

    403 Forbidden에러는

    이게 잘 작동한 것

     

    ===

    다되는 API는 잘 호출됨

    'Spring > SpringSecurity' 카테고리의 다른 글

    인증, 인가를 프로젝트에 끼워넣기  (0) 2022.04.23
    Spring Boot JWT Tutorial (3)  (0) 2022.04.12
    Spring Boot JWT Tutorial (2)JWT  (0) 2022.04.12
    Spring Boot JWT Tutorial (1)설정, DB  (0) 2022.04.12
Designed by Tistory.