Spring-WS 2.3.0 Security Header Validation with WSS4J 2.1.4 - NoSecurity won't work - spring-ws

I'm using Spring-WS for Client an try to update to the newest version. Allthough configured not to validate incoming security header the new Wss4jSecurityInterceptor throws Wss4jSecurityValidationException("No WS-Security header found").
<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
<property name="securementActions" value="UsernameToken"/>
<property name="validationActions" value="NoSecurity"/>
<property name="securementPasswordType" value="PasswordText"/>
<property name="securementUsernameTokenElements" value="Nonce"/>
</bean>
In my opinion it's because Spring-WS 2.3.0 and WSS4J 2.1.4 are incompatible at this point.
Wss4jSecurityInterceptor fills the field validationActionsVector as follows:
public void setValidationActions(String actions) {
this.validationActions = actions;
try {
validationActionsVector = WSSecurityUtil.decodeAction(actions);
}
catch (WSSecurityException ex) {
throw new IllegalArgumentException(ex);
}
}
where WSS4J in case of NoSecurity returns in WSSecurityUtil an empty List:
public static List<Integer> decodeAction(String action) throws WSSecurityException {
String actionToParse = action;
if (actionToParse == null) {
return Collections.emptyList();
}
actionToParse = actionToParse.trim();
if ("".equals(actionToParse)) {
return Collections.emptyList();
}
List<Integer> actions = new ArrayList<>();
String single[] = actionToParse.split("\\s");
for (int i = 0; i < single.length; i++) {
if (single[i].equals(WSHandlerConstants.NO_SECURITY)) {
return actions;
} else if ...
But Wss4jSecurityInterceptor checks for an NoSecurity-Item in the list:
#Override
protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext)
throws WsSecurityValidationException {
if (logger.isDebugEnabled()) {
logger.debug("Validating message [" + soapMessage + "] with actions [" + validationActions + "]");
}
if (validationActionsVector.contains(WSConstants.NO_SECURITY)) {
return;
} ...
Is this a known issue? Does a workaround exist? Or do I have to override the method in WSS4J to fill the list with the expected item?

I had the same problem - no way to avoid validation - but I solved it by:
setting validateRequest and validateResponse to false in the interceptor.
No need to hack any code or extend any class. You can check the related issue at https://jira.spring.io/browse/SWS-961.

I agree, this is a problem.
I have the same scenario where I do not need to validate the incoming message.
I have overridden the validateMessage method in my application class which extends Wss4jSecurityInterceptor and this seems to be a cleaner solution.
#Override
protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext) throws WsSecurityValidationException {
return;
}

I found a workaraound that works for me. Of course it would be better to be fixed in the next Spring-WS Version.
public class MyWss4jSecurityInterceptor extends Wss4jSecurityInterceptor {
private String validationActions;
/**
* Overrides the method in order to avoid a security check if the
* ValidationAction 'NoSecurity'is selected.
*
* #param messageContext
*/
#Override
protected void validateMessage(SoapMessage soapMessage, MessageContext messageContext)
throws WsSecurityValidationException {
if (!WSHandlerConstants.NO_SECURITY.equals(validationActions)) {
super.validateMessage(soapMessage, messageContext);
}
}
/**
* #return the validationActions
*/
public String getValidationActions() {
return validationActions;
}
/**
* #param validationActions the validationActions to set
*/
#Override
public void setValidationActions(String validationActions) {
this.validationActions = validationActions;
super.setValidationActions(validationActions);
}
}

can use "org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor" its deprecated though.
It worked for me instead of creating new Extension to class, anyway i am not using it for any validation.

Related

How to get consumerTag in spring-rabbitmq 1.x

the spring-rabbitmq config is like
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.1.3.RELEASE</version>
I want to canceling a consumer , use channel.basicCannel(consumerTag)
when i use ChannelAwareMessageListener in spring-rabbitmq 2.x version, consumerTag are
in MessageProperties,
but my online service is use 1.x version, there has no consumerTag in MessageProperties,
so i cant use the basicCancel api
my full listener code below
public class RPCListener implements ChannelAwareMessageListener {
private static final Logger log = LoggerFactory.getLogger(RPCListener.class);
#Autowired
private MessagePropertiesConverter messagePropertiesConverter;
private MessageConverter messageConverter = new SimpleMessageConverter();
#Autowired
private AmqpTemplate amqpTemplate;
private Boolean flag = false;
#Override
public void onMessage(Message message, Channel channel) throws Exception {
try {
log.error("DeliveryTag(): {}", message.getMessageProperties().getDeliveryTag());
if (flag) {
log.error("canceling....");
//If true, messages will be requeued and possibly
channel.basicRecover(true);
// there is no consumerTag property in MessageProperties
//channel.basicCancel(message.getMessageProperties().getConsumerTag());
return;
}
amqpTemplate.send(message.getMessageProperties().getReplyTo(), message);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
e.printStackTrace();
}
}
public Boolean getFlag() {
return flag;
}
public void setFlag(Boolean flag) {
this.flag = flag;
}
}
Is there any way to get consumerTag?
Thanks all!
1.1.3 is 7 years old; the consumerTag was added to MessageProperties in 1.4.2.
The only supported 1.x version is 1.7.14 - see the project page.
1.7.x will no longer be supported after this year.
However; you should NOT manually cancel the consumer; stop the container instead.

How to configure Micronaut and Micrometer to write ILP directly to InfluxDB?

I have a Micronaut application that uses Micrometer to report metrics to InfluxDB with the micronaut-micrometer project. Currently it is using the Statsd Registry provided via the io.micronaut.configuration:micronaut-micrometer-registry-statsd dependency.
I would like to instead output metrics in Influx Line Protocol (ILP), but the micronaut-micrometer project does not offer an Influx Registry currently. I tried to work around this by importing the io.micrometer:micrometer-registry-influx dependency and configuring an InfluxMeterRegistry manually like this:
#Factory
public class MyMetricRegistryConfigurer implements MeterRegistryConfigurer {
#Bean
#Primary
#Singleton
public MeterRegistry getMeterRegistry() {
InfluxConfig config = new InfluxConfig() {
#Override
public Duration step() {
return Duration.ofSeconds(10);
}
#Override
public String db() {
return "metrics";
}
#Override
public String get(String k) {
return null; // accept the rest of the defaults
}
};
return new InfluxMeterRegistry(config, Clock.SYSTEM);
}
#Override
public boolean supports(MeterRegistry meterRegistry) {
return meterRegistry instanceof InfluxMeterRegistry;
}
}
When the application runs, the metrics are exposed on my /metrics endpoint as I would expect, but nothing gets written to InfluxDB. I confirmed that my local InfluxDB accepts metrics at the expected localhost:8086/write?db=metrics endpoint using curl. Can anyone give me some pointers to get this working? I'm wondering if I need to manually define a reporter somewhere...
After playing around for a bit, I got this working with the following code:
#Factory
public class InfluxMeterRegistryFactory {
#Bean
#Singleton
#Requires(property = MeterRegistryFactory.MICRONAUT_METRICS_ENABLED, value =
StringUtils.TRUE, defaultValue = StringUtils.TRUE)
#Requires(beans = CompositeMeterRegistry.class)
public InfluxMeterRegistry getMeterRegistry() {
InfluxConfig config = new InfluxConfig() {
#Override
public Duration step() {
return Duration.ofSeconds(10);
}
#Override
public String db() {
return "metrics";
}
#Override
public String get(String k) {
return null; // accept the rest of the defaults
}
};
return new InfluxMeterRegistry(config, Clock.SYSTEM);
}
}
I also noticed that an InfluxMeterRegistry will be available out of the box in the future for micronaut-micrometer as of v1.2.0.

