[Security] JWT ๊ตฌํ (6) - ํ์ ๊ฐ์ & Authorization Validation
by rlaehddnd0422๋ง์ง๋ง์ผ๋ก ํ์ ๊ฐ์ ์ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ์์ ์ ์ฅํ๊ณ ๋ก๊ทธ์ธ์ ํตํด JWT๋ฅผ ๋ฐ๊ธ๋ฐ์, ์ ํ๋ ๋ฆฌ์์ค์ ์ ๊ทผ ์ ๋ฐ๊ธ๋ฐ์ JWT๋ฅผ
๊ฒ์ฆํ๋ ๋ก์ง์ ๊ฐ๋ฐํด๋ด ์๋ค.
์ฐ์ , ํ์๊ฐ์ ๋ก์ง์ ๊ฐ๋ฐํ๊ธฐ ์ํด MemberService๋ฅผ ๋ง๋ค์ด์ค์๋ค.
MemberService
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MemberService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
// (1)
@Transactional
public MemberDto signup(MemberDto memberDto) {
Member memberInDb = memberRepository.findOneWithAuthoritiesByUsername(memberDto.getUsername()).orElse(null);
if (memberInDb != null) {
throw new RuntimeException("์ด๋ฏธ ๊ฐ์
๋์ด ์๋ ์ ์ ์
๋๋ค.");
}
Member member = Member.create(memberDto, passwordEncoder.encode(memberDto.getPassword()));
return MemberDto.toDto(memberRepository.save(member));
}
// (2)
public MemberDto getMemberWithAuthorities(String username) {
return MemberDto.toDto(
memberRepository.findOneWithAuthoritiesByUsername(username).orElse(null));
}
// (3)
// SecurityContext ๋ด๋ถ Authentication ๊ฐ์ฒด์ usrename
public MemberDto me() {
return MemberDto.toDto(SecurityUtil.getCurrentUsername()
.flatMap(memberRepository::findOneWithAuthoritiesByUsername).orElse(null));
}
}
singup() ๋ฉ์๋
์ค์ง์ ์ผ๋ก ํ์๊ฐ์ ์ ๋์ํ๋ ๋ฉ์๋์ ๋๋ค.
- MemberDto๋ฅผ ์ธ์๋ก ๋ฐ์ต๋๋ค.
- findOneWithAuthorityByUsername() : JPA ์ฟผ๋ฆฌ ๋ฉ์๋์
๋๋ค.
- memberDto์ username์ผ๋ก Repository์์ member๋ฅผ ์ฐพ๊ณ member์ authority๊น์ง @EntityGraph๋ก ํจ๊ป ํ์ํด์ memberInDb์ ๋ฆฌํดํฉ๋๋ค.
- ๋ง์ฝ ํด๋น ๋ฉค๋ฒ๋ฅผ ์ฐพ์๋ค๋ฉด ์ด๋ฏธ ๊ฐ์ ๋์ด ์๋ ์ ์ ์ด๋ฏ๋ก RuntimeException์ ๋์ง๊ณ ,
public static Member create(MemberDto memberDto, String encodedPw) {
return Member.builder()
.username(memberDto.getUsername())
.password(encodedPw)
.nickname(memberDto.getNickname())
.activated(true)
.authorities(Collections.singleton(Authority.builder().authorityName("ROLE_USER").build()))
.build();
}
4. ๊ทธ๋ ์ง ์์ผ๋ฉด Member์ static๋ฉ์๋์ builder๋ก ํ์์ ํ๋ ๋ง๋ค์ด Repository์ ์ ์ฅํฉ๋๋ค. ์ด ๋ Password๋ ๋ฐ๋์ Encodingํ์ฌ ์ ์ฅํฉ์๋ค.
5. ์ ์ฅํ Member โก๏ธ MemberDto๋ก ๋ณํ, ๋ฆฌํด
getMemberWithAuthoritiesForAdmin()
public MemberDto getMemberWithAuthoritiesForAdmin(String username) {
return MemberDto.toDto(
memberRepository.findOneWithAuthoritiesByUsername(username).orElse(null));
}
- ์ผ๋ฐ์ ์ผ๋ก username์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ member๋ฅผ ์ฐพ๊ณ authority๊น์ง ํจ๊ป ๊ฒ์ํ๋ ๋ฉ์๋์ ๋๋ค.
- (MemberController์์ admin๊ถํ์ ์ฌ์ฉ์๋ง ์ฌ์ฉํ ์ ์๋๋ก ์ค์ ํ ์์
getMemberWithAuthoritiesForUser()
// SecurityContext ๋ด๋ถ Authentication ๊ฐ์ฒด์ usrename
public MemberDto me() {
return MemberDto.toDto(SecurityUtil.getCurrentUsername()
.flatMap(memberRepository::findOneWithAuthoritiesByUsername).orElse(null));
}
public static Optional<String> getCurrentUsername() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
log.info("no authentication info found");
return Optional.empty();
}
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
return Optional.ofNullable(userDetails.getUsername());
}
if (principal instanceof String) {
return Optional.of(principal.toString());
}
throw new IllegalStateException("invalid authentication");
}
- ํด๋ผ์ด์ธํธ๊ฐ ์์ ์ ์ ๋ณด๋ฅผ ๋ณผ ์ ์๋๋ก ๊ฒ์ํ๋ ๋ฉ์๋์ ๋๋ค.
- SecurityContext์์ Authentication ๊ฐ์ฒด๋ฅผ ๊บผ๋ด์ด UserDetails์ Username์ ๊บผ๋ด ๋ฆฌํดํฉ๋๋ค.
- (MemberController์์ admin, user ๊ถํ์ ์ฌ์ฉ์๋ง ์ฌ์ฉํ ์ ์๋๋ก ์ค์ ํ ์์ )
๋ง์ง๋ง์ผ๋ก ํ์๊ฐ์ ์ ์์ฒญํ ์ปจํธ๋กค๋ฌ ๊ณ์ธต์ ๊ฐ๋ฐํด๋ด ์๋ค.
MemberController
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@PostMapping(value = "/signup")
public ResponseEntity<MemberDto> signUp(@Valid @RequestBody MemberDto memberDto) {
return ResponseEntity.ok(memberService.signup(memberDto));
}
@GetMapping(value = "/member", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
public ResponseEntity<MemberDto> findMyInfoForUserAndAdmin() {
return ResponseEntity.ok(memberService.getMemberWithAuthoritiesForUser());
}
@GetMapping(value = "/member/{username}", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasRole('ADMIN')")
@ResponseBody
public ResponseEntity<MemberDto> findInfoForAdmin(@PathVariable String username) {
return ResponseEntity.ok(memberService.getMemberWithAuthoritiesForAdmin(username));
}
}
- ํ์๊ฐ์ ์ ์์ฒญ, ์์ ์ ์ ๋ณด๋ฅผ ์กฐํ, username์ผ๋ก ํ์์ ์ ๋ณด๋ฅผ ๊ฒ์ํ ์ ์๋ API๋ฅผ MemberController์ ์ค๊ณํ์์ต๋๋ค.
@PostMapping(value = "/signup")
public ResponseEntity<MemberDto> signUp(@Valid @RequestBody MemberDto memberDto) {
return ResponseEntity.ok(memberService.signup(memberDto));
}
signUp() - "/api/signup"
- RequestBody : MemberDto
- memberService์ ์ ๊ทผํด RequestBody์ ๋ณด๋ก ํ์๊ฐ์ ์ ์งํํ๊ณ ์์ธ ๋ฐ์ ์์ด ์๋ฃ ๋๋ฉด ResponseEntity์ Dto ๋ด์ ๋ฆฌํด
@GetMapping(value = "/member", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
public ResponseEntity<MemberDto> findMyInfoForUserAndAdmin() {
return ResponseEntity.ok(memberService.getMemberWithAuthoritiesForUser());
}
findMyInfoUserAndAdmin() - "/api/member"
- @PreAuthorize : USER, ADMIN
- ์์ ์ ์ ๋ณด๋ฅผ ์กฐํํ๋ API์ ๋๋ค.
- ์์์ ์ธ๊ธํ memberService์ getMemberWithAuthoritiesForUser() ๋ฉ์๋๋ก ์์ ์ ์ ๋ณด๋ฅผ MemberDto์ ๋ด์ ๋ฆฌํดํฉ๋๋ค.
@GetMapping(value = "/member/{username}", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasRole('ADMIN')")
@ResponseBody
public ResponseEntity<MemberDto> findInfoForAdmin(@PathVariable String username) {
return ResponseEntity.ok(memberService.getMemberWithAuthoritiesForAdmin(username));
}
findInfoForAdmin() - "/api/member/{username}"
- @PathVariable : username
- @PreAuthorize : ADMIN
- ํ์์ ์ ๋ณด๋ฅผ username์ ํตํด ๊ฒ์ํ ์ ์๋ ๋ฉ์๋์ ๋๋ค.
- memberService์ getMemberWithAuthorityForAdmin() ๋ฉ์๋๋ก ์ ์ ์ ์ ๋ณด๋ฅผ ๊ฒ์ํ์ฌ ResponseEntity์ MemberDto๋ฅผ ๋ด์ ๋ฆฌํดํฉ๋๋ค.
Postman Test
๋ง์ง๋ง์ผ๋ก ํฌ์คํธ๋งจ์ ์ฌ์ฉํ์ฌ API๋ฅผ ํ ์คํธ ํด๋ด ์๋ค.
ํ์๊ฐ์ โก๏ธ ๋ก๊ทธ์ธ โก๏ธ ๋ฆฌ์์ค ์์ฒญ
ํ์๊ฐ์
๋ก๊ทธ์ธ
๊ถํ ๊ฒ์ฆ OK
๊ถํ ๊ฒ์ฆ Forbidden
- USER ๊ถํ์ ๊ฐ์ง ์ฌ์ฉ์์ Token์ผ๋ก ADMIN ๊ถํ์ผ๋ก ์ ํ๋ ๋ฆฌ์์ค์ ์ ๊ทผ ์ Forbidden 403 Error๋ฅผ ๋ฆฌํดํฉ๋๋ค.
<์ฐธ๊ณ ์๋ฃ>
๋ณธ ํฌ์คํ ์ ์ ์๊ตฌ๋์ Spring Boot JWT Tutorial ๊ฐ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ์์ฑํ์์ต๋๋ค.
'๐ Backend > Spring Security' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋ธ๋ก๊ทธ์ ์ ๋ณด
Study Repository
rlaehddnd0422