2011-01-06 32 views
10

2つの別々のサービスセットを提供する2つのSpring Webアプリケーションがあります。 Web App 1には、ユーザーベースの認証を使用してSpring Securityが実装されています。SpringセキュリティRestTemplateを使用した認証

今、Web App 2はWeb App 1のサービスにアクセスする必要があります。通常、RestTemplateクラスを使用して他のWebサービスに要求します。

どのように我々は(現在は認証されたユーザーの資格情報がSecurityContextを通じてアクセス可能であるAuthenticationオブジェクト、上のWebアプリケーション1で利用可能であるべきである1

答えて

9

私は同じ状況にあった。ここに私の解決策があります。

サーバ - 春のセキュリティ設定

<sec:http> 
    <sec:intercept-url pattern="/**" access="ROLE_USER" method="POST"/> 
    <sec:intercept-url pattern="/**" filters="none" method="GET"/> 
    <sec:http-basic /> 
</sec:http> 

<sec:authentication-manager alias="authenticationManager"> 
    <sec:authentication-provider> 
     <sec:user-service> 
      <sec:user name="${rest.username}" password="${rest.password}" authorities="ROLE_USER"/> 
     </sec:user-service> 
    </sec:authentication-provider> 
</sec:authentication-manager> 

クライアント側RestTemplate設定

<bean id="httpClient" class="org.apache.commons.httpclient.HttpClient"> 
    <constructor-arg ref="httpClientParams"/> 
    <property name="state" ref="httpState"/> 
</bean> 

<bean id="httpState" class="CustomHttpState"> 
    <property name="credentials" ref="credentials"/> 
</bean> 

<bean id="credentials" class="org.apache.commons.httpclient.UsernamePasswordCredentials"> 
    <constructor-arg value="${rest.username}"/> 
    <constructor-arg value="${rest.password}"/> 
</bean> 

<bean id="httpClientFactory" class="org.springframework.http.client.CommonsClientHttpRequestFactory"> 
    <constructor-arg ref="httpClient"/> 
</bean> 


<bean class="org.springframework.web.client.RestTemplate"> 
    <constructor-arg ref="httpClientFactory"/>     
</bean> 

カスタムHttpState実装

/** 
* Custom implementation of {@link HttpState} with credentials property. 
* 
* @author banterCZ 
*/ 
public class CustomHttpState extends HttpState { 

    /** 
    * Set credentials property. 
    * 
    * @param credentials 
    * @see #setCredentials(org.apache.commons.httpclient.auth.AuthScope, org.apache.commons.httpclient.Credentials) 
    */ 
    public void setCredentials(final Credentials credentials) { 
     super.setCredentials(AuthScope.ANY, credentials); 
    } 

} 

Mavenの依存

<dependency> 
    <groupId>commons-httpclient</groupId> 
    <artifactId>commons-httpclient</artifactId> 
    <version>3.1</version> 
</dependency> 
+0

カスタムhttp状態クラスが必要な理由を説明できますか? – kamaci

+0

@kamaci HttpState#setCredentialsは2つのパラメータを必要とするためsetter(アクセッサとも呼ばれます)ではありません。したがって、資格情報はPOJOフィールドではなく、Spring XML設定ではアクセスできません。 – banterCZ

+0

私は自分のアプリケーションを実行すると、それを記録します: '[org.springframework.beans.GenericTypeAwarePropertyDescriptor] - [無効なJavaBeanプロパティの 'credentials'にアクセスしています!実際に使用されている[public void a.b.c.d.CustomHttpState.setCredentials(org.apache.commons.httpclient.Credentials)]の横に見つかったあいまいな書き込みメソッド:...(エラーが続く) '。普段ですか? – kamaci

1

WebアプリケーションをWebアプリケーション2の要求で認証資格を渡すんたとえば、 SecurityContextHolder.getContext().getAuthentication()を呼び出すことで取得できます)。

あなたが資格情報を取得した後は、Webアプリケーションにアクセスするためにそれらを使用することができます。2.

あなたはどちらかが( hereを説明するように)デコレータでそれを拡張するか、 RestTemplate.exchange()メソッドを使用してRestTemplateで「Authentiation」ヘッダを渡すことができます

in this forum post)。

+0

私は、Web App 2がWeb App 1のHTTPセッションを見ることができないと想定します。これはおそらく動作しません。 – AngerClown

+0

申し訳ありませんが、希望の方向を誤解しています:* Web App2 *から* Web App 1. *私の答えを変更する。 –

3

RestTemplateは非常に基本的なものであり、制限されています。これを行うのは簡単な方法ではないようです。最善の方法は、おそらくWeb App 1の基本認証のダイジェストを実装することです。次に、Apache HttpClientを直接使用して、Web App 2から残りのサービスにアクセスします。