Can't catch success authorization even on Spring Security

Problem:
I have implemented the following application event listener and it can catch authentication (both cases success and failure) and authorization ( fail). However, while authorization is successful, the even does not be triggered. I traced the code and figured out publishAuthorizationSuccess in AbstractSecurityInterceptor class is always false so it doesn’t publish AuthorizedEvent.
Environment:
Run it on JUnit
The execution sequence of my program:
Run MySampleApp -> SomeService -> ResourcePatternBaseVoter -> AbstractSecurityInterceptor -> SecurityAuditor (not triggered when authorized successfully)
My code and config are shown as follows:
MySampleApp.class
public class MySampleApp{
#Test
public void test2() {
Authentication authentication = providerManager
.authenticate(new UsernamePasswordAuthenticationToken("admin", "admin"));
SecurityContextHolder.getContext().setAuthentication(authentication);
logger.debug(someService1.someMethod6());
}
SomeService.java
#Service
public class SomeService1 {
#Secured("rpb:reports:a.b.c:create")
public String someMethod6() {
return String.valueOf(Math.random());
}
ResourcePatternBaseVoter.java
#Component
public class ResourcePatternBaseVoter implements org.springframework.security.access.AccessDecisionVoter<Object> {
private static final Log logger = LogFactory.getLog(ResourcePatternBaseVoter.class);
#Autowired
private ResourcePatternBaseAuthorizer resourcePatternBaseAuthorizer;
#Override
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith("rpb:")) {
logger.debug("support attribute: " + attribute.getAttribute());
return true;
} else {
logger.debug("not support attribute: " + attribute.getAttribute());
return false;
}
}
#Override
public boolean supports(Class<?> clazz) {
return true;
}
#Override
public int vote(Authentication authentication, Object secureObject, Collection<ConfigAttribute> attributes) {
/* doSomething */
return ACCESS_GRANTED;
}
}
SecurityAuditor.java
#Component
public class SecurityAuditor implements ApplicationListener<AuthorizedEvent> {
#Override
public void onApplicationEvent(AuthorizedEvent event) {
logger.info("Here");
}
myAcl.xml
<bean id="methodAccessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg name="decisionVoters">
<list>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
<bean class="com.ibm.gbsc.ty.acl.rpb.ResourcePatternBaseVoter" />
</list>
</constructor-arg>
</bean>
AbstractSecurityInterceptor.class
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
This article got me started, but that bean does not exist in Spring Security 4.1.3 anymore. However, I found it hidden inside FilterChainProxy.
Not sure how ugly this hack is, but works:
#Configuration
#EnableWebSecurity
#EnableJpaAuditing
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ApplicationContext applicationContext;
#EventListener
public void handle(ContextRefreshedEvent event) {
FilterChainProxy proxy = applicationContext.getBean(FilterChainProxy.class);
for (Filter f : proxy.getFilters("/")) {
if (f instanceof FilterSecurityInterceptor) {
((FilterSecurityInterceptor)f).setPublishAuthorizationSuccess(true);
}
}
}
...
}
Then my listener finally receives the AuthorizedEvent:
#Component
public class AppEventListener implements ApplicationListener {
private static final Logger logger = LoggerFactory.getLogger(AppEventListener.class);
#Override
#EventListener(value = {AuthorizedEvent.class})
public void onApplicationEvent(ApplicationEvent event)
{
if (event instanceof InteractiveAuthenticationSuccessEvent) {
Authentication auth = ((InteractiveAuthenticationSuccessEvent)event).getAuthentication();
logger.info("Login success: " + auth.getName() + ", details: " + event.toString());
} else if (event instanceof AbstractAuthenticationFailureEvent) {
logger.error("Login failed: " + event.toString());
} else if (event instanceof AuthorizedEvent) {
Authentication auth = ((AuthorizedEvent)event).getAuthentication();
logger.debug("Authorized: " + auth.getName() + ", details: " + event.toString());
} else if (event instanceof AuthorizationFailureEvent) {
Authentication auth = ((AuthorizationFailureEvent)event).getAuthentication();
logger.error("Authorization failed: " + auth.getName() + ", details: " + event.toString());
}
}
}
I tried to use the solution proposed by Arthur, however it throws a UnsupportedOperationException at proxy.getFilters("/").
Caused by: java.lang.UnsupportedOperationException: public abstract javax.servlet.ServletContext javax.servlet.ServletRequest.getServletContext() is not supported
at org.springframework.security.web.UnsupportedOperationExceptionInvocationHandler.invoke(FilterInvocation.java:235) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at com.sun.proxy.$Proxy269.getServletContext(Unknown Source) ~[na:na]
at javax.servlet.ServletRequestWrapper.getServletContext(ServletRequestWrapper.java:369) ~[tomcat-embed-core-9.0.43.jar:4.0.FR]
at javax.servlet.ServletRequestWrapper.getServletContext(ServletRequestWrapper.java:369) ~[tomcat-embed-core-9.0.43.jar:4.0.FR]
at org.springframework.boot.security.servlet.ApplicationContextRequestMatcher.matches(ApplicationContextRequestMatcher.java:58) ~[spring-boot-2.3.9.RELEASE.jar:2.3.9.RELEASE]
at org.springframework.security.web.util.matcher.OrRequestMatcher.matches(OrRequestMatcher.java:67) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.DefaultSecurityFilterChain.matches(DefaultSecurityFilterChain.java:57) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.FilterChainProxy.getFilters(FilterChainProxy.java:226) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.FilterChainProxy.getFilters(FilterChainProxy.java:241) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
In order to fix this I changed the implementation to
#EventListener
public void handle ( ContextRefreshedEvent event ) {
applicationContext.getBean ( FilterChainProxy.class )
.getFilterChains ()
.stream ()
.map ( SecurityFilterChain::getFilters )
.flatMap ( Collection::stream )
.filter ( filter -> filter instanceof FilterSecurityInterceptor )
.map ( filter -> (FilterSecurityInterceptor) filter)
.forEach ( filterSecurityInterceptor -> filterSecurityInterceptor.setPublishAuthorizationSuccess ( true ) );
}
While this works this will apply to all filter chains and all instances of the FilterSecurityInterceptor.
It would be possible to filter these further since the FilterSecurityInterceptor maintains a map of which the keys are RequestMatchers and these could be used to narrow it down further, for example to those instances of the FilterSecurityInterceptor that are applied to a certain route or those that require a certain authority. However since the map is private and there is no way to access the keys of the map, Reflection is required in order to do this.
Since I want to avoid using Reflection, I would rather suggest carefully configuring the Security Filter Chain so that it does not throw unnecessary AuthorizedEvents and to ensure that whatever is listening to these events is cheap and fast to execute.
I'm using Spring Boot 2.3.9.RELEASE which depends on Spring Security 5.3.8.RELEASE
It worth pointing that Spring Security is currently adding the so called AuthorizationManager, hopefully this will allow the configuration of this option in more natural way or make it obsolete.

