우선 인증과 인가 코드를 작성하기 위해 의존성을 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
스프링 시큐리티는 기본적으로 UsernamePasswordAuthenticationFilter를 통해 인증을 수행하도록 구성
-> 이 필터는 인증이 실패하면 로그인 폼이 포함된 화면을 전달하게 되는데, 이 프로젝트에는 이러한 화면 존재 안함
따라서 JWT를 사용하는 인증 필터를 구현하고 UsernamePasswordAuthenticationFilter 앞에 인증 필터를 배치해서 인증 주체를 변경하는 작업을 수행하는 방식으로 구성
User(사용자 정보) 엔티티 생성
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(nullable = false, unique = true)
private String uid;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String name;
@ElementCollection(fetch = FetchType.EAGER)
@Builder.Default
private List<String> roles = new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Override
public String getUsername() {
return this.uid;
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@Override
public boolean isEnabled() {
return false;
}
}
이 엔티티는 앞으로 토큰을 생성할때 토큰의 정보로 사용될 정보와 권한 정보를 갖게 됨
UserDetails 인터페이스
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
getAuthorities()
: 계정이 가지고 있는 권한 목록 리턴getPassword()
: 계정의 비밀번호 리턴getUsername()
: 계정 이름 리턴isAccountNonExpired()
: 계정이 만료됐는지 리턴 -> true는 완료되지 않음 의미isAccountNonLocked()
: 계정이 잠겨있는지 리턴 -> true는 잠기지 않음isCredentialNonExpired()
: 비밀번호가 만료됐는지 리턴 -> true는 만료X 의미isEnabled()
: 계정이 활성화돼 있는지 리턴 -> true는 활성화 상태 의미엔티티를 조회하기 위한 리포지토리와 서비스 구현
public interface UserRepository extends JpaRepository<User, Long> {
User getByUid(String uid);
}
ID 값은 인덱스 값이기 때문에 id값을 토큰 생성 정보로 사용하기 위해 getByUid() 메서드를 생성
리포지토리를 통해 User엔티티의 id를 가져오는 서비스 생성
UserDetailsServiceImpl 구현
@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final Logger LOGGER = LoggerFactory.getLogger(UserDetailsServiceImpl.class)
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username){
LOGGER.info("[loadUserByUsername] loadUserByUsername 수행. username : {}", username);
return userRepository.getByUid(username);
}
}
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetails는 스프링 시큐리티에서 제공하는 개념으로, UserDetails의 username은 각 사용자를 구분할수 있는 ID 의미
위 인터페이스를 보면 username을 가지고 UserDetails 객체를 리턴하는데, UserDetails의 구현체로 User 엔티티를 생성하였기에 User 객체를 리턴하게끔 구현한 것