簡體   English   中英

glassfish的基本身份驗證失敗

[英]basic authentication fails with glassfish

首先,我為這篇長篇文章道歉。 這是我之前的問題( 7u21更新后彈出身份驗證所需窗口 )的延續,但是我縮小了搜索范圍。 簡而言之,自Java 7u21以來,我的BASIC身份驗證似乎已被破壞。

通過JNLP文件啟動的小程序根本不起作用,並提供了身份驗證彈出窗口。

設置

首先,我建立了一個帶有usertable和grouptable的MySQL數據庫

  • 表:身份驗證

在此輸入圖像描述

  • 表:組

在此輸入圖像描述

接下來我在Glassfish中設置了一個jdbcRealm 請注意,數據庫用戶和數據庫密碼字段為空,因為我使用的是JNDI(請參見下文):

Glassfish境界設置:

在此輸入圖像描述

JDNI配置 (如domain.xml文件中所示):

<jdbc-connection-pool connection-validation-method="auto-commit" datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource" wrap-jdbc-objects="false" res-type="javax.sql.DataSource" name="mysql_mit_rohhPool">
  <property name="URL" value="jdbc:mysql://localhost:3306/mit?zeroDateTimeBehavior=convertToNull"></property>
  <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
  <property name="Password" value="****"></property>
  <property name="portNumber" value="3306"></property>
  <property name="databaseName" value="mit"></property>
  <property name="User" value="****"></property>
  <property name="serverName" value="localhost"></property>
</jdbc-connection-pool>
<jdbc-resource pool-name="mysql_mit_rohhPool" jndi-name="jdbc/DB_MIT"></jdbc-resource>

完成此操作后,我將默認域更改為新創建的jdbcRealm,並檢查Default principal to role mapping

在此輸入圖像描述

測試