これはテストのために、大きなハック。基本的にRestTemplateを使用してログイン(j_spring_security_check)を送信し、リクエストヘッダーからjsessionidを解析し、残りのリクエストを送信します。ここにコードがありますが、これは生産準備が整ったコードに最適なソリューションだとは思っていません。このため

public final class RESTTest { 
    public static void main(String[] args) { 
    RestTemplate rest = new RestTemplate(); 

    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { 
     @Override 
     public boolean verify(String s, SSLSession sslsession) { 
      return true; 
     } 
    }); 

    // setting up a trust store with JCA is a whole other issue 
    // this assumes you can only log in via SSL 
    // you could turn that off, but not on a production site! 
    System.setProperty("javax.net.ssl.trustStore", "/path/to/cacerts"); 
    System.setProperty("javax.net.ssl.trustStorePassword", "somepassword"); 

    String jsessionid = rest.execute("https://localhost:8443/j_spring_security_check", HttpMethod.POST, 
      new RequestCallback() { 
       @Override 
       public void doWithRequest(ClientHttpRequest request) throws IOException { 
       request.getBody().write("j_username=user&j_password=user".getBytes()); 
       } 
      }, new ResponseExtractor<String>() { 
       @Override 
       public String extractData(ClientHttpResponse response) throws IOException { 
        List<String> cookies = response.getHeaders().get("Cookie"); 

        // assuming only one cookie with jsessionid as the only value 
        if (cookies == null) { 
         cookies = response.getHeaders().get("Set-Cookie"); 
        } 

        String cookie = cookies.get(cookies.size() - 1); 

        int start = cookie.indexOf('='); 
        int end = cookie.indexOf(';'); 

        return cookie.substring(start + 1, end); 
       } 
      }); 

    rest.put("http://localhost:8080/rest/program.json;jsessionid=" + jsessionid, new DAO("REST Test").asJSON()); 
} 

}

注作業には、SSL接続を実際に行うことができるように、JCAでトラスト・ストアを作成する必要があります。私はあなたがSpring Securityのログインを生産現場のプレーンなHTTP上に置くことを望んでいないと思うのですが、それは大規模なセキュリティホールになるからです。

+0

あなたは1年以上前にこれに答えましたが、まだ利用可能な場合は、「コードを投稿する」という申し出をお待ちしています。 :) thanks – Justin

+0

コードは掲載されましたが、最近のバージョンのSpringで再度試してみませんでした。 RestTemplateは更新されていませんか? – AngerClown

12

ここで私はこのサイトに基づいて様々な答えを作成し、スプリングRestTempalteのソースコードを読んで春3.1とApache HttpComponents 4.1と非常によく機能するソリューションです。私は他の時間を節約することを望んで共有しています、私は春は、このようないくつかのコードを持っている必要がありますが、それはないと思う。以下は

RestClient client = new RestClient(); 
client.setApplicationPath("someApp"); 
String url = client.login("theuser", "123456"); 
UserPortfolio portfolio = client.template().getForObject(client.apiUrl("portfolio"), 
         UserPortfolio.class); 

RestTemplate持つすべてのリクエストで同じになるようにHttpComponentsコンテキストをセットアップするファクトリクラスです。

public class StatefullHttpComponentsClientHttpRequestFactory extends 
        HttpComponentsClientHttpRequestFactory 
{ 
    private final HttpContext httpContext; 

    public StatefullHttpComponentsClientHttpRequestFactory(HttpClient httpClient, HttpContext httpContext) 
    { 
     super(httpClient); 
     this.httpContext = httpContext; 
    } 

    @Override 
    protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) 
    { 
     return this.httpContext; 
    } 
} 

以下は、あなたがJSESSIONIDを覚えているだろう、それを使ってログインし、その後の要求にそれを送った後に、クッキーを覚えて使用することができますステートフルレストテンプレートです。ここで

public class StatefullRestTemplate extends RestTemplate 
{ 
    private final HttpClient httpClient; 
    private final CookieStore cookieStore; 
    private final HttpContext httpContext; 
    private final StatefullHttpComponentsClientHttpRequestFactory statefullHttpComponentsClientHttpRequestFactory; 

    public StatefullRestTemplate() 
    { 
     super(); 
     httpClient = new DefaultHttpClient(); 
     cookieStore = new BasicCookieStore(); 
     httpContext = new BasicHttpContext(); 
     httpContext.setAttribute(ClientContext.COOKIE_STORE, getCookieStore()); 
     statefullHttpComponentsClientHttpRequestFactory = new StatefullHttpComponentsClientHttpRequestFactory(httpClient, httpContext); 
     super.setRequestFactory(statefullHttpComponentsClientHttpRequestFactory); 
    } 

