개발/Spring & Springboot

[SpringBoot] JWT Tutorial 7 - 회원가입, 권한검증

뚜키 💻 2022. 5. 29. 23:38
반응형

## 개인기록용

# Spring Boot JWT Tutorial 학습

https://github.com/jennie267/jwt-tutorial

 

[SpringBoot] JWT Tutorial 1 - JWT

[SpringBoot] JWT Tutorial 2 - 프로젝트 생성

[SpringBoot] JWT Tutorial 3 - Security 설정

[SpringBoot] JWT Tutorial 4 - Data 설정

[SpringBoot] JWT Tutorial 5 - JWT 코드, Security 설정 추가

[SpringBoot] JWT Tutorial 6 - DTO, Repository, 로그인

>  [SpringBoot] JWT Tutorial 7 - 회원가입, 권한검증


 

- 회원가입 API 생성

- 허용권한이 다른 API들을 만들어서 권한검증 확인

 

0. 기타 추가사항

AuthorityDto.java 생성

package study.cherry.jwttutorial.dto;

import lombok.*;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AuthorityDto {
    private String authorityName;
}

 

UserDto.java 아래 내용 추가

...
public class UserDto {

...
    private Set<AuthorityDto> authorityDtoSet;

    public static UserDto from(User user) {
        if(user == null) return null;

        return UserDto.builder()
                .username(user.getUsername())
                .nickname(user.getNickname())
                .authorityDtoSet(user.getAuthorities().stream()
                        .map(authority -> AuthorityDto.builder().authorityName(authority.getAuthorityName()).build())
                        .collect(Collectors.toSet()))
                .build();
    }

}

 

 

1. 유틸리티 메서드를 만들기 위해 SecurityUtil 클래스 생성

- util 패키지 추가

- getCurrentUsername 메서드 생성 : Security Context의 Authentication 객체를 이용해 username을 리턴해주는 유틸성 메서드

(Security Context에 Authentication 객체가 저장되는 시점은 JwtFilter의 doFilter메서드에서 Request가 들어올때 SecurityContext에 Authentication 객체가 저장됨)

 

SecurityUtil.java

package study.cherry.jwttutorial.util;

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() {
    }

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

 

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

- UserRepository, PasswordEncoder 주입

- signup 메서드 : 회원가입 로직을 수행하는 메서드

username이 DB에 존재하지 않으면 Authority와 User 정보를 생성하여 UserRepository의 save를 통해 DB에 정보 저장

- signup 메서드를 통해 가입한 회원은 ROLE_USER라는 권한을 가지고 있고 data.sql에서 자동생성되는 admin 계정은 USER, ADMIN ROLE을 가지고 있음 -> 허용권한을 다르게 줬으니 이 차이를 가지고 권한검증 테스트

 

- getUserWithAuthorities : username을 기준으로 권한정보를 가져오는 메서드

- getMyUserWithAuthorities : SecurityContext에 저장된 username에 해당하는 정보만 가져옴

 

- 위 두 메서드의 허용권한을 다르게해서 테스트

 

UserService.java

package study.cherry.jwttutorial.service;


import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import study.cherry.jwttutorial.dto.UserDto;
import study.cherry.jwttutorial.entity.Authority;
import study.cherry.jwttutorial.entity.User;
import study.cherry.jwttutorial.repository.UserRepository;
import study.cherry.jwttutorial.util.SecurityUtil;

import java.util.Collections;

@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 RuntimeException("이미 가입되어 있는 유저입니다.");
        }

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

 

 

3. UserService의 메서드들을 호출할 UserController 생성

- signup : 회원가입

@PreAuthorize  : 권한별로 접근 제어 통제

- getMyUserInfo -> USER, ADMIN 두가지 권한 모두 허용

- getUserInfo -> ADMIN 권한만 허용

 

UserController.java

package study.cherry.jwttutorial.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import study.cherry.jwttutorial.dto.UserDto;
import study.cherry.jwttutorial.entity.User;
import study.cherry.jwttutorial.service.UserService;

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<UserDto> signup(@Valid @RequestBody UserDto userDto) {
        return ResponseEntity.ok(userService.signup(userDto));
    }

    @GetMapping("/user")
    @PreAuthorize("hasAnyRole('USER','ADMIN')")
    public ResponseEntity<UserDto> getMyUserInfo() {
        return ResponseEntity.ok(userService.getMyUserWithAuthorities());
    }

    @GetMapping("/user/{username}")
    @PreAuthorize("hasAnyRole('ADMIN')")
    public ResponseEntity<UserDto> getUserInfo(@PathVariable String username) {
        return ResponseEntity.ok(userService.getUserWithAuthorities(username));
    }
}

 

4. 테스트 (Postman, H2 Console)

4-1. 회원가입

* Postman

* h2 console (http://localhost:8080/h2-console)

 

4-2. admin 권한만 있는 api 호출

- admin 계정의 token으로 호출하니까 잘 조회됨 (postman에 response저장기능을 이용해서 담아뒀었음)

 

- cherry 계정의 token으로 호출시에 조회 안됨 (403 Forbidden)

 

4-3. User권한을 허용한 api를 cherry 계정의 token으로 조회

- cherry 권한으로도 조회가능한 api이므로 잘 조회됨

 

 


 

* 최종 구조

 

 

 

 


 

Reference

Spring Boot JWT Tutorial

https://github.com/SilverNine/spring-boot-jwt-tutorial

 

GitHub - SilverNine/spring-boot-jwt-tutorial

Contribute to SilverNine/spring-boot-jwt-tutorial development by creating an account on GitHub.

github.com

 

반응형