회원가입 API 생성
권한검증 확인
간단한 유틸리티 메소드를 만들기 위해 SecurityUtil 클래스 생성
package com.example.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);
}
}
getCurrentUsername메소드를 가지고 있는 클래스로 해당 메소드의 역할은 Security Context의 Authentication 객체를 이용해 username을 리턴해주는 간단한 유틸성 메소드입니다.
Security Context에 Authentication 객체가 저장되는 시점은 JwtFilter의 doFilter메소드에서 Request가 들어올 때 SecurityContext에 Authentication 객체를 저장하게 되는데 이때 저장된 객체를 꺼내게 됩니다.
회원가입, 유저정보조회 등의 메소드를 만들기 위해 UserService 클래스를 생성
package com.example.jwttutorial.service;
import com.example.jwttutorial.dto.UserDto;
import com.example.jwttutorial.entity.Authority;
import com.example.jwttutorial.entity.JwtUser;
import com.example.jwttutorial.exception.DuplicateMemberException;
import com.example.jwttutorial.repository.JwtUserRepository;
import com.example.jwttutorial.util.SecurityUtil;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
@Service
public class UserService {
private final JwtUserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(JwtUserRepository 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();
JwtUser user = JwtUser.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));
}
}
UserService는 UserRepository, PasswordEncoder를 주입받습니다.
signup 메소드는 회원가입 로직을 수행하는 메소드입니다. 파라미터로 받은 username이 DB에 존재하지 않으면 Authority(권한정보)와 User 정보를 생성해서 UserRepository의 save 메소드를 통해 DB에 정보(User정보와 권한정보)를 저장합니다.
여기서 중요한 점은 signup 메소드를 통해 가입한 회원은 USER ROLE을 가지고 있고 data.sql에서 자동 생성되는 admin 계정은 USER, ADMIN ROLE을 가지고 있습니다. (이 차이에 대해 권한검증 부분을 테스트)
UserService 메소드들을 호출할 UserController를 생성
package com.example.jwttutorial.controller;
import com.example.jwttutorial.dto.UserDto;
import com.example.jwttutorial.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.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
@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 메소드를 호출합니다.
getMyUserInfo 메소드는 @PreAuthorize를 통해서 USER, ADMIN 두가지 권한 모두 허용했고 getUserInfo 메소드는 ADMIN 권한만 호출할 수 있도록 설정했습니다.
getUserInfo 메소드는 UserService에서 만들었던 username 파라미터를 기준으로 유저 정보와 권한 정보를 리턴해줍니다.
signup 을 실행시켜보면 잘 생성이 된 것을 확인
{
"username":"name",
"password":"1234",
"nickname":"nickname"
}
admin 유저는 ROLE_ADMIN, ROLE_USER 두가지 권한을 소유하고 있고
새로 만든 유저는 ROLE_USER 권한만 소유하고 있습니다.
권한이 다른 두 계정을 가지고 허용 권한이 다른 두개의 API 를 테스트합니다.
전에 담아둔 토큰 변수를 헤더에 넣어주고 getUser를 테스트합니다.
http://localhost:8080/api/user/name 은 admin 만 허용
http://localhost:8080/api/user 은 둘다 허용 되어있습니다.
'Spring > JWT' 카테고리의 다른 글
DTO, Repository, 로그인 (0) | 2022.05.25 |
---|---|
JWT 코드, Security 설정 추가 (0) | 2022.05.19 |
Security 설정, Data 설정 (0) | 2022.05.18 |
JWT 소개 (0) | 2022.05.17 |