    public HttpClient getHttpClient() 
    { 
     return httpClient; 
    } 

    public CookieStore getCookieStore() 
    { 
     return cookieStore; 
    } 

    public HttpContext getHttpContext() 
    { 
     return httpContext; 
    } 

    public StatefullHttpComponentsClientHttpRequestFactory getStatefulHttpClientRequestFactory() 
    { 
     return statefullHttpComponentsClientHttpRequestFactory; 
    } 
} 

あなたは春 セキュリティで保護アプリに呼び出すことができるように、残りのクライアントを表現するクラスです。

public class RestClient 
{ 
    private String host = "localhost"; 
    private String port = "8080"; 
    private String applicationPath; 
    private String apiPath = "api"; 
    private String loginPath = "j_spring_security_check"; 
    private String logoutPath = "logout"; 
    private final String usernameInputFieldName = "j_username"; 
    private final String passwordInputFieldName = "j_password"; 
    private final StatefullRestTemplate template = new StatefullRestTemplate(); 

    /** 
    * This method logs into a service by doing an standard http using the configuration in this class. 
    * 
    * @param username 
    *   the username to log into the application with 
    * @param password 
    *   the password to log into the application with 
    * 
    * @return the url that the login redirects to 
    */ 
    public String login(String username, String password) 
    { 
     MultiValueMap<String, String> form = new LinkedMultiValueMap<>(); 
     form.add(usernameInputFieldName, username); 
     form.add(passwordInputFieldName, password); 
     URI location = this.template.postForLocation(loginUrl(), form); 
     return location.toString(); 
    } 

    /** 
    * Logout by doing an http get on the logout url 
    * 
    * @return result of the get as ResponseEntity 
    */ 
    public ResponseEntity<String> logout() 
    { 
     return this.template.getForEntity(logoutUrl(), String.class); 
    } 

    public String applicationUrl(String relativePath) 
    { 
     return applicationUrl() + "/" + checkNotNull(relativePath); 
    } 

    public String apiUrl(String relativePath) 
    { 
     return applicationUrl(apiPath + "/" + checkNotNull(relativePath)); 
    } 

    public StatefullRestTemplate template() 
    { 
     return template; 
    } 

    public String serverUrl() 
    { 
     return "http://" + host + ":" + port; 
    } 

    public String applicationUrl() 
    { 
     return serverUrl() + "/" + nullToEmpty(applicationPath); 
    } 

    public String loginUrl() 
    { 
     return applicationUrl(loginPath); 
    } 

    public String logoutUrl() 
    { 
     return applicationUrl(logoutPath); 
    } 

    public String apiUrl() 
    { 
     return applicationUrl(apiPath); 
    } 

    public void setLogoutPath(String logoutPath) 
    { 
     this.logoutPath = logoutPath; 
    } 

    public String getHost() 
    { 
     return host; 
    } 

    public void setHost(String host) 
    { 
     this.host = host; 
    } 

    public String getPort() 
    { 
     return port; 
    } 

    public void setPort(String port) 
    { 
     this.port = port; 
    } 

    public String getApplicationPath() 
    { 
     return applicationPath; 
    } 

    public void setApplicationPath(String contextPath) 
    { 
     this.applicationPath = contextPath; 
    } 

    public String getApiPath() 
    { 
     return apiPath; 
    } 

    public void setApiPath(String apiPath) 
    { 
     this.apiPath = apiPath; 
    } 

    public String getLoginPath() 
    { 
     return loginPath; 
    } 

    public void setLoginPath(String loginPath) 
    { 
     this.loginPath = loginPath; 
    } 

    public String getLogoutPath() 
    { 
     return logoutPath; 
    } 

    @Override 
    public String toString() 
    { 
     StringBuilder builder = new StringBuilder(); 
     builder.append("RestClient [\n serverUrl()="); 
     builder.append(serverUrl()); 
     builder.append(", \n applicationUrl()="); 
     builder.append(applicationUrl()); 
     builder.append(", \n loginUrl()="); 
     builder.append(loginUrl()); 
     builder.append(", \n logoutUrl()="); 
     builder.append(logoutUrl()); 
     builder.append(", \n apiUrl()="); 
     builder.append(apiUrl()); 
     builder.append("\n]"); 
     return builder.toString(); 
    } 
} 
2

APIコンシューマではなく単純な呼び出しを探している人のために、これを行う簡単な方法があります。

