[Spring Security] UserDetailsService와 UserDetails 객체
스프링 시큐리티 테이블 구성
테이블은 role과 user로 구성되어 있다. user 테이블에서 user_name은 아이디를 의미한다. 이름은 user_nickname 컬럼에 들어간다.
role 테이블은 사용자가 어떤 역할을 수행하는지를 정의하는 컬럼이 들어있다. 어떤 사용자는 일반 사용자의 역할을 수행하고, 111122 사용자는 일반 사용자와 관리자 역할을 둘 다 수행하는 것을 확인할 수 있다. 한 사용자는 여러 개의 권한을 가질 수 있다.
스프링 시큐리티 설정을 위해서 구현해야 하는 인터페이스가 두 가지 있다. DB에서 유저 정보를 조회하는 기능을 하는 UserDetailsService를 구현한 객체와 사용자 정보를 제공하는 UserDetails를 구현한 객체이다.
CustomUserDetailsService
이 인터페이스에는 loadUserByUsername(String username) 메소드가 정의되어져 있다. 여기서 username이란 사용자를 고유하게 식별할 수 있는 값을 의미하는데, 아이디, 사번, 학번, 이메일, 고객번호 등이 될 수 있다.
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userMapper.getUserByEmail(username);
}
}
UserDetails
UserDetails 인터페이스에 정의되어 있는 메소드는 다음과 같다.
- Collection<? extends GrantedAuthority> getAuthorities();
- 계정의 권한 목록 반환
- String getPassword();
- String getUsername();
- 사용자의 고유한 값 반환 (DB primary key값 혹은 이메일)
- boolean isAccountNonExpired();
- boolean isAccountNonLocked();
- boolean isCredentialsNonExpired();
- boolean isEnabled();
public class User implements UserDetails {
private static final long serialVersionUID = -4446871534767640001L;
private int no;
private String email;
private String password;
private String name;
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setPassword(String password) {
this.password = password;
}
// 인증이 완료된 사용하는 항상 ROLE_USER 권한을 반환한다.
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return authorities;
}
@Override
public String getPassword() {
return password;
}
// 사용자를 고유하게 식별할 수 있는 값
@Override
public String getUsername() {
return email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
WebSecurityConfigurerAdapter
다른 일반적인 설정은 SpringBoot와 같은데, 한가지 추가 설정이 필요하다. config 패키지 안의 WebSecurityConfig 클래스를 살펴보자. 해당 객체에는 크게 UserDetailsService와 PasswordEncoder를 주입해 주어야 한다.
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CustomUserDetailsService customUserDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests()
// 권한별 접근제어
.antMatchers("/", "/register", "/login", "/error/*").permitAll()
.antMatchers("/admin/*").hasRole("ADMIN")
.anyRequest().hasRole("USER")
// 접근거부 예외 처리(http 응답코드 : 403)
.and()
.exceptionHandling().accessDeniedPage("/error/forbidden")
// 로그인 설정
.and()
.formLogin() // 폼 로그인사용
.loginPage("/login") // 사용자 로그인 페이지 지정, 로그인이 필요한 경우 /login을 시큐리티가 요청한다.
.loginProcessingUrl("/login") // 로그인폼에서 로그인을 요청할 때 사용하는 URL을 지정
.defaultSuccessUrl("/login", true) // 로그인 성공일 때 요청하는 URL 지정
.failureUrl("/login?error=true")// 로그인 실패했을 때 요청하는 URL 지정
// 로그아웃 설정
.and()
.logout()
.logoutUrl("/logout") // 로그아웃을 요청하는 URL 지정
.logoutSuccessUrl("/") // 로그아웃 성공일 때 요청하는 URL 지정
.deleteCookies("JSESSIONID"); // 로그아웃이 완료되면 쿠키값을 삭제
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// UserDetails - 인증된 사용자 정보를 제공하는 기능이 정의된 인터페이스다.
// UserDetailsService - 사용자 인증처리를 수행하는 기능이 정의된 인터페이스다.
// 스프링 시큐리티의 인증관리자객체에 사용자정의 UserDetailsService(사용자정보를 제공)와
// 비밀번호를 암호화하는 패스워드인코더를 전달한다.
auth
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
// 스프링 시큐리티 인증과정을 사용하지 않는 요청 URL을 지정한다.
// 주로 정적 자원(이미지, CSS, 자바스크립트)을 요청하는 URL을 지정한다.
web.ignoring().antMatchers("/resources/**");
}
}