1. 개요

이 빠른 기사에서는 Spring Security에서 사용자를 인증하기 위해 여러 메커니즘을 사용하는 데 중점을 둘 것입니다.

여러 인증 공급자를 구성하여 이를 수행합니다.

2. 인증 공급자

AuthenticationProvider 는 특정 저장소(예: 데이터베이스 , LDAP , 사용자 지정 타사 소스 등) 에서 사용자 정보를 가져오기 위한 추상화입니다 . 가져온 사용자 정보를 사용하여 제공된 자격 증명의 유효성을 검사합니다.

간단히 말해 여러 인증 공급자가 정의되면 공급자는 선언된 순서대로 쿼리됩니다.

빠른 데모를 위해 사용자 지정 인증 공급자와 메모리 내 인증 공급자라는 두 가지 인증 공급자를 구성합니다.

3. 메이븐 의존성

먼저 필요한 Spring Security 의존성을 웹 애플리케이션에 추가해 보겠습니다.

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

그리고 스프링 부트 없이:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>

이러한 의존성의 최신 버전은 spring-security-web , spring-security-corespring-security-config 에서 찾을 수 있습니다 .

4. 사용자 지정 인증 제공자

이제 AuthneticationProvider 인터페이스 를 구현하여 사용자 지정 인증 공급자를 만들어 보겠습니다 .

인증 을 시도하는 인증 방법 을 구현할 것 입니다. 입력 인증 개체에는 사용자가 제공한 사용자 이름 및 암호 자격 증명이 포함되어 있습니다.

인증 방법 은 인증에 성공하면 완전히 채워진 인증 개체를 반환합니다. 인증에 실패하면 AuthenticationException 유형의 예외가 발생합니다 .

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication auth) 
      throws AuthenticationException {
        String username = auth.getName();
        String password = auth.getCredentials()
            .toString();

        if ("externaluser".equals(username) && "pass".equals(password)) {
            return new UsernamePasswordAuthenticationToken
              (username, password, Collections.emptyList());
        } else {
            throw new 
              BadCredentialsException("External system authentication failed");
        }
    }

    @Override
    public boolean supports(Class<?> auth) {
        return auth.equals(UsernamePasswordAuthenticationToken.class);
    }
}

당연히 이것은 여기 예제의 목적을 위한 간단한 구현입니다.

5. 다중 인증 공급자 구성

이제 CustomAuthenticationProvider 와 메모리 내 인증 공급자를 Spring Security 구성에 추가해 보겠습니다.

5.1. 자바 구성

구성 클래스에서 이제 AuthenticationManagerBuilder 를 사용하여 인증 공급자를 만들고 추가해 보겠습니다 .

먼저 CustomAuthenticationProvider 를 사용한 다음 inMemoryAuthentication() 을 사용하여 메모리 내 인증 공급자를 만듭니다.

또한 URL 패턴 " /api/** "에 대한 액세스가 인증되어야 하는지 확인하고 있습니다.

@EnableWebSecurity
public class MultipleAuthProvidersSecurityConfig {

    @Autowired
    CustomAuthenticationProvider customAuthProvider;

    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder = 
          http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.authenticationProvider(customAuthProvider);
        authenticationManagerBuilder.inMemoryAuthentication()
            .withUser("memuser")
            .password(passwordEncoder().encode("pass"))
            .roles("USER");
        return authenticationManagerBuilder.build();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authManager) 
      throws Exception {
        http.httpBasic()
            .and()
            .authorizeRequests()
            .antMatchers("/api/**")
            .authenticated()
            .and()
            .authenticationManager(authManager);
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

5.2. XML 구성

또는 Java 구성 대신 XML 구성을 사용하려는 경우:

<security:authentication-manager>
    <security:authentication-provider>
        <security:user-service>
            <security:user name="memuser" password="pass" 
              authorities="ROLE_USER" />
        </security:user-service>
    </security:authentication-provider>
    <security:authentication-provider
      ref="customAuthenticationProvider" />
</security:authentication-manager>

<security:http>
    <security:http-basic />
    <security:intercept-url pattern="/api/**" 
      access="isAuthenticated()" />
</security:http>

6. 신청

다음으로 두 인증 공급자가 보호하는 간단한 REST 엔드포인트를 생성해 보겠습니다.

이 Endpoints에 액세스하려면 유효한 사용자 이름과 암호를 제공해야 합니다. 인증 공급자는 자격 증명을 확인하고 액세스 허용 여부를 결정합니다.

@RestController
public class MultipleAuthController {
    @GetMapping("/api/ping")
    public String getPing() {
        return "OK";
    }
}

7. 테스트

마지막으로 Security 애플리케이션에 대한 액세스를 테스트해 보겠습니다. 유효한 자격 증명이 제공된 경우에만 액세스가 허용됩니다.

@Autowired
private TestRestTemplate restTemplate;

@Test
public void givenMemUsers_whenGetPingWithValidUser_thenOk() {
    ResponseEntity<String> result 
      = makeRestCallToGetPing("memuser", "pass");

    assertThat(result.getStatusCodeValue()).isEqualTo(200);
    assertThat(result.getBody()).isEqualTo("OK");
}

@Test
public void givenExternalUsers_whenGetPingWithValidUser_thenOK() {
    ResponseEntity<String> result 
      = makeRestCallToGetPing("externaluser", "pass");

    assertThat(result.getStatusCodeValue()).isEqualTo(200);
    assertThat(result.getBody()).isEqualTo("OK");
}

@Test
public void givenAuthProviders_whenGetPingWithNoCred_then401() {
    ResponseEntity<String> result = makeRestCallToGetPing();

    assertThat(result.getStatusCodeValue()).isEqualTo(401);
}

@Test
public void givenAuthProviders_whenGetPingWithBadCred_then401() {
    ResponseEntity<String> result 
      = makeRestCallToGetPing("user", "bad_password");

    assertThat(result.getStatusCodeValue()).isEqualTo(401);
}

private ResponseEntity<String> 
  makeRestCallToGetPing(String username, String password) {
    return restTemplate.withBasicAuth(username, password)
      .getForEntity("/api/ping", String.class, Collections.emptyMap());
}

private ResponseEntity<String> makeRestCallToGetPing() {
    return restTemplate
      .getForEntity("/api/ping", String.class, Collections.emptyMap());
}

8. 결론

이 빠른 사용방법(예제)에서는 Spring Security에서 여러 인증 공급자를 구성하는 방법을 살펴보았습니다. Custom형 인증 공급자와 메모리 내 인증 공급자를 사용하여 간단한 애플리케이션을 보호했습니다.

또한 애플리케이션에 액세스하려면 인증 제공자 중 최소 한 명이 검증할 수 있는 자격 증명이 필요한지 확인하는 테스트를 작성했습니다.

항상 그렇듯이 구현의 전체 소스 코드는 GitHub 에서 찾을 수 있습니다 .

Security footer banner