HttpClient client = new HttpClient(); 
    client.getParams().setAuthenticationPreemptive(true); 
    Credentials defaultcreds = new UsernamePasswordCredentials("username", "password"); 
    RestTemplate restTemplate = new RestTemplate(); 
    restTemplate.setRequestFactory(new CommonsClientHttpRequestFactory(client)); 
    client.getState().setCredentials(AuthScope.ANY, defaultcreds); 
+0

これは、RestTemplate RequestFactoryを構成しているように見えます。複数のURLにアクセスする際に、それぞれが異なるダイジェスト認証を必要とする場合はどうなりますか?ホストごとに認証を追加するか、新しいrestTemplateを使用する必要がありますか?あなたは例を提供することができます –

+0

私はダイジェストタイプごとにrestTemplateを作成しなければならないと思います。あるいは、CommonsClientHttpRequestFactoryを拡張することができます。要求に応じて、使用するクライアントを決定してください。 – Panthro

+0

ありがとう、このコードは私の問題を解決しました –

2

以下は、認証とセッションクッキーを返します。これは、AMSのアプローチに非常によく似て

String sessionCookie= restTemplate.execute(uri, HttpMethod.POST, request -> { 
     request.getBody().write(("j_username=USER_NAME&j_password=PASSWORD").getBytes()); 
    }, response -> { 
     AbstractClientHttpResponse r = (AbstractClientHttpResponse) response; 
     HttpHeaders headers = r.getHeaders(); 
     return headers.get("Set-Cookie").get(0); 
    }); 
0

、私は完全にStatefulClientHttpRequestFactoryでセッションクッキーを維持することの懸念をカプセル化してきた点が異なります。また、既存のClientHttpRequestFactoryをこの振る舞いでデコレートすることによって、それは任意の基礎となるClientHttpRequestFactoryと共に使用することができ、特定の実装に束縛されない。

import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 
import org.springframework.http.HttpHeaders; 
import org.springframework.http.HttpMethod; 
import org.springframework.http.client.ClientHttpRequest; 
import org.springframework.http.client.ClientHttpRequestFactory; 
import org.springframework.http.client.ClientHttpResponse; 

import java.io.IOException; 
import java.io.OutputStream; 
import java.net.URI; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
import java.util.function.Function; 
import java.util.stream.Collectors; 

import static java.lang.String.format; 

/** 
* Decorates a ClientHttpRequestFactory to maintain sessions (cookies) 
* to web servers. 
*/ 
public class StatefulClientHttpRequestFactory implements ClientHttpRequestFactory { 
    protected final Log logger = LogFactory.getLog(this.getClass()); 

    private final ClientHttpRequestFactory requestFactory; 
    private final Map<String, String> hostToCookie = new HashMap<>(); 

    public StatefulClientHttpRequestFactory(ClientHttpRequestFactory requestFactory){ 
     this.requestFactory = requestFactory; 
    } 

    @Override 
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { 

     ClientHttpRequest request = requestFactory.createRequest(uri, httpMethod); 

     final String host = request.getURI().getHost(); 
     String cookie = getCookie(host); 
     if(cookie != null){ 
      logger.debug(format("Setting request Cookie header to [%s]", cookie)); 
      request.getHeaders().set("Cookie", cookie); 
     } 

     //decorate the request with a callback to process 'Set-Cookie' when executed 
     return new CallbackClientHttpRequest(request, response -> { 
      List<String> responseCookie = response.getHeaders().get("Set-Cookie"); 
      if(responseCookie != null){ 
       setCookie(host, responseCookie.stream().collect(Collectors.joining("; "))); 
      } 
      return response; 
     }); 
    } 

    private synchronized String getCookie(String host){ 
     String cookie = hostToCookie.get(host); 
     return cookie; 
    } 

    private synchronized void setCookie(String host, String cookie){ 
     hostToCookie.put(host, cookie); 
    } 

    private static class CallbackClientHttpRequest implements ClientHttpRequest{ 

     private final ClientHttpRequest request; 
     private final Function<ClientHttpResponse, ClientHttpResponse> filter; 

     public CallbackClientHttpRequest(ClientHttpRequest request, Function<ClientHttpResponse, ClientHttpResponse> filter){ 
      this.request = request; 
      this.filter = filter; 
     } 

     @Override 
     public ClientHttpResponse execute() throws IOException { 
      ClientHttpResponse response = request.execute(); 
      return filter.apply(response); 
     } 

     @Override 
     public OutputStream getBody() throws IOException { 
      return request.getBody(); 
     } 

     @Override 
     public HttpMethod getMethod() { 
      return request.getMethod(); 
     } 

     @Override 
     public URI getURI() { 
      return request.getURI(); 
     } 

     @Override 
     public HttpHeaders getHeaders() { 
      return request.getHeaders(); 
     } 
    } 
} 
関連する問題