Custom annotation with spring security

I have read spring security docs and learned that I can use the following annotation to check if the subject had access to edit user.
#PreAuthorize("hasPermission('USER_EDIT')")
public String editUSer(User user);
What I would like to do is to write my custom annotation MyAutorizationCheck and use it like below
#MyAuthorizationCheck(Application.USER_MANAGEMENT, AccessLevel.EDIT)
public String editUSer(User user);
Where Application and AccessLevel are enum.
enum Application{
USER_MANAGEMENT, ORDER_MANAGEMENT
}
enum AccessLevel{
READ, CREATE, UPDATE, DELETE
}
Handler for this annotation should be able to decide if the user has permission or not.
Any pointers how to achieve this?
Thank you.
Spring security use PrePostAnnotationSecurityMetadataSource to find the #PreAuthorize and convert the Spring-EL expression to ConfigAttribute.
You can implement your MyAuthorizationCheckAnnotationSecurityMetadataSource and override getAttributes method to convert your enums to ConfigAttribute too;
write your code like this:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface MyAuthorizationCheck {
Application app();
AccessLevel level();
}
public class MyAuthorizationCheckAnnotationSecurityMetadataSource extends AbstractMethodSecurityMetadataSource {
private final PrePostInvocationAttributeFactory attributeFactory;
public MyAuthorizationCheckAnnotationSecurityMetadataSource(PrePostInvocationAttributeFactory attributeFactory) {
this.attributeFactory = attributeFactory;
}
#Override
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return Collections.emptyList();
}
this.logger.trace(LogMessage.format("Looking for FddApi annotations for method '%s' on target class '%s'",
method.getName(), targetClass));
MyAuthorizationCheck myAuthorization = findAnnotation(method, targetClass, MyAuthorizationCheck.class);
if (myAuthorization == null) {
this.logger.trace("No expression annotations found");
return Collections.emptyList();
}
Application app = myAuthorization.app();
AccessLevel level = myAuthorization.level();
// build the Spring-EL expression from enums
String expr = "hasPermission('" + app.name() + "_" + level.name() + "')";
PreInvocationAttribute pre = this.attributeFactory.createPreInvocationAttribute(null, null, expr);
return CollUtil.newArrayList(pre);
}
// other method can copy from PrePostAnnotationSecurityMetadataSource
...
}
then registe the MyAuthorizationCheckAnnotationSecurityMetadataSource.
our code need PreInvocationAuthorizationAdviceVoter to check permission, so need enable the prePostEnabled
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class CustomSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
getExpressionHandler());
return new MyAuthorizationCheckAnnotationSecurityMetadataSource(attributeFactory);
}
}
Finally you can use the #MyAuthorizationCheck like this:
#MyAuthorizationCheck(app = Application.USER_MANAGEMENT, level = AccessLevel.EDIT)
public String editUSer(User user);
It is not a direct response to your question.
As a workoround you can continue to use built-in annotations:
#PreAuthorize("hasPermission('USER_MANAGEMENT_READ')")
#PreAuthorize("hasPermission('USER_MANAGEMENT_CREATE')")
#PreAuthorize("hasPermission('USER_MANAGEMENT_UPDATE')")
#PreAuthorize("hasPermission('USER_MANAGEMENT_DELETE')")
#PreAuthorize("hasPermission('ORDER_MANAGEMENT_READ')")
#PreAuthorize("hasPermission('ORDER_MANAGEMENT_CREATE')")
#PreAuthorize("hasPermission('ORDER_MANAGEMENT_UPDATE')")
#PreAuthorize("hasPermission('ORDER_MANAGEMENT_DELETE')")

