Spring/Spring Security

Spring Security (4) - 기본설정과 JPA 연동

시큐리티 설정

  • Spring Security는 의존성만 추가해도 기본 인증 폼을 보내줍니다.(Spring boot 사용시 자동으로 auto config 되는 부분이 있음) ==> 자동 설정으로 id 가 user 이고 password 가 콘솔에 출력되는 유저를 생성합니다.

자동 생성 유저의 password

  • Spring security 기본 설정 추가

    <!-- pom.xml -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
// java 설정파일
// 시큐리티 설정 파일임을 나타냄
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 특정 요청에 대해 설정
        // 인가(요청에 대한 권한 설정)
        http.authorizeRequests()
                .mvcMatchers("/", "/info").permitAll()  // 인증없이 접근 가능
                .mvcMatchers("/admin").hasRole("ADMIN")  // role 에 따라 구분
                .anyRequest().authenticated();   // 기타 요청은 인증을 하기만 하면 됨

        // 인증(login 방식 설정)
        http.formLogin();  // form login 을 사용
        http.httpBasic();  // http basic authentication 사용
    }
}
  • UserDetailsServiceAutoConfiguration 에서 InMemory 방식으로 유저를 자동 생성

이름 user 에 password 를 random 하게 생성한다

  • 따라서 설정 파일을 세팅하여 특정유저를 만들 수 있습니다.(사용가능성은 거의 없을 거 같지만..)

    // application.properties
    spring.security.user.name=admin
    spring.security.user.password=123
    spring.security.user.roles=ADMIN

인메모리 유저 설정

  • 실제적인 프로젝트에서 사용하지는 않을 거 같지만, 메모리내에 유저를 저장하는 방식이 있습니다. 기본 설정대로 두면 인메모리 방식으로 유저를 자동 생성하지만 따로 저장할 수 없스니다.

    // SecurityConfig file
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.inMemoryAuthentication()       // 인메모리 authentication 설정
          .withUser("sc").password("{noop}123").roles("USER").and()
          .withUser("admin").password("{noop}!@#").roles("ADMIN");
    }
  • InMemoryUserDetailsManager 를 bean 으로 등록하여, 외부에서 입력받은 유저를 메모리에 저장할 수도 있습니다.

    // SecurityConfig file
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
      return new InMemoryUserDetailsManager();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // UserDetailsService 를 InMemoryUserDetailsManager 로 설정
      auth.userDetailsService(inMemoryUserDetailsManager());
    }
    // Controller
    @RestController
    @RequiredArgsConstructor
    public class AccountController {
      private final InMemoryUserDetailsManager inMemoryUserDetailsManager;
    
      @GetMapping("/account/inmemory/{role}/{username}/{password}")
      public Account joinInMemory(@ModelAttribute Account account) {
        inMemoryUserDetailsManager.createUser(new UserAccount(account));
        return account;
      }
    }
    // UserAccount
    import org.springframework.security.core.userdetails.User;
    @Getter
    public class UserAccount extends User {
      private Account account;
    
      public UserAccount(Account account) {
          super(account.getUsername(), account.getPassword(),
              List.of(new SimpleGrantedAuthority("ROLE_" + account.getRole())));
          this.account = account;
      }
    }
  • Test 이외의 용도로는 딱히 사용할 일이 없습니다.

Memory 에 user 저장

JPA 연동

  • 의존성 추가

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-data</artifactId>
    </dependency>
  • UserDetailsService : 스프링 시큐리티에서 DAO 인터페이스를 통해 데이터베이스에 있는 유저 정보를 가지고 인증을 하는 서비스 인터페이스

    • loadUserByUsername 메서드 : user 정보를 가져와서 UserDetails type 의 구현체를 return 하도록 구현
    • UserDetailsService 타입의 빈이 등록만 되어 있으면, security config 에서 AuthenticationManagerBuilder 에 UserDetailsService 를 설정하지 않아도 됨
// Entity
@Entity
public class Account {

    @Id @GeneratedValue
    private Long id;

    @Column(unique = true)
    private String username;

    private String password;

    private String role;

    public void encodePassword(PasswordEncoder passwordEncoder) {
      this.password = passwordEncoder.encode(this.password);
    }
}

//UserDetailsService 구현체
@Service
@RequiredArgsConstructor
public class AccountService implements UserDetailsService {

  private final AccountRepository accountRepository;
  private final PasswordEncoder passwordEncoder;

  //loadUserByUsername 에서 UserDetails type 의 구현체를 반환
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    final Account account = accountRepository.findByUsername(username);
    return Optional.ofNullable(account)
        .map(UserAccount::new)
        .orElseThrow(() -> new UsernameNotFoundException(username));
  }

  // user 를 영속계층에 저장
  @Transactional
  public Account createNew(Account account) {
    account.encodePassword(passwordEncoder);
    return accountRepository.save(account);
  }
}

PasswordEncoder

  • 패스워드 인코딩 방식을 결정해주는 빈

  • BCryptPasswordEncoder 가 기본전략

    // 기본전략으로 설정
    @Bean
    public PasswordEncoder passwordEncoder() {
      return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }