튜토리얼 코드
https://github.com/SilverNine/spring-boot-jwt-tutorial
바로가기
Spring Boot JWT Tutorial (1) – JWT 소개, 프로젝트 생성
Spring Boot JWT Tutorial (2) – Security 기본 설정, Data 설정
Spring Boot JWT Tutorial (3) – JWT 코드, Security 설정 추가
Spring Boot JWT Tutorial (4) – Repository, 로그인
Spring Boot JWT Tutorial (5) – 회원가입, 권한검증
회원가입
SecurityUtil.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package me.silvernine.tutorial.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); } } |
util 패키지를 생성한 후 SecurityUtil 클래스를 생성합니다.
getCurrentUsername() 메소드는 JwtFilter 클래스의 doFilter 메소드에서 저장한 Security Context의 인증 정보에서 username을 리턴합니다.
UserService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
package me.silvernine.tutorial.service; import me.silvernine.tutorial.dto.UserDto; import me.silvernine.tutorial.entity.Authority; import me.silvernine.tutorial.entity.User; import me.silvernine.tutorial.repository.UserRepository; import me.silvernine.tutorial.util.SecurityUtil; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Collections; import java.util.Optional; @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 User 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 userRepository.save(user); } @Transactional(readOnly = true) public Optional<User> getUserWithAuthorities(String username) { return userRepository.findOneWithAuthoritiesByUsername(username); } @Transactional(readOnly = true) public Optional<User> getMyUserWithAuthorities() { return SecurityUtil.getCurrentUsername().flatMap(userRepository::findOneWithAuthoritiesByUsername); } } |
UserRepository, PasswordEncoder를 주입받는 UserService 클래스를 생성합니다.
signup 메소드는 이미 같은 username으로 가입된 유저가 있는 지 확인하고, UserDto 객체의 정보들을 기반으로 권한 객체와 유저 객체를 생성하여 Database에 저장합니다.
중요! 이 메소드를 통해 생성되는 유저는 ROLE_USER 권한을 소유합니다. 이 메소드를 통해 가입한 유저는 ROLE_ADMIN 권한만 호출할 수 있는 API는 호출할 수 없겠죠?
getUserWithAuthorities 메소드는 username을 파라미터로 받아 해당 유저의 정보 및 권한 정보를 리턴합니다.
getMyUserWithAuthorities 메소드는 위에서 만든 SecurityUtil의 getCurrentUsername() 메소드가 리턴하는 username의 유저 및 권한 정보를 리턴합니다.
UserController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package me.silvernine.tutorial.controller; import me.silvernine.tutorial.dto.UserDto; import me.silvernine.tutorial.entity.User; import me.silvernine.tutorial.service.UserService; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; 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')") public ResponseEntity<User> getMyUserInfo() { return ResponseEntity.ok(userService.getMyUserWithAuthorities().get()); } @GetMapping("/user/{username}") @PreAuthorize("hasAnyRole('ADMIN')") public ResponseEntity<User> getUserInfo(@PathVariable String username) { return ResponseEntity.ok(userService.getUserWithAuthorities(username).get()); } } |
controller 패키지에 UserService를 주입받는 UserController 클래스를 생성합니다.
signup 메소드는 회원가입 API 이고 SecurityConfig.java 에서 permitAll를 설정했기 때문에 권한 없이 호출할 수 있습니다.
getMyUserInfo 메소드는 현재 Security Context에 저장되어 있는 인증 정보의 username을 기준으로 한 유저 정보 및 권한 정보를 리턴하는 API 입니다. @PreAuthorize(“hasAnyRole(‘USER’,’ADMIN’)”) 어노테이션을 이용해서 ROLE_USER, ROLE_ADMIN 권한 모두 호출 가능하게 설정합니다.
getUserInfo 메소드는 username을 파라미터로 받아 해당 username의 유저 정보 및 권한 정보를 리턴합니다. @PreAuthorize(“hasAnyRole(‘ADMIN’)”) 어노테이션을 이용해서 ROLE_ADMIN 권한을 소유한 토큰만 호출할 수 있도록 설정합니다.
회원가입 테스트
http://localhost:8080/api/signup API를 호출하여 silvernine 유저를 등록했습니다.
http://localhost:8080/h2-console 에 접속하여 확인해보니 정상적으로 silvernine 유저가 추가된 것을 확인할 수 있습니다.
권한검증
권한검증 테스트
신규 회원가입했던 silvernine 유저의 토큰을 /api/authenticate API를 통해 발급받습니다.
Authorization 탭을 클릭하고 Bearer Token을 선택합니다. Token 항목에는 /api/authenticate API의 결과 토큰값을 넣으셔도 되지만 우리는 포스트맨 전역변수에 토큰을 담아놓았기 때문에 해당 변수를 사용하도록 하겠습니다.
호출 후 정상 적으로 유저 정보 및 권한 정보가 리턴되는 것을 확인할 수 있습니다.
이번엔 /api/user/{username} API를 호출해 보겠습니다. 이번에는
5편에 걸쳐 Spring Boot JWT Tutorial을 진행해보았습니다. 너무 긴가요? 최대한 심플하게 구성을 해보자 했지만 그래도 길어지는 것을 어쩔수 없네요.
이 내용들은 후에 유튜브 영상으로 한번 찍어봐도 좋을 것 같습니다.
오늘 하루도 다들 행복하시길 바랍니다. 그럼 이만!