LoginSignup
3
13

More than 5 years have passed since last update.

Spring SecurityでSSO(not SAML)

Posted at

Spring Securityを利用して、リバースプロキシ型やエージェント型などのSSO認証を行います。

前提

Idpで認証確認した後、リクエストヘッダに認証情報を付加してリクエストしてくるという想定です。

環境

  • Java8
  • Spring Boot 2.0.4.RELEASE
pom.xmlのdependencyだけ抜き出し。
<dependencies>
    <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>
</dependencies>

やり方

Spring SeccurityにはRequestHeaderAuthenticationFilterというクラスがあり、ヘッダから情報を取得して認証を行います。
認証処理自体はAuthenticationManagerに任せます。
AuthenticationProviderとしては、PreAuthenticatedAuthenticationProviderを利用します。
その際、UserDetaisを生成するクラスは実装する必要があります。

@SpringBootApplication
@RestController
public class SampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }

    @GetMapping("/sample")
    public String sample() {
        return "Hello World!!";
    }
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception {
        RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
        filter.setCredentialsRequestHeader("credentials");
        filter.setPrincipalRequestHeader("principal");
        filter.setAuthenticationManager(authenticationManager());

        return filter;
    }

    @Bean
    public PreAuthenticatedAuthenticationProvider preAuthenticationProvider() {
        PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
        provider.setPreAuthenticatedUserDetailsService(auds);
        return provider;
    }

    @Autowired
    private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> auds;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(preAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilter(requestHeaderAuthenticationFilter()).authorizeRequests().anyRequest().authenticated();
    }
}
@Component
public class SampleUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {

    @Override
    public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) {
        return new User(token.getPrincipal().toString(), "N/A", AuthorityUtils.createAuthorityList("ADMIN"));

    }
}

確認

Idpを構築するのは面倒なので、ヘッダを付与した状態でリクエストを送信してみます。

まずはヘッダなし。
エラーになっていることがわかります。

C:\Users\d-yosh> curl -I http://localhost:8080/sample
HTTP/1.1 500
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 820
Date: Fri, 31 Aug 2018 01:23:02 GMT
Connection: close

続いてヘッダあり。
正常にリクエストが完了しています。

C:\Users\d-yosh> curl -I -H "principal:fugafuga" -H "credentials:key" localhost:8080/sample
HTTP/1.1 200
Set-Cookie: JSESSIONID=AD66429DA4978F23BE0A0B0F2C214EA3; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/plain;charset=UTF-8
Content-Length: 13
Date: Fri, 31 Aug 2018 01:37:25 GMT

解説

RequestHeaderAuthenticationFilter

リクエストヘッダから情報を取得して、AuthenticationManagerに認証処理を依頼します。
ヘッダからは、principalとcredentialsを取得します。

認証処理を行う前に、SecurityContextに認証情報が格納されている場合は、認証済みとして認証処理を行いません。
なお、checkForPrincipalChangestrueにすると、リクエストヘッダに設定されたprincipalsの値と、SecurityContextに格納されている値が異なる場合には再度認証処理が行われます。
(デフォルトではfalseです。)
このとき、ヘッダの値とUserDetailsのgetUserName()の値が等しいことを確認します。

PreAuthenticatedAuthenticationProvider

実際に認証処理を行います。
実はそんなに大したことは行っていなくて、principalとcredentialsを確認して、nullであれば例外をスロー、nullでない場合は、AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>を実装したクラスを利用してUserDetailsを取得します。

このように最低限の処理しか行っていないので、たとえばcredentialsの中身を確認するような処理が必要なのであれば、このクラスを拡張して実装する必要があります。

AuthenticationUserDetailsService

PreAuthenticatedAuthenticationTokenを利用してUserDetailsを取得する処理を実装します。
今回のサンプルでは適当なデータを詰めて返却していますが、DBなどからユーザ情報を取り出すななどする必要があると思います。


使ってみた感想として、ある程度クラスが用意されているので、結構手軽に実装できるなーという印象でした。
しかし、細かいことをやろうとすると拡張は必須なのかなと感じます。

3
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
13