Java 使用RestTemplate的Spring安全认证

Java 使用RestTemplate的Spring安全认证,java,spring,authentication,spring-security,Java,Spring,Authentication,Spring Security,我有两个SpringWeb应用程序,它们提供两套独立的服务。Web App 1使用基于用户的身份验证实现了Spring安全性 <bean id="httpClient" class="org.apache.commons.httpclient.HttpClient"> <constructor-arg ref="httpClientParams"/> <property name="state" ref="httpState"/> </b

我有两个SpringWeb应用程序,它们提供两套独立的服务。Web App 1使用基于用户的身份验证实现了Spring安全性

<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>
现在,Web App 2需要访问Web App 1的服务。通常,我们将使用RestTemplate类向其他web服务发出请求

<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>
我们如何将Web App 2请求中的身份验证凭据传递给Web App 1

当前已验证的用户凭据应在Web App 1中的
身份验证
对象上可用,该对象可通过
SecurityContext
(例如,您可以通过调用
SecurityContextHolder.getContext().getAuthentication()
)来检索它)

<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>
检索凭据后,您可以使用它们访问Web App 2

<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>

您可以使用RestTemplate传递“authentication”头,方法是使用decorator(如上所述)扩展它,或者使用
RestTemplate.exchange()
方法,如前所述。

RestTemplate非常基本且有限;似乎没有简单的方法可以做到这一点。最好的方法可能是在Web App 1中实现基本身份验证摘要。然后直接使用Apache HttpClient从Web App 2访问rest服务

<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>
话虽如此,为了进行测试,我做了一个很大的改动。基本上,您可以使用RestTemplate提交登录(j_spring_security_check),从请求头解析出jsessionid,然后提交rest请求。下面是代码,但我怀疑这是生产就绪代码的最佳解决方案

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());
}
<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>
公共期末考试{
公共静态void main(字符串[]args){
RestTemplate rest=新建RestTemplate();
HttpsURLConnection.setDefaultHostnameVerifier(新的HostnameVerifier(){
@凌驾
公共布尔验证(字符串s,SSLSession SSLSession){
返回true;
}
});
//使用JCA建立信任存储是另一个问题
//这假设您只能通过SSL登录
//你可以关掉它,但不能在生产现场!
setProperty(“javax.net.ssl.trustStore”,“/path/to/cacerts”);
setProperty(“javax.net.ssl.trustStorePassword”、“somepassword”);
字符串jsessionid=rest.execute(“https://localhost:8443/j_spring_security_check“,HttpMethod.POST,
新的RequestCallback(){
@凌驾
public void doWithRequest(ClientHttpRequest请求)引发IOException{
request.getBody().write(“j_username=user&j_password=user.getBytes());
}
},新的响应抽取器(){
@凌驾
公共字符串提取数据(ClientHttpResponse响应)引发IOException{
List cookies=response.getHeaders().get(“Cookie”);
//假设只有一个cookie的jsessionid是唯一的值
如果(cookies==null){
cookies=response.getHeaders().get(“设置Cookie”);
}
字符串cookie=cookies.get(cookies.size()-1);
int start=cookie.indexOf('=');
int end=cookie.indexOf(“;”);
返回cookie.substring(开始+1,结束);
}
});
休息。放(”http://localhost:8080/rest/program.json;jsessionid=“+jsessionid,新DAO(“REST测试”).asJSON();
}
}

<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>

注意:要使其正常工作,您需要在JCA中创建一个信任存储区,以便实际建立SSL连接。我假设您不想让Spring Security通过生产站点的纯HTTP登录,因为这将是一个巨大的安全漏洞。

我也遇到过同样的情况。这是我的解决方案

<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>
服务器-spring安全配置

<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>

客户端RestTemplateconfig

<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>

<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>
Maven依赖

<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>

commons httpclient
commons httpclient
3.1

这里有一个解决方案,它可以很好地与Spring 3.1和Apache HttpComponents 4.1配合使用,我在这个网站上创建了各种答案,并阅读了Spring RestTempalte源代码。我与大家分享,希望能节省其他人的时间,我认为Spring应该内置一些类似的代码,但它没有

<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>
RestClient client = new RestClient();
client.setApplicationPath("someApp");
String url = client.login("theuser", "123456");
UserPortfolio portfolio = client.template().getForObject(client.apiUrl("portfolio"), 
                         UserPortfolio.class);
下面是Factory类,它将HttpComponents上下文设置为在使用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>
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;
    }
}
下面是Statefull Rest模板,您可以使用它来记住cookies,一旦您使用它登录,它将记住JSESSIONID并在后续请求中发送它

<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>
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;
    }
}
下面是一个表示rest客户端的类,这样您就可以调用spring保护的应用程序 安全

<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>
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();
    }
}
公共类RestClient
{
私有字符串host=“localhost”;
专用字符串端口=“8080”;
私有字符串应用路径;
私有字符串apiPath=“api”;
私有字符串loginPath=“j_spring\u security\u check”;
私有字符串logoutPath=“logout”;
私有最终字符串usernameInputFieldName=“j_username”;
私有最终字符串passwordInputFieldName=“j_password”;
private final StatefullRestTemplate=新的StatefullRestTemplate();
/**
*此方法通过使用此类中的配置执行标准http登录到服务。
* 
*@param用户名
*用于登录应用程序的用户名
*@param密码
*用于登录应用程序的密码
* 
*@返回登录重定向到的url
*/
公共字符串登录(字符串用户名、字符串密码)
{
MultiValueMap form=新链接的MultiValueMap();
添加(用户名输入字段名,用户名);
添加(passwordInputFieldName,password);
URI location=this.template.postForLocation(loginUrl(),form);
返回位置.toString();
}
/**
*通过执行http ge注销