畢竟,為了測試,我在Netbeans中創建了一個簡單的WebService,它從數據庫中獲取一些國家並配置web.xml進行BASIC身份驗證:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<servlet>
    <servlet-name>ServletAdaptor</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
        <description>Multiple packages, separated by semicolon(;), can be specified in param-value</description>
        <param-name>com.sun.jersey.config.property.packages</param-name>
        <param-value>service</param-value>
    </init-param>
    <init-param>
        <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
        <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>ServletAdaptor</servlet-name>
    <url-pattern>/webresources/*</url-pattern>
</servlet-mapping>
<session-config>
    <session-timeout>
        30
    </session-timeout>
</session-config>
<security-constraint>
    <display-name>Basic Protection</display-name>
    <web-resource-collection>
        <web-resource-name>REST</web-resource-name>
        <description/>
        <url-pattern>/webresources/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <description/>
        <role-name>dummy</role-name>
    </auth-constraint>
</security-constraint>
<login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>jdbcRealm</realm-name>
</login-config>
<security-role>
    <description>Dummy</description>
    <role-name>dummy</role-name>
</security-role>

為了測試Web服務,我在NetBeans中右鍵單擊它並單擊Test RESTful Web Service 一個新的Internet Explorer窗口打開並顯示一個登錄屏幕,我輸入虛擬用戶的憑據,一切正常。

接下來,我創建一個簡單的JavaFX FXML項目來獲取這些國家/地區。 我有一個班級(誰使用澤西島)看起來像跟隨。 這是由Netbeans 7.3生成的代碼:

private WebResource webResource;
private Client client;
private static final String BASE_URI = "http://localhost:8080/myWS/webresources";

public CountriesClient() {
    com.sun.jersey.api.client.config.ClientConfig config = new com.sun.jersey.api.client.config.DefaultClientConfig();
    client = Client.create(config);
    webResource = client.resource(BASE_URI).path("entities.countries");
}

public void close() {
    client.destroy();
}

public void setUsernamePassword(String username, String password) {
    client.addFilter(new com.sun.jersey.api.client.filter.HTTPBasicAuthFilter(username, password));
}

public <T> T findAll_XML(Class<T> responseType) throws UniformInterfaceException {
    WebResource resource = webResource;
    return resource.accept(javax.ws.rs.core.MediaType.APPLICATION_XML).get(responseType);
}

在我的FXML Controller文件中,我將此方法鏈接到一個按鈕:

@FXML
private void handleButtonAction(ActionEvent event) {
    System.out.println("You clicked me!");

    CountriesClient c = new CountriesClient();
    c.setUsernamePassword("dummy", "****");
    String r = c.findAll_XML(String.class);
    System.out.println(r);
    c.close();
}

這是關於我的項目的設置。 現在,當我在Netbeans中測試它或者我通過* .jar文件啟動它時,一切都按預期工作,它給了我以下輸出:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><countriess><countries><country>Belgium</country><id>1</id></countries><countries><country>Ireland</country><id>2</id></countries><countries><country>United Kingdom</country><id>3</id></countries><countries><country>Poland</country><id>4</id></countries></countriess>

但是,一旦我通過* .jnlp文件啟動applet,我就會收到這個惱人的彈出窗口抱怨憑據:

在此輸入圖像描述

java控制台記錄了這個:

network: Cache entry found [url: http://localhost:8080/myWS/webresources/entities.countries, version: null] prevalidated=false/0
cache: Adding MemoryCache entry: http://localhost:8080/myWS/webresources/entities.countries
cache: Resource http://localhost:8080/myWS/webresources/entities.countries has expired.
cache: Resource http://localhost:8080/myWS/webresources/entities.countries has cache control: no-cache.
network: Connecting http://localhost:8080/myWS/webresources/entities.countries with proxy=DIRECT
network: Connecting socket://localhost:8080 with proxy=DIRECT
network: Firewall authentication: site=localhost/127.0.0.1:8080, protocol=http, prompt=jdbcRealm, scheme=basic
network: ResponseCode for http://localhost:8080/myWS/webresources/entities.countries : 401
network: Encoding for http://localhost:8080/myWS/webresources/entities.countries : null
network: Connecting http://localhost:8080/myWS/webresources/entities.countries with proxy=DIRECT
basic: JNLP2ClassLoader.findClass: com.sun.jersey.core.header.InBoundHeaders: try again ..
basic: JNLP2ClassLoader.findClass: com.sun.jersey.core.util.StringKeyStringValueIgnoreCaseMultivaluedMap: try again ..
network: Downloading resource: http://localhost:8080/myWS/webresources/entities.countries
Content-Length: 322
Content-Encoding: null
network: Wrote URL http://localhost:8080/myWS/webresources/entities.countries to File C:\Users\stbrunee\AppData\LocalLow\Sun\Java\Deployment\cache\6.0\6\4b456206-236d2196-temp
cache: MemoryCache replacing http://localhost:8080/myWS/webresources/entities.countries (refcnt=0). Was: URL: http://localhost:8080/myWS/webresources/entities.countries | C:\Users\stbrunee\AppData\LocalLow\Sun\Java\Deployment\cache\6.0\6\4b456206-15cb0b99.idx Now: URL: http://localhost:8080/myWS/webresources/entities.countries | C:\Users\stbrunee\AppData\LocalLow\Sun\Java\Deployment\cache\6.0\6\4b456206-236d2196.idx
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><countriess><countries><country>Belgium</country><id>1</id></countries><countries><country>Ireland</country><id>2</id></countries><countries><country>United Kingdom</country><id>3</id></countries><countries><country>Poland</country><id>4</id></countries></countriess>

在服務器端(glassfish日志)

FINE: [Web-Security] Policy Context ID was: myWS/myWS
FINE: [Web-Security] hasUserDataPermission perm: ("javax.security.jacc.WebUserDataPermission" "/webresources/entities.countries" "GET")
FINE: [Web-Security] hasUserDataPermission isGranted: true
FINE: [Web-Security] Policy Context ID was: myWS/myWS
FINE: [Web-Security] Codesource with Web URL: file:/myWS/myWS
FINE: [Web-Security] Checking Web Permission with Principals : null
FINE: [Web-Security] Web Permission = ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET")
FINEST: JACC Policy Provider: PolicyWrapper.implies, context (myWS/myWS)- result was(false) permission (("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET"))
FINE: [Web-Security] hasResource isGranted: false
FINE: [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET")
FINE: [Web-Security] Policy Context ID was: myWS/myWS
FINE: [Web-Security] hasUserDataPermission perm: ("javax.security.jacc.WebUserDataPermission" "/webresources/entities.countries" "HEAD")
FINE: [Web-Security] hasUserDataPermission isGranted: true
FINE: [Web-Security] Policy Context ID was: myWS/myWS
FINE: [Web-Security] Codesource with Web URL: file:/myWS/myWS
FINE: [Web-Security] Checking Web Permission with Principals : null
FINE: [Web-Security] Web Permission = ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "HEAD")
FINEST: JACC Policy Provider: PolicyWrapper.implies, context (myWS/myWS)- result was(false) permission (("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "HEAD"))
FINE: [Web-Security] hasResource isGranted: false
FINE: [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "HEAD")
//NOW I PRESS CANCEL AT THE POPUP WINDOW CLIENT SIDE
FINE: [Web-Security] Setting Policy Context ID: old = null ctxID = myWS/myWS
FINE: [Web-Security] hasUserDataPermission perm: ("javax.security.jacc.WebUserDataPermission" "/webresources/entities.countries" "GET")
FINE: [Web-Security] hasUserDataPermission isGranted: true
FINE: [Web-Security] Policy Context ID was: myWS/myWS
FINE: [Web-Security] Codesource with Web URL: file:/myWS/myWS
FINE: [Web-Security] Checking Web Permission with Principals : null
FINE: [Web-Security] Web Permission = ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET")
FINEST: JACC Policy Provider: PolicyWrapper.implies, context (myWS/myWS)- result was(false) permission (("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET"))
FINE: [Web-Security] hasResource isGranted: false
FINE: [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET")
FINEST: Processing login with credentials of type: class com.sun.enterprise.security.auth.login.common.PasswordCredential
FINE: Logging in user [dummy] into realm: jdbcRealm using JAAS module: jdbcRealm
FINE: Login module initialized: class com.sun.enterprise.security.auth.login.JDBCLoginModule
FINEST: JDBC login succeeded for: dummy groups:[dummy]
FINE: JAAS login complete.
FINE: JAAS authentication committed.
FINE: Password login succeeded for : dummy
FINE: Set security context as user: dummy
FINE: [Web-Security] Policy Context ID was: myWS/myWS
FINE: [Web-Security] Codesource with Web URL: file:/myWS/myWS
FINE: [Web-Security] Checking Web Permission with Principals : dummy, dummy
FINE: [Web-Security] Web Permission = ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET")
FINE: [Web-Security] hasResource isGranted: true
FINE: [Web-Security] hasResource perm: ("javax.security.jacc.WebResourcePermission" "/webresources/entities.countries" "GET")

我真的不明白一些事情:

  • 在客戶端,我看到401響應代碼,這意味着未經授權。 如果我使用與用於測試我的Web服務的完全相同的憑據,這怎么可能呢?
  • 在客戶端,如果我在身份驗證彈出窗口中按取消,如果用戶未經過正確的身份驗證,為什么還會從我的請求中收到XML數據?
  • 在服務器端,仍然正在進行身份驗證過程。 這是否與Java編碼的事實有關? 但同樣,如果身份驗證過程實際成功,為什么在客戶端顯示身份驗證彈出窗口?

您遇到的症狀的原因是Oracle 在Web Start中默認使用JDK7啟用了HTTP響應的緩存

默認情況下啟用緩存:默認情況下,現在啟用在Web啟動模式下運行的應用程序代碼的網絡內容緩存。 這允許應用程序改進性能並與applet執行模式保持一致。 為確保使用最新的內容副本,應用程序可以使用URLConnection.setUseCaches(false)或請求標頭Cache-Controlno-cache/no-store

所以我做的是,在創建Jersey客戶端后設置此標頭:

Client client = Client.create();
client.addFilter( new HTTPBasicAuthFilter( userId, password ) );
client.addFilter( new ClientFilter() {

    @Override
    public ClientResponse handle( ClientRequest cr )
       throws ClientHandlerException {
           List<Object> cacheControlRequestValues = new ArrayList<Object>();
           cacheControlRequestValues.add( "no-cache" );
           cacheControlRequestValues.add( "no-store" );
           cr.getHeaders().put( HttpHeaders.CACHE_CONTROL, cacheControlRequestValues );
           return getNext().handle( cr );
    }
}

現在,如果上述規范是正確的,並且Web Start的實現將遵循HTTP / 1.1引用 ,其中說明

no-store指令的目的是防止無意釋放或保留敏感信息(例如,在備份磁帶上)。 no-store指令適用於整個消息,可以在響應中或在請求中發送。 如果在請求中發送,則緩存不得存儲此請求的任何部分或對其的任何響應。

我們沒問題 - 網絡嗅探器證明客戶端正確設置了Cache-Control標頭。 此外,澤西島的ContainerResponseFilter向我展示了請求中的標頭設置正確。 另一方面, 響應沒有設置Cache-Control標頭。 根據規范無所謂,但實際上Web Start一直在緩存響應!

所以我編寫了一個ContainerResponseFilter,它將請求中的Cache-Control頭復制到響應中:

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;

import javax.ws.rs.core.HttpHeaders;
import java.util.ArrayList;
import java.util.List;

public class CacheControlCopyFilter
    implements ContainerResponseFilter 

    @Override
    public ContainerResponse filter( ContainerRequest containerRequest, ContainerResponse containerResponse ) {
        if ( containerRequest.getRequestHeader( HttpHeaders.CACHE_CONTROL ) != null ) {
            List<Object> responseCacheControlValues = new ArrayList<Object>( containerRequest.getRequestHeader( HttpHeaders.CACHE_CONTROL ).size() );
            for ( String value : containerRequest.getRequestHeader( HttpHeaders.CACHE_CONTROL ) ) {
                responseCacheControlValues.add( value );
            }
            containerResponse.getHttpHeaders().put( HttpHeaders.CACHE_CONTROL, responseCacheControlValues );
        }
        return containerResponse;
    }

}

並在web.xml激活它

<init-param>
    <param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
    <param-value>my.package.CacheControlCopyFilter</param-value>
</init-param>

然后你必須刪除你的Java客戶端緩存:

"javaws -viewer" -> General
                 -> Settings...
                 -> Delete Files...
                 -> Select all three check boxes
                 -> OK

和voilà,沒有更煩人的身份驗證彈出窗口:)

我已經將上面的代碼轉換為使用Jersey 2.0 API。

在客戶端,ClientFilter替換為ClientRequestFilter,並且過濾器已注冊。 此外,過濾器接口不像1.0 API中那樣鏈接。

  Client client = ClientBuilder.newClient(clientConfig);
  client.register(new HttpBasicAuthFilter(username, password));
  client.register(new ClientRequestFilter() {
     @Override
     public void filter(ClientRequestContext crc) throws IOException {
        List<Object> cacheControlRequestValues = new ArrayList<>();
        cacheControlRequestValues.add("no-cache");
        cacheControlRequestValues.add("no-store");
        crc.getHeaders().put(HttpHeaders.CACHE_CONTROL, cacheControlRequestValues);
     }
  });

在服務器端,接口名稱ContainerResponseFilter是相同的,但方法簽名有點不同。 不需要web.xml,因為您可以在類的開頭指定@Provider注釋(對我來說這是使解決方案起作用的棘手部分)。

@Provider
public class CacheControlCopyFilter implements ContainerResponseFilter {

   @Override
   public void filter(
           ContainerRequestContext requestContext,
           ContainerResponseContext responseContext) throws IOException {

      if (requestContext.getHeaderString(HttpHeaders.CACHE_CONTROL) != null) {
         List<Object> responseCacheControlValues = new ArrayList<>(
                 requestContext.getHeaders().get(HttpHeaders.CACHE_CONTROL).size());
         for (String value : requestContext.getHeaders().get(HttpHeaders.CACHE_CONTROL)) {
            responseCacheControlValues.add(value);
         }
         responseContext.getHeaders().put(HttpHeaders.CACHE_CONTROL, responseCacheControlValues);
      }
   }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM