자라선

33. Spring Security 본문

Develop/Spring Boot

33. Spring Security

자라선 2020. 7. 27. 17:33

스프링 부트 시큐리티 스프링 부트에 적용되는 인증 라이브러리

여러가지 종류가 있다.

·        웹 시큐리티

·        메소드 시큐리티

·        다양한 인증 방법 지원

·        LDAP, 폼 인증, Basic 인증, Oauth 등…

 

적용방식

스프링 부트 시큐리티는 아주 간편하게 사용 할 수 있도록 제공도 해주는데

의존성 라이브러리에 등록만 해줘도 자동설정으로 웹 전체에 적용이 된다.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

스프링 부트 시큐리티 의존성을 정의하면 자동설정이 적용되어 모든 페이지에 접근시 인증(로그인)이 필요하다.

인증하지 않고 요청시 로그인 /login 페이지로 리다이렉트 한다.

이떄 로그인 페이지의 로그인 정보는 기본은 user / <콘솔창에 출력된 비밀번호> 이다.

 

3xx 의 3으로 시작하는 모든 요청상태는 리다이렉션의 종류이다.

 

requestaccept 타입에 따라 응답되는 헤더가 다르게 나오는데

테스트로 요청시 401 에러가 나오며 accecpt 타입을 HTML 로 한다면 304에러가 나며 리다이렉션 시켜준다.


SecurityAutoConfiguration

스프링 시큐리티가 의존성 등록 되어있을때 등록이 실행되는 설정파일로 DefaultAuthenticationEventPublisher bean으로 생성되고 있다. 또한 이러한 설정 클래스는 스프링 시큐리티가 가지고 있기 때문에 굳이 부트를 사용해 의존성을 받지 않아도 DefaultAuthenticationEventPublisher bean으로 등록하면 사용 할 수 있다.

 

DefaultAuthenticationEventPublisher 이벤트 퍼블러셔는 로그인실패, 만료, 락 등등 유저의 이벤트를 발생시키고 우리는 그러한 이벤트로 핸들러를 작성하여 변경 하는등에 사용 할 수있다.

 

UserDetailServiceAutoConfigration

초기에 인-메모리 유저 매니저를 만들어서 유저를 생성한다.

스프링 부트가 지원하는 스프링 시큐리티 관련된 기능들은 별로 쓸일이 없다고함


Spring Boot Security Test

스프링 부트 시큐리티를 테스트 하려면 의존성을 별도로 정의 해주어야함

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>${spring-security.version}</version>
    <scope>test</scope>
</dependency>

테스트 메소드에 @WithMockUser를 정의하면 mock 유저를 넣어 테스트함

@Test
@WithMockUser
void hello() throws Exception {
    MvcResult mvcResult = mockMvc.perform(get("/hello")
                .accept(MediaType.TEXT_HTML))
            .andExpect(status().isOk())
            .andDo(print())
            .andExpect(view().name("hello"))
            .andReturn();
}

get() or post()withuser의 정보를 넣을 수 도 있다.

@Test
void hello() throws Exception {
    MvcResult mvcResult = mockMvc.perform(get("/hello").with(user("user").password("123"))
                .accept(MediaType.TEXT_HTML))
            .andExpect(status().isOk())
            .andDo(print())
            .andExpect(view().name("hello"))
            .andReturn();
}

스프링 부트 시큐리티 설정

스프링 부트 시큐리티의 설정을 커스텀마이징을 하기 위해서는 @Configuration 어노테이션을 정의한 클래스로 설정을 셋팅한다.

그 후 WebSecurityConfigrerAdapter 를 상속 받으면,  스프링 부트 시큐리티가 기본적으로 셋팅한 값을 덮어씌운다.

 

스프링 부트 시큐리티가 기본으로 설정하는 인증 설정

    // @formatter:off
    protected void configure(HttpSecurity http) throws Exception {
        logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin().and()
            .httpBasic();
    }
 

 

스프링 부트 시큐리티에서 권장하고 있는 암호화 인코딩을 bean으로 생성한다. (기본이 bcrypt)

https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#core-services-password-encoding

 

만약 별도의 암호화 인코딩을 정의하지 않는다면 스프링 부트 시큐리티는 로그인 할때 비밀번호의 값을

NoOpPasswordEncoder  인식하여 복호화를 진행한다그런데 이때 해당 암호화 인코더 bean 찾을  없어 에러가 난다.

최소한 NoOpPasswordEncoder  bean으로 올려줘야한다.

 

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
 
@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();
    }
 
    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

andMatchers(String ….) 로 매핑들을 작성하고 permitAll 로 접근 허용

anyRequest().authenticated() 로 그 이외로 요청할때는 인증을 필수로 받는다.

html을 accept 타입으로 온다면 formLogin() 에서 반응하여 /login 페이지로 리다이렉션 시켜주고

그 이외는 httpBasic() 으로 핸들링 시킨다.


UserDetailsService 설정

스프링 부트 시큐리티는 기본적으로 유저 계정을 하나 가지고 간다.

이러한 계정을 생성하기 위해서는 bean으로 올라가는 클래스에 UserDetailsService 인터페이스를 상 받아 초기 계정을 가져와야한다.

 

상속 받은 인터페이스로 오버라이딩을 하여 loadUserByUsername 메소드를 생성 후 정의

이 메소드는 /login 에서 입력한 ID를 매개변수로 받는다.

 

Optional<> 에서 *.orElseThrow( () -> new UsernameNotFoundException(**) ) 으로 해당 유저가 없다면 예외처리

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
 
@Service
public class AccountService implements UserDetailsService {
 
    @Autowired
    private AccountRepository repository;
 
    @Autowired
    PasswordEncoder passwordEncoder;
    public Account createAccount(String username, String password){
        Account account = new Account();
        account.setUsername(username);
        account.setPassword(passwordEncoder.encode(password));
        return repository.save(account);
    }
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<Account> byAccount = repository.findByUsername(username);
        Account account = byAccount.orElseThrow(() -> new UsernameNotFoundException(username));
        return new User(account.getUsername(), account.getPassword(), authorities());
    }
 
    private Collection<? extends GrantedAuthority> authorities() {
        return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
    }
}

Runner 를 사용하여 어플리케이션 실행시 유저 생성

import me.tony.demospringboot.account.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
 
@Component
public class AccountRunner implements ApplicationRunner {
 
    @Autowired
    AccountService service;
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        service.createAccount("tony", "123");
    }
}

'Develop > Spring Boot' 카테고리의 다른 글

35. Spring Actuator  (0) 2020.07.27
34. Rest Client  (0) 2020.07.27
32. Neo4j Connection  (0) 2020.07.27
31. MongoDB Connection  (0) 2020.07.27
30. Redis Connection  (0) 2020.07.27
Comments