-1

I have an certificate based authentication:

import java.security.cert.X509Certificate;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;
import javax.inject.Named;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;

@Named
public class UsernameService {

    private static final String MISSING_NAME = null;

    private static final Logger LOG = Logger.getLogger(UsernameService.class.getCanonicalName());

    @Inject
    private final Optional<X509Certificate> cert = null;

    public String currentRequestUsername() {
        if (cert.isPresent()) {
            try {
                LOG.finest("Check Certificate: " + cert); // <- exception!
                for (Rdn rdn : new LdapName(cert.get().getSubjectDN().getName()).getRdns()) {
                    LOG.finest("Check RDN entry for Common name(cn): " + rdn);
                    if (rdn.getType().equals("CN")) {
                        LOG.finer("Found Username: " + rdn.getValue().toString());
                        return rdn.getValue().toString();
                    }
                }
            } catch (InvalidNameException e) {
                LOG.log(Level.CONFIG, "Parse DName impossible: " + cert.get().getSubjectDN().getName() + ".", e);
            } catch (NullPointerException e) {
                LOG.log(Level.FINE, e.getMessage(), e);
            }
        }
        return MISSING_NAME;
    }
}

As you can see in the code above, if a value is "present" print the optional to the logger. What does it mean present? Are null values valid values? The answer is in the javadoc:

If a value is present in this Optional, returns the value,otherwise throws NoSuchElementException.

Ok, so returing values are "present". Is null a possible returning value? We read more:

Returns: the non-null value held by this Optional

So no, null values are not identified as present.

The App-Config does the request-scoped certificate bean:

@Bean
@Autowired
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public X509Certificate getCertificate(HttpServletRequest r) {
    X509Certificate[] certs = (X509Certificate[]) r.getAttribute("javax.servlet.request.X509Certificate");
    if (certs == null || certs.length == 0) {
        return null;
    }
    return certs[0];
}

But I get an exception in the console:

org.springframework.aop.AopInvocationException: AOP configuration seems to be invalid: tried calling method [public abstract java.lang.String java.security.cert.Certificate.toString()] on target [null]; nested exception is java.lang.IllegalArgumentException: object is not an instance of declaring class
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:752)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:136)
        at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
        at $java.security.cert.X509Certificate$$EnhancerBySpringCGLIB$$96797f0e.toString(<generated>)
        at java.util.Formatter$FormatSpecifier.printString(Formatter.java:2886)
        at java.util.Formatter$FormatSpecifier.print(Formatter.java:2763)
        at java.util.Formatter.format(Formatter.java:2520)
        at java.util.Formatter.format(Formatter.java:2455)
        at java.lang.String.format(String.java:2940)
        at java.util.Optional.toString(Optional.java:346)
        at java.lang.String.valueOf(String.java:2994)
        at java.lang.StringBuilder.append(StringBuilder.java:131)
        at XXX.UsernameService.currentRequestUsername(UsernameService.java:27)
...
Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

Any idea?

Nikos Paraskevopoulos
  • 39,514
  • 12
  • 85
  • 90
Grim
  • 1,938
  • 10
  • 56
  • 123
  • 1
    `Optional cert = null` seems wrong – Lino Jun 04 '19 at 07:40
  • @Lino Why? 5-characters-more-to-write – Grim Jun 04 '19 at 07:42
  • 6
    Don’t use `Optional` for a field. Use it for a return value, that’s what it’s for. And *never ever* allow an `Optional` to be null. You’re creating exactly the problem that `Optional` was supposed to help you solve. – Ole V.V. Jun 04 '19 at 07:44
  • 1
    Your `Optional` clearly holds a (non-null) cert. A guess would be that the cert object is somehow invalid. Maybe it contains a null somewhere where it shouldn’t? – Ole V.V. Jun 04 '19 at 07:45
  • 1
    How can you inject `X509Certificate` bean to `Optional cert`? Also, it's declared as final and initialized to null? – Madhu Bhat Jun 04 '19 at 07:46
  • @OleV.V. `And never ever allow an Optional to be null`, UsernameService is a CDI-Bean, so `cert` never ever is `null`. – Grim Jun 04 '19 at 07:48
  • @OleV.V. I understand that an optional field is bad practice. In https://stackoverflow.com/questions/19485878 there is an (good) working example. – Grim Jun 04 '19 at 07:50
  • @MadhuBhat I have an `AppConfig` who creates the CDI-Bean out from the certificate. Spring calls the standard-constructor of `UsernameService`, uses reflection to 1. remove the final and 2. inject the value. How the workflow is in detail you have to ask developers of the Spring-CDI. – Grim Jun 04 '19 at 07:53
  • If you really want to do that, you probably have to make `getCertificate()` return an `Optional` as well. – Jai Jun 04 '19 at 07:56
  • Instead of `cert` in the log, can you try having `cert.get().getSubjectDN().getName()` ? – Madhu Bhat Jun 04 '19 at 07:58
  • With this error `org.springframework.aop.AopInvocationException: AOP configuration seems to be invalid: tried calling method [public abstract java.lang.String java.security.cert.Certificate.toString()] on target [null]; nested exception is java.lang.IllegalArgumentException: object is not an instance of declaring class ` doesn't it look like the `cert` object has a null? – Madhu Bhat Jun 04 '19 at 08:02
  • @MadhuBhat I could but I like to print the certificate, not the subjectDN's name. – Grim Jun 04 '19 at 08:03
  • @PeterRader that was to check if the optional object really has the certificate data within. – Madhu Bhat Jun 04 '19 at 08:04

1 Answers1

1

Now I created a breakpoint in the constructor of the IllegalArgumentException. Then I steped back 2 traces and inspect the CDI invocation.

Debug point in IAE constructor

I see an NullBean having an instance of org.springframework.beans.factory.support.NullBean.

So the optional seems not to be null but the bean is a null bean.

My solution is to simply catch the Exception that a NullBean is not a Certificate.

import java.security.cert.X509Certificate;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;
import javax.inject.Named;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;

@Named
public class UsernameService {

    private static final String MISSING_NAME = null;

    private static final Logger LOG = Logger.getLogger(UsernameService.class.getCanonicalName());

    @Inject
    private final Optional<X509Certificate> cert = null;

    public String currentRequestUsername() {
        if (cert.isPresent()) {
            try {
                LOG.finest("Check Certificate: " + cert);
                for (Rdn rdn : new LdapName(cert.get().getSubjectDN().getName()).getRdns()) {
                    LOG.finest("Check RDN entry for Common name(cn): " + rdn);
                    if (rdn.getType().equals("CN")) {
                        LOG.finer("Found Username: " + rdn.getValue().toString());
                        return rdn.getValue().toString();
                    }
                }
            } catch (InvalidNameException e) {
                LOG.log(Level.CONFIG, "Parse DName impossible: " + cert.get().getSubjectDN().getName() + ".", e);
            } catch (NullPointerException e) {
                LOG.log(Level.FINE, e.getMessage(), e);
            } catch (RuntimeException e) {
                LOG.log(Level.FINE, e.getMessage(), e);
            }
        }
        return MISSING_NAME;
    }
}
Grim
  • 1,938
  • 10
  • 56
  • 123