DTO ๊ณ์ธต ์์ฑ
LoginDto
package com.oliviarla.tutorial.dto;
import lombok.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginDto {
@NotNull
@Size(min = 3, max = 50)
private String username;
@NotNull
@Size(min = 3, max = 100)
private String password;
}
TokenDto
package com.oliviarla.tutorial.dto;
import lombok.*;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TokenDto {
private String token;
}
UserDto
package com.oliviarla.tutorial.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserDto {
@NotNull
@Size(min = 3, max = 50)
private String username;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@NotNull
@Size(min = 3, max = 100)
private String password;
@NotNull
@Size(min = 3, max = 50)
private String nickname;
}
@JsonProperty ์ฌ์ฉํ๋ ์ด์ : ์๋ต๊ฐ์ password ํฌํจํ์ง ์๊ธฐ ์ํด JsonIgnore ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ ๋ Deserialize๊น์ง ๋ฌด์ํ๊ฒ ๋์ด ์ ํจ์ฑ ์๋ฌ๊ฐ ๋ฐ์ -> ์ฐ๋ ค๋ ๊ฒฝ์ฐ(deserialize)์๋ง ์ ๊ทผ ํ์ฉํ ์ ์๋๋ก ํจ
Repository ์์ฑ
UserRepository.java
package com.oliviarla.tutorial.repository;
import com.oliviarla.tutorial.entity.User;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(attributePaths = "authorities")
Optional<User> findOneWithAuthoritiesByUsername(String username);
}
username์ ๊ธฐ์ค์ผ๋ก User ์ ๋ณด ๊ฐ์ ธ์ค๋ ์ญํ ์ํ
authorities ์ ๋ณด๋ Eager ์กฐํ๋ก User์ ์กฐ์ธํด์ ๊ฐ์ ธ์ด
์๋น์ค ๊ณ์ธต ์์ฑ
package com.oliviarla.tutorial.service;
import com.oliviarla.tutorial.entity.User;
import com.oliviarla.tutorial.repository.UserRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Component("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
@Transactional
public UserDetails loadUserByUsername(final String username) {
return userRepository.findOneWithAuthoritiesByUsername(username)
.map(user -> createUser(username, user))
.orElseThrow(() -> new UsernameNotFoundException(username + " -> ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐพ์ ์ ์์ต๋๋ค."));
}
private org.springframework.security.core.userdetails.User createUser(String username, User user) {
if (!user.isActivated()) {
throw new RuntimeException(username + " -> ํ์ฑํ๋์ด ์์ง ์์ต๋๋ค.");
}
List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getAuthorityName()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(),
grantedAuthorities);
}
}
UserDetailsService ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ณ UserRepository๋ฅผ ์ฃผ์ ๋ฐ์
loadUserByUsername(): ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ User ์ ๋ณด ์กฐํ
createUser(): ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์กฐํํด์จ User ๋ฐ ๊ถํ ์ ๋ณด๋ฅผ userdetails ํจํค์ง์ User ๊ฐ์ฒด๋ก ๋ณํํ์ฌ ๋ฐํ
์ปจํธ๋กค๋ฌ ๊ณ์ธต ์์ฑ
AuthController.java
package com.oliviarla.tutorial.controller;
import com.oliviarla.tutorial.dto.LoginDto;
import com.oliviarla.tutorial.dto.TokenDto;
import com.oliviarla.tutorial.jwt.JwtFilter;
import com.oliviarla.tutorial.jwt.TokenProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/api")
public class AuthController {
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.tokenProvider = tokenProvider;
this.authenticationManagerBuilder = authenticationManagerBuilder;
}
@PostMapping("/authenticate")
public ResponseEntity<TokenDto> authorize(@Valid @RequestBody LoginDto loginDto) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.createToken(authentication);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
return new ResponseEntity<>(new TokenDto(jwt), httpHeaders, HttpStatus.OK);
}
}
- authorize(): username, password๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ ฅ๋ฐ์ UserPasswordAuthenticationToken ๊ฐ์ฒด ์์ฑ
- UserPasswordAuthenticationToken ๊ฐ์ฒด๋ก authenticate ๋ฉ์๋ ๋ก์ง์ ์ํ
- ์ธ์ฆ ์ ๋ณด authentication์ ํ์ฌ ์คํ์ค์ธ ์ค๋ ๋์ ์ ์ฅ
- authentication ๊ธฐ๋ฐ์ผ๋ก jwt ํ ํฐ ์์ฑ
- ์์ฑ๋ ํ ํฐ์ httpHeaders์ ๋ฃ๊ณ Tokendto ๊ฐ์ฒด๋ฅผ ์ด์ฉํด Response Body์๋ ๋ฃ์ด ๋ฐํ
๋ก๊ทธ์ธ ํ ์คํธ
Tests ํญ์์ ๋ค์๊ณผ ๊ฐ์ด ์ฝ๋ฉํ๋ฉด Postman ์ ์ญ ๋ณ์์ ํด๋น ํ ํฐ ๋ด์ ๋ค๋ฅธ ํธ์ถ ์์ ์์ ์ฌ์ฉ ๊ฐ๋ฅ
var jsonData = JSON.parse(responseBody)
pm.globals.set("jwt_tutorial_token", jsonData.token);
์ถ์ฒ: https://eglowc.tistory.com/28, https://silvernine.me/wp/?p=1152
๋๊ธ