Custom JavaFX WebView Protocol Handler

I am trying to write my own protocol handler for a JavaFX application that uses webview to access a single website. What I have done so far
My custom URLStreamHandlerFactory
public class MyURLStreamHandlerFactory implements URLStreamHandlerFactory {
public URLStreamHandler createURLStreamHandler(String protocol) {
System.out.println("Protocol: " + protocol);
if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) {
return new MyURLStreamHandler();
} else {
return new URLStreamHandler() {
#Override
protected URLConnection openConnection(URL u) throws IOException {
return new URLConnection(u) {
#Override
public void connect() throws IOException {
}
};
}
};
}
}
}
My custom URLStreamHandler
public class MyURLStreamHandler extends java.net.URLStreamHandler{
protected HttpURLConnection openConnection(URL u){
MyURLConnection q = new MyURLConnection(u);
return q;
}
}
My custom HttpURLConnection
public class MyURLConnection extends HttpURLConnection {
static int defaultPort = 443;
InputStream in;
OutputStream out;
Socket s;
publicMyURLConnection(URL url) {
super(url);
try {
setRequestMethod("POST");
} catch (ProtocolException ex) {
ex.printStackTrace();
}
}
public void setRequestProperty(String name, String value){
super.setRequestProperty(name, value);
System.out.println("Namee: " + name);
System.out.println("Value: " + value);
}
public String getRequestProperty(String name){
System.out.println("GET REQUEST: ");
return super.getRequestProperty(name);
}
public OutputStream getOutputStream() throws IOException {
OutputStream os = super.getOutputStream();
System.out.println("Output: " + os);
return os;
}
public InputStream getInputStream() throws IOException {
InputStream is = super.getInputStream();
System.out.println("INout stream: " + is);
return is;
}
#Override
public void connect() throws IOException {
}
#Override
public void disconnect() {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public boolean usingProxy() {
throw new UnsupportedOperationException("Not supported yet.");
}
When I run the application I get the following error althouhg it seems to set some headers
Jul 08, 2013 11:09:04 AM com.sun.webpane.webkit.network.URLLoader doRun
WARNING: Unexpected error
java.net.UnknownServiceException: protocol doesn't support input
at java.net.URLConnection.getInputStream(URLConnection.java:839)
at qmed.QMedURLConnection.getInputStream(MyURLConnection.java:67)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:468)
at com.sun.webpane.webkit.network.URLLoader.receiveResponse(URLLoader.java:383)
at com.sun.webpane.webkit.network.URLLoader.doRun(URLLoader.java:142)
at com.sun.webpane.webkit.network.URLLoader.access$000(URLLoader.java:44)
at com.sun.webpane.webkit.network.URLLoader$1.run(URLLoader.java:106)
at com.sun.webpane.webkit.network.URLLoader$1.run(URLLoader.java:103)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.webpane.webkit.network.URLLoader.run(URLLoader.java:103)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
All I want to do is get the response back for a given request and reads its binary data. I want the protocol to behave exactly the same way as the default one and only check the binary data of a given respone. What am I doing wrong?
The application is doing all shorts of URLConnections. Is it correct to use a HTTPURLConnection as my custom URLConnection class when the protocol is http or https and start a default URLStreamHandler when other protocols are used like I am doing in MyURLStreamHandlerFactory? Should I just extend the default URLConnection class in MYURLConnection to handle all protocols the same?
Any help would be much appreciated as this is a project threatening problem
Thank you
It might be that all you are missing is a setDoInput(true) or override getDoInput() and return true (that's what i did).
If that does not help check out my working solution:
MyURLStreamHandlerFactory:
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
public class MyURLStreamHandlerFactory implements URLStreamHandlerFactory
{
public URLStreamHandler createURLStreamHandler(String protocol)
{
if (protocol.equals("myapp"))
{
return new MyURLHandler();
}
return null;
}
}
Register Factory:
URL.setURLStreamHandlerFactory(new MyURLStreamHandlerFactory());
MyURLHandler :
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public class MyURLHandler extends URLStreamHandler
{
#Override
protected URLConnection openConnection(URL url) throws IOException
{
return new MyURLConnection(url);
}
}
MyURLConnection:
import java.io.*;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
/**
* Register a protocol handler for URLs like this: <code>myapp:///pics/sland.gif</code><br>
*/
public class MyURLConnection extends URLConnection
{
private byte[] data;
#Override
public void connect() throws IOException
{
if (connected)
{
return;
}
loadImage();
connected = true;
}
public String getHeaderField(String name)
{
if ("Content-Type".equalsIgnoreCase(name))
{
return getContentType();
}
else if ("Content-Length".equalsIgnoreCase(name))
{
return "" + getContentLength();
}
return null;
}
public String getContentType()
{
String fileName = getURL().getFile();
String ext = fileName.substring(fileName.lastIndexOf('.'));
return "image/" + ext; // TODO: switch based on file-type
}
public int getContentLength()
{
return data.length;
}
public long getContentLengthLong()
{
return data.length;
}
public boolean getDoInput()
{
return true;
}
public InputStream getInputStream() throws IOException
{
connect();
return new ByteArrayInputStream(data);
}
private void loadImage() throws IOException
{
if (data != null)
{
return;
}
try
{
int timeout = this.getConnectTimeout();
long start = System.currentTimeMillis();
URL url = getURL();
String imgPath = url.toExternalForm();
imgPath = imgPath.startsWith("myapp://") ? imgPath.substring("myapp://".length()) : imgPath.substring("myapp:".length()); // attention: triple '/' is reduced to a single '/'
// this is my own asynchronous image implementation
// instead of this part (including the following loop) you could do your own (synchronous) loading logic
MyImage img = MyApp.getImage(imgPath);
do
{
if (img.isFailed())
{
throw new IOException("Could not load image: " + getURL());
}
else if (!img.hasData())
{
long now = System.currentTimeMillis();
if (now - start > timeout)
{
throw new SocketTimeoutException();
}
Thread.sleep(100);
}
} while (!img.hasData());
data = img.getData();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
public OutputStream getOutputStream() throws IOException
{
// this might be unnecessary - the whole method can probably be omitted for our purposes
return new ByteArrayOutputStream();
}
public java.security.Permission getPermission() throws IOException
{
return null; // we need no permissions to access this URL
}
}
Some parts of MyURLConnection might not be necessary for it to work, but like this it works for me.
Usage in JavaFX WebView:
<img src="myapp:///pics/image.png"/>
Note about permissions:
I used an applet with AllPermissions for my test with the above code.
In a Sandbox-Applet this won't work, as the setFactory permission is missing.
This is not directly related to the question asked, but might make the question itself obsolete.
With Java SE 6 Update 10 Java Applets support to access resources on any domain and port which is correctly set up with a crossdomain.xml.
With this the reason to register your own protocol might become obsolete, as you can access all resources that you need.
Another idea is: If you are trying to create a kind of network sniffer, why not directly use a network sniffer/analyzer program designed for such a task?
By activating Logging and Tracing in the Java Control-Panel your Java-Console will print all attempts and executed network calls including those from the WebView.
You can see all HTTP & HTTPS calls and their return-code + cookie data.
You might also see other protocol connections, but probably not any data sent over them.
This applies to Applets in a Browser.
If you need this in a different context maybe there is a way to activate the same options by passing command line parameters.

Resources