본문 바로가기
학습/Spring

[spring] 스프링 부트에서 Spring Security 커스터마이징하기

by KKambi 2020. 5. 21.

Web Security 커스터마이징

- WebSecurityConfigurerAdapter를 상속하던 SpringBootWebSecurityConfiguration 대체
- 모든 HTTP요청에 인증을 요구하던 스프링 부트의 자동설정 해제

  1. WebSecurityConfigurerAdapter를 implements하는 @Configuration class 생성
    • 스프링 부트의 SpringBootWebSecurityConfiguration이 빈으로 등록되지 않게 된다.
  2. HttpSecurity 객체를 파라미터로 받는 configure 메소드 오버라이드
    • authroizeRequest() : http요청을 위한 웹 기반 보안을 설정하겠다는 의미
    • antMatchers() : ant pattern으로 URL지정
    • permitAll() : 해당 URL에 모든 사용자 접근을 허용
    • authenticated() : 해당 URL에 인증된 사용자 접근만을 허용
    • and() / formLogin() / httpBasic()으로 어떤 인증을 사용할지 설정
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/hello").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
                .httpBasic();
    }
}

 

 

UserDetailsService 커스터마이징

- 유저, 패스워드를 자동으로 만들어주던 스프링 부트의 자동설정 해제

- 입력한 유저이름과 일치하는 유저 정보를 DB에서 찾아 반환하는 역할

  1. h2 및 spring-boot-starter-data-jpa 의존성 추가
  2. 유저 객체 생성을 위해, Account class 및 AccountRepository interface 추가
  3. 유저 객체 생성 작업을 하는 Account service 추가
  4. 해당 서비스 클래스에 UserDetailsService Interface를 implements
    • 이를 구현하는 빈이 있어야 부트의 자동설정이 해제된다.
    • pubic UserDetails loadUserByUserName(String username) 메소드를 오버라이드해야 한다.
    • 로그인을 시도하면, 해당 메소드가 호출 → 입력한 유저이름에 해당하는 정보를 담아 객체로 반환
@Entity
public class Account {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    
    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

 

loadUserByUserName 오버라이딩

  • Optional 객체를 사용해보자.
  • DB에서 입력받은 username과 일치하는 유저 정보를 찾았다면, 이 정보로 User객체를 생성하여 반환
  • 해당 메소드는 UserDetails라는 인터페이스의 구현체를 반환해야하는데 시큐리티가 제공.
    → User(유저정보의 name, 유저정보의 password, 보유권한을 담은 ArrayList)
  • 보유 권한 ArrayList는 authorities 메소드로 만든다.
public interface AccountRepository extends JpaRepository<Account, Long> {
    Optional<Account> findByUsername(String username);
}
@Service
public class AccountService implements UserDetailsService{

    private AccountRepository accountRepository;

    public AccountService(AccountRepository accountRepository){
        this.accountRepository = accountRepository;
    }

    public Account createAccount(String username, String password){
        Account account = new Account(username, password);
        return this.accountRepository.save(account);
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<Account> byUsername = accountRepository.findByUsername(username);
        Account account = byUsername.orElseThrow(() -> new UsernameNotFoundException(username));

        return new User(account.getUsername(), account.getPassword(), authorites());
    }

    private Collection<? extends GrantedAuthority> authorites(){
        return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
    }
}

 

 

UserDetailsService 커스터마이징

- 패스워드는 DB에 암호화되어 저장되어야 한다.

- 암호화를 위한 패스워드 인코더 필요

  1. WebConfig에 패스워드 인코더를 빈으로 등록
    → PasswordEncoderFactories class의 createDelegatingPasswordEncoder() 메소드 사용
  2. UserDetailsService를 impl한 서비스 클래스에서 해당 인코더를 주입받는다
  3. 해당 인코더로 인코딩해서 엔티티 객체에 저장해야 한다.
//WebConfig에서 등록한 인코더를 주입받아야 한다.
//loadUserByUsername 메소드에서 User객체를 반환할 때 인코딩
return new User(account.getUsername(), passwordEncoder.encode(account.getPassword()), authorites());

 

댓글