简体   繁体   English

glassfish的基本身份验证失败

[英]basic authentication fails with glassfish

First of all, my apologies for this long post. 首先,我为这篇长篇文章道歉。 This is a continuation of my previous question ( Authentication required window popping up after 7u21 update ) regarding this issue, but I narrowed down the search. 这是我之前的问题( 7u21更新后弹出身份验证所需窗口 )的延续,但是我缩小了搜索范围。 In short, it seems that my BASIC authentication is broken since Java 7u21. 简而言之,自Java 7u21以来,我的BASIC身份验证似乎已被破坏。

Applets started through JNLP files are not functioning stable at all and gives Authentication Popup windows. 通过JNLP文件启动的小程序根本不起作用,并提供了身份验证弹出窗口。

THE SETUP 设置

First of all I've set up a MySQL database with a usertable and grouptable. 首先,我建立了一个带有usertable和grouptable的MySQL数据库

  • Table: authentication 表:身份验证

在此输入图像描述

  • Table: groups 表:组

在此输入图像描述

Next I have setup a jdbcRealm in Glassfish. 接下来我在Glassfish中设置了一个jdbcRealm Notice that the database user and database password fields are empty because I use a JNDI (see further below): 请注意,数据库用户和数据库密码字段为空,因为我使用的是JNDI(请参见下文):

Glassfish realm settings: Glassfish境界设置:

在此输入图像描述

JDNI configuration (as shown in the domain.xml file): 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>

Once this was done, I changed the default realm to the newly created jdbcRealm and checked the Default principal to role mapping : 完成此操作后,我将默认域更改为新创建的jdbcRealm,并检查Default principal to role mapping

在此输入图像描述

TESTING 测试

After all that, for testing I created a simple WebService in Netbeans who fetches some countries from the database and configured the web.xml for BASIC authentication: 毕竟,为了测试,我在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>

To test the webservice, I right clicked it in NetBeans and clicked the Test RESTful Web Service . 为了测试Web服务,我在NetBeans中右键单击它并单击Test RESTful Web Service A new Internet Explorer window opens and show me a login screen, I enter the credentials for the dummy user and everything works. 一个新的Internet Explorer窗口打开并显示一个登录屏幕,我输入虚拟用户的凭据,一切正常。

Next, I create a simple JavaFX FXML project who fetches the countries. 接下来,我创建一个简单的JavaFX FXML项目来获取这些国家/地区。 I have a class (who uses Jersey) who looks like following. 我有一个班级(谁使用泽西岛)看起来像跟随。 This is generated code by Netbeans 7.3: 这是由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);
}

In my FXML Controller file, I have this method linked to a button: 在我的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();
}

That is about the setup of my project. 这是关于我的项目的设置。 Now, when I test this inside Netbeans or I launch this through the *.jar file, everything works as intended and it gives me following output: 现在,当我在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>

However, as soon as I start the applet through a *.jnlp file I get this annoying popup complaining about credentials: 但是,一旦我通过* .jnlp文件启动applet,我就会收到这个恼人的弹出窗口抱怨凭据:

在此输入图像描述

The java console records this: 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>

On the server side (glassfish log) 在服务器端(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")

There are a few things that I really don't understand: 我真的不明白一些事情:

  • On the client side, I see a 401 response code which means Not authorized. 在客户端,我看到401响应代码,这意味着未经授权。 How can this be if I use the exact same credentials that I use for testing my webservice? 如果我使用与用于测试我的Web服务的完全相同的凭据,这怎么可能呢?
  • On the client side, If I press cancel on the authentication popup windows, why do I still receive the XML data from my request if the user is not properly authenticated? 在客户端,如果我在身份验证弹出窗口中按取消,如果用户未经过正确的身份验证,为什么还会从我的请求中收到XML数据?
  • On the server side, Still a authentication process is happening. 在服务器端,仍然正在进行身份验证过程。 Does this have to do with the fact that this is coded in Java? 这是否与Java编码的事实有关? But again, if the authentication process actually succeeds, why is the authentication popup showing on the client side? 但同样,如果身份验证过程实际成功,为什么在客户端显示身份验证弹出窗口?

The reason for the symptoms you encounter is that Oracle has enabled caching of HTTP responses by default with JDK7 in Web Start : 您遇到的症状的原因是Oracle 在Web Start中默认使用JDK7启用了HTTP响应的缓存

Caching enabled by default: Caching of network content for application code running in Web Start mode is now enabled by default. 默认情况下启用缓存:默认情况下,现在启用在Web启动模式下运行的应用程序代码的网络内容缓存。 This allows application improved performance and consistency with applet execution mode. 这允许应用程序改进性能并与applet执行模式保持一致。 To ensure the latest copy of content is used, the application can use URLConnection.setUseCaches(false) or request header Cache-Control values no-cache/no-store . 为确保使用最新的内容副本,应用程序可以使用URLConnection.setUseCaches(false)或请求标头Cache-Controlno-cache/no-store

So what I did was, setting this header after creating the Jersey client: 所以我做的是,在创建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 );
    }
}

Now if the above specification would be correct, and the implementation of Web Start would follow the HTTP/1.1 reference , which states 现在,如果上述规范是正确的,并且Web Start的实现将遵循HTTP / 1.1引用 ,其中说明

The purpose of the no-store directive is to prevent the inadvertent release or retention of sensitive information (for example, on backup tapes). no-store指令的目的是防止无意释放或保留敏感信息(例如,在备份磁带上)。 The no-store directive applies to the entire message, and MAY be sent either in a response or in a request. no-store指令适用于整个消息,可以在响应中或在请求中发送。 If sent in a request, a cache MUST NOT store any part of either this request or any response to it. 如果在请求中发送,则缓存不得存储此请求的任何部分或对其的任何响应。

we would be fine - a network sniffer proved that the client was setting the Cache-Control header correctly. 我们没问题 - 网络嗅探器证明客户端正确设置了Cache-Control标头。 Also a ContainerResponseFilter of Jersey was showing me, that the header on the request was set correctly. 此外,泽西岛的ContainerResponseFilter向我展示了请求中的标头设置正确。 The response on the other hand had no Cache-Control header set. 另一方面, 响应没有设置Cache-Control标头。 Should not matter according to the specification, but in reality Web Start kept caching the responses! 根据规范无所谓,但实际上Web Start一直在缓存响应!

So I wrote a ContainerResponseFilter that copies the Cache-Control header from the request to the response: 所以我编写了一个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;
    }

}

and activated it in the web.xml 并在web.xml激活它

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

Then you have to delete your Java client cache: 然后你必须删除你的Java客户端缓存:

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

and voilà, no more annoying authentication pop ups :) 和voilà,没有更烦人的身份验证弹出窗口:)

I've converted the above code to use the Jersey 2.0 API. 我已经将上面的代码转换为使用Jersey 2.0 API。

On the client side, the ClientFilter is replaced with the ClientRequestFilter, and the filter's are registered. 在客户端,ClientFilter替换为ClientRequestFilter,并且过滤器已注册。 Also the filter interface doesn't chain like in the 1.0 API. 此外,过滤器接口不像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);
     }
  });

On the server side the interface name, ContainerResponseFilter, is the same, but the method signature is a little different. 在服务器端,接口名称ContainerResponseFilter是相同的,但方法签名有点不同。 There is no need for web.xml, as you can specify the @Provider annotation at the beginning of the class (which for me was the tricky part to get the solution to work). 不需要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