데이터베이스 기반 인증 처리

  1. UsernamePasswordAuthenticationFilter에서 인증 처리를 담당한다.
  2. AuthenticationManager의 구현체인 ProviderManager는 사용자가 요청한 인증을 처리할 수 있는 Provider로 인증처리를 넘긴다.
    (UsernamePasswordAuthenticationToken 타입에 대한 인증 요청의 경우 DaoAuthenticationProvider가 처리)
  3. Provider는 내부의 UserDetailsService의 구현체인 JdbcDaoImpl를 사용해서 사용자 인증 정보를 데이터베이스에서 가져온다.
  4. 가져온 인증 정보를 바탕으로 인증 객체를 반환한다.(기본적으로 users와 authentications과 같은 )

WebSecurityConfigurerAdapter

스프링시큐리티와 관련된 어댑터 클래스를 오버라이드하여 사용하는 방법에서 컴포넌트 방식의 빈을 설정하는 방식으로 사용방식이 변경되었다.

이전 사용 방식

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                    .antMatchers("/me").hasAnyRole("USER","ADMIN")
                    .anyRequest().permitAll()
                    .and()
                .formLogin()
                    .defaultSuccessUrl("/")
                    .permitAll()
                    .and()
                .logout()
                    .logoutSuccessUrl("/")
                    .and()
                .rememberMe()
                    .rememberMeParameter("remember-me")
                    .tokenValiditySeconds(300);
    }
    
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/ignore1", "/ignore2");
    }
}

새로운 사용방식

@Configuration
public class SecurityConfiguration {    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                    .antMatchers("/me").hasAnyRole("USER","ADMIN")
                    .anyRequest().permitAll()
                    .and()
                .formLogin()
                    .defaultSuccessUrl("/")
                    .permitAll()
                    .and()
                .logout()
                    .logoutSuccessUrl("/")
                    .and()
                .rememberMe()
                    .rememberMeParameter("remember-me")
                    .tokenValiditySeconds(300);
        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/assets/**", "/h2-console/**");
    }
}

데이터베이스 기반 인증 고급 설정

UserDetailsService의 구현체인 JdbcDaoImpl 클래스를 설정을 통해 기존 데이터베이스를 활용한 인증 확인 기능을 더욱 정교하게 설정하는 것이 가능하다. 대표적인 사용 예시는 사용자가 속하는 특정 그룹이 가지고 있는 권한 집합을 참조하는 방식의 Group-based Access Control의 적용이다.

  1. 그룹 인증을 위한 데이터베이스 스키마를 작성한다.
  2. JdbcDaoImpl 클래스에서 인증 정보 확인을 위한 3개의 SQL 쿼리를 작성한 데이터베이스 스키마에 맞게 수정한다.
  • usersByUsernameQuery : 사용자명과 일치하는 하나 이상의 사용자를 조회하는 쿼리로 조회하는 값들은 반드시 username(String), password(String), enabled(Boolean) 순서이어야 한다.
    select username, password, enabled from users where username = ?
  • authoritiesByUsernameQuery : 사용자에게 직접 부여된 하나 이상의 권한을 반환하는 쿼리로 Group-based Access Control 미적용시 사용하게 되는 쿼리이다. 두 번째 값은 반드시 authority(String)이어야 한다.
    select username, authority from authorities where username = ?
  • groupAuthoritiesByUsernameQuery : 그룹 멤버십을 통해 사용자에게 승인된 권한을 반환하는 쿼리로 Group-based Access Control 적용시 사용하게 되는 쿼리로 세 번째 값은 반드시 authority(String)이어야 한다.
    조회하는 세 번째 값은 반드시 authority(String)이어야 한다.
    select g.id, g.group_name, ga.authority 
     from groups g, group_members gm, group_authorities ga 
     where gm.username = ? and g.id = ga.group_id and g.id = gm.group_id
  1. AuthenticationManagerBuilder를 통해 수정된 쿼리와 권한 부여 방식을 수정한다.

     @Bean
      public UserDetailsService userDetailsService(DataSource dataSource) {
          JdbcDaoImpl jdbcDao = new JdbcDaoImpl();
          jdbcDao.setDataSource(dataSource);
          jdbcDao.setEnableAuthorities(false);
          jdbcDao.setEnableGroups(true);
          jdbcDao.setUsersByUsernameQuery(
                  "select login_id, passwd, true " +
                          "from users " +
                          "where login_id = ?"
          );
          jdbcDao.setGroupAuthoritiesByUsernameQuery(
                  "select u.login_id, g.name, p.name \n" +
                  "from users u join groups g on u.group_id = g.id\n" +
                          "left join groups_permission gp on g.id = gp.group_id\n" +
                          "join permissions p on p.id = gp.permission_id\n" +
                  "where u.login_id = ?"
          );
          return jdbcDao;
      }

JPA 기반 데이터베이스 인증 설정

  1. 인증과 관련된 엔티티를 설계한다.

  2. 사용자 조회와 관련된 JpaRepository인터페이스 메소드를 추가한다.

      public interface UserRepository extends JpaRepository<User, Long> {
    
        Optional<User> findByLoginId(String loginId);
    
      }
  3. 2의 메소드를 활용하여 UserDetailsService 인터페이스의 loadUserByUsername 메소드를 오버라이딩 한다.(반환되는 UserDetails 객체는 Authentication 객체로 캡슐화 된다.)

    @Service
    public class UserService implements UserDetailsService {
    
      private final UserRepository userRepository;
    
      public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
      }
    
      @Override
      @Transactional(readOnly = true)
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByLoginId(username)
          .map(user ->
            User.builder()
              .username(user.getLoginId())
              .password(user.getPasswd())
              .authorities(user.getGroup().getAuthorities())
              .build()
          )
          .orElseThrow(() -> new UsernameNotFoundException("Could not found user for " + username));
      }
    
    }
  4. 3을 UserDetailsSerivice 인터페이스의 구현체로 사용할 수 있도록 AuthenticationManagerBuilder의 값을 수정한다.

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
    
      private final Logger log = LoggerFactory.getLogger(getClass());
    
      private UserService userService;
    
      @Autowired
      private void setUserService(UserService userService) {
        this.userService = userService;
      }
    
        @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
      }
    }
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfigure {
      @Bean
      @Primary
      public AuthenticationManagerBuilder myAuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor, UserService userService) throws Exception {
          AuthenticationManagerBuilder authenticationManagerBuilder = new AuthenticationManagerBuilder(objectPostProcessor);
          authenticationManagerBuilder.userDetailsService(userService);
          return authenticationManagerBuilder;
      }
    }   

Spring Security without the WebSecurityConfigurerAdapter

profile
ㅇㅅㅇ

0개의 댓글