- 인증(Authentication) : 식별 가능한 정보로 등록된 유저의 신원을 입증하는 과정을 의미한다.
- 인가(Authorization) : 인증된 사용자에 대한 자원 접근 권한을 확인한다.
- Ex) 많은 회사가 모여 있는 건물에 들어갈 때 사원증이 필요하다.
- 사원증을 이용해 회사 건물에 들어가는 것을 인증,
- 건물 내 많은 회사 중 나의 회사 층만 들어갈 수 있는 것은 인가이다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'
user
이며, 비밀번호는 콘솔창에 Using generated security password 에 나오게 된다.1, 2 . 로그인 요청이 들어오면 AuthenticationFilter에서 입력된 아이디와 비밀번호는 UsernamePasswordToken으로 바꾸어 전달된다.
3 . AuthenticationManager(구현체는 ProviderManager)는 UsernamePasswordToken 인증을 처리할 AuthenticationProvider를 가지고 있다. (여러 개 존재할 수 있다.)
4 . AuthenticationManager는 토큰을 전달하여 AuthenticationProvider의 구현체들을 통해 인증을 요구한다.
5 . AuthenticationProvider는 UserDetailsService를 통해 사용자 정보를 조회한다.
6, 7 . UserDetailsService은 UserDetails를 리턴해주는 하나의 메소드(loadUserByUsername)를 구현해야 한다. 일반적으로 UserRepository를 주입받아 UserDetails를 리턴한다.
- UserDetails는 인터페이스고 먼저 만든 UserVO 객체가 있다면 주입해줘도 되고, 인터페이스 자체를 구현해도 된다.
8 . AuthenticationProvider가 인증에 성공하면 성공한 UsernameAuthenticationToken을 생성하여 AuthenticaionManager에게 전달한다.
9 . 그리고 AuthenticaionManager는 AuthenticationFilter에게 토큰을 전달한다.
10 . 토큰은 LoginSuccessHandler로 전달되고 SecurityContextHolder에 저장된다.
- SecurityContextHolder은 SecurityContext를 thread와 연결해주고 있다.
- SecurityContext은 Authentication에 대한 get/set() 메소드가 정의되어 있다. (Authentication : user의 인증 정보)
- Spring Security 처리 과정 및 구현 예제_ MangKyu’s Diary 을 참고하면 구현 예제에 대한 설명을 자세히 볼 수 있다. (좋은 자료 감사합니다.)
- spring security guide 를 참고하여 간단하게 실습해봐도 좋을 것 같다.
- Bean 등록을 통해 커스텀할 수 있다.
- 일반 객체를 생성자 주입하여 사용하거나 인터페이스 자체를 구현해준 뒤 사용하면 된다.
WebSecurityConfig.java
@EnableWebSecurity //Spring Security 커스텀에 필수적인 Annotation
public class WebMvcConfiguration {
@Bean
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/auth/**", "/js/**", "/image/**", "/webjars/**")
.permitAll()
// 위에서 언급한 경로 외에는 모두 인증을 거치도록 설정
.and()
.authorizeRequests().anyRequest().authenticated()
// 시큐리티가 제공하는 기본 로그인 화면은 CSRF 토큰을 무조건 전달
// 하지만 사용자 정의 로그인 화면에서는 CSRF 토큰을 전달하지 않게 설정
.and()
.csrf().disable()
// 사용자가 만든 로그인 화면 이용
.formLogin().loginPage("/auth/login")
//로그인 실패 핸들러
.failureHandler(authenticationFailureHandler)
// 로그아웃 설정
.and()
.logout().logoutUrl("/auth/logout").logoutSuccessUrl("/");
return http.build();
}
}
LoginSuccessHandler
, 로그인 실패 핸들러인 AuthenticationFailureHandler
, 비밀번호 인코드 BCryptPasswordEncoder
등의 여러 기능을 Bean 추가 혹은 클래스를 주입 받아 사용할 수 있다.UserDetailsServiceImpl.java
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
//UserDetails 객체 리턴 (인가가 추가된 객체)
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User principal = userRepository.findByUsername(username).get();
return new UserDetailsImpl(principal);
}
}
UserDetailsImpl.java
@Getter
@Setter
public class UserDetailsImpl implements UserDetails {
private static final long serialVersionUID = 1L;
// User 엔티티 타입의 참조변수 선언
private User user;
//UserDetails 객체 생성
public UserDetailsImpl(User user) {
this.user = user;
}
// User 엔티티가 가지고 있는 권한 목록을 저장하여 리턴
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 권한 목록을 저장할 컬렉션
Collection<GrantedAuthority> roleList = new ArrayList<>();
// 권한 설정
roleList.add(new GrantedAuthority() {
private static final long serialVersionUID = 1L;
@Override
public String getAuthority() {
return "ROLE_" + user.getRole();
}
});
return roleList;
}
@Override
public String getPassword() {
// {noop}은 비밀번호를 암호화하지 않도록 하는 접두사
return "{noop}" + user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// 계정이 만료됬는지 여부를 리턴
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정이 잠겨있는지 여부를 리턴
@Override
public boolean isAccountNonLocked() {
return true;
}
// 비밀번호가 만료됬는지 여부를 리턴
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정의 활성화 여부를 리턴
@Override
public boolean isEnabled() {
return true;
}
}
Reference: