in Coding, Java, StackOverflow

A Windows SSO (for Java on client and server)

A couple of months ago I worked on a single sign-on (SSO) for a Windows client and server made in Java. The scenario was the following:

  • A client made with Java running on Windows
  • A server made with Java running on Windows
  • Both where logged-in to the same domain (an Active Directory LDAP)

The question was, how the server could get the identity (the name of the Windows account) of the client and – of course – how it could trust this information. But if the client would just send a name (e.g. from Java’s System.getProperty("user.name"); method), the client could send anything.

The solution for this dilemma (trust what the client sends to you) is to use a (so called) trusted third party. A trusted third party is an instance which both, client and server, know and trust. The client authenticates itself to this party and the server can verify requests against it. In the scenario above, the domain of the company (an Active Directory LDAP) is the trusted third party. Each client identifies itself against this domain when it logs-in to Windows. Its Windows username and password are checked by the domain/LDAP. On the other side, the server has also access to the domain controller and can verify information send by the client.

The nice thing about this is, that the Windows domain is already configured on nearly every machine which stands in a company. Every company, bigger than maybe five people, will have a Windows domain to log-in. Therefor, a SSO based on the Windows domain will work right out of the box in most cases and we don’t need and configuration in our Java code, since it is already configured in Windows.

Java Native Access (JNA)

To use Windows and the domain controller for authentication, we can use native Windows APIs. To call those APIs in Java, we can use the Java Native Access (JNA) library, which you can find on GitHub at https://github.com/twall/jna and on Maven central:

For example, to get all user groups of the current user, you would do:

Waffle

On top of JNA exists a library called Waffle which encapsulates all functionality you need to implement user authentication. You can find it on GitHub at https://github.com/dblock/waffle and also on Maven central:

You can use Waffle to create a token on the client, send it to the server (e.g. over HTTP or whatever) and to validate that token on the server. At the end of this process (create, send and validate) you will know on the server who the client is – for sure!

Here is an example of how to identify a client on the server. Note that this piece of code is executed completely on one machine. However, you could easily split it into two parts, one on the client and one on the server. The only thing you would need to do, is to exchange the byte[] tokens between client and server. I commented the appropriate lines of code.

(By the way, I asked this myself on Stackoverflow some times ago).

The only thing that is a little bit complicated with that solution is, that you need to do a small handshake between client and server. The client will send a token to the server, which will response with another token, which the client needs to answer again to get the final “you are authenticated” token from the server. To do this, you need to hold some state on the server for the duration of the handshake. Since the handshake is done in a second or two, I just used a limited cache from Google’s Guava library to hold maybe 100 client contexts on the server.

The exchanged tokens are validated against the underlying Windows and its domain.

Best regards,
Thomas

  • Gusti Arya

    Hi, could you help me, i trying to get current client windows username, i add below code

    clientContext.getIdentity().getFqn()

    but then i get this error.

    com.sun.jna.platform.win32.Win32Exception: The security context does not allow impersonation of the client

    pls help

  • Hi Gusti Arya! You get the identity of the client from the ServerContext as shown in the example above. You will have the Windows user name in this object.

    Best regards, Thomas.

  • Mike Azazel

    im doing a jsf webapp and this is bringing me the user the server app runs on the server, not client… any ideas?

    • This approach will only work with a Java client (and server), not with a web application. As far as I know, there is no way to use Windows authentication
      in the browser to login on a website. If you are looking for a web SSO, you must go with something like OAuth or SAML.

      • Mike Azazel

        nah i got it to work with waffle
        user gets logged on at startpage, automatically
        i cast a httprequest element on my bean that gives me to logged on user

        • David K.

          I have the same problem, could you give me a brief explanaition how you did this?

        • vignesh kumar

          hi mike,

          am also trying same process .in server side ,i need to get the client user name ,whose accessing my server application,am facing so many issue ,could you please help me to resolve the issue ,Could you please post the code which you used to get the result.it’s highly appreciate.

          • Mike Azazel

            hi vignesh,

            this is the relevant code from the login method in my session bean:

            // get logged in users name from http request
            HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
            String username = request.getUserPrincipal().getName();
            // extract the login name from the string received in the http servlet request
            username = username.substring(username.indexOf(“\”) + 1, username.indexOf(“\”) + 4);
            logger.info(username);

            please note that this login method is annotated with postconstruct.

            then, in my web.xml, i have the following:

            SecurityFilter
            waffle.servlet.NegotiateSecurityFilter

            principalFormat
            fqn

            SecurityFilter
            /*

            i hope that this helps you.

          • vignesh kumar

            HI mike,

            Could you please tell, which waffle configuration you have been used .because so many configuration is there. like waffle-jna,waffle-jetty,waffle-tomcat 6,7,8,9 ,waffle-distro some more configuration also is there.

          • Mike Azazel

            hi vignesh
            what parts you need is defined by where your webapp runs on and if you use spring in your application and so on

            here are the dependencys that i needed to run it inside a tomcat8 with spring

            com.github.dblock.waffle
            waffle-tomcat8
            1.8.0

            com.github.dblock.waffle
            waffle-jna
            1.8.0

            com.github.dblock.waffle
            waffle-spring-security3
            1.8.0

            as for the token, you wouldnt need one working with this approach, at least i dont have any, the login method is all you need to get the username

            waffle is supposed to handle all this token stuff itself, i never had to do anything, when accessing the page i get a message from waffle st like ‘user xy has been logged in’

          • vignesh kumar

            HI mike ,

            Am using struts web application with tomcat 7. which dependency has been used in your application everything am using as jar file. because in struts we can not use dependency like spring. token is coming null,it’s not generating by default ,i have generated byte array token manually. but it’s getting failed in SecurityContext class .it’s returing value as “o”

          • vignesh kumar

            Hi mike ,

            Am trying above method in spring 3.0 and tomcat 6 ,am getting error in log “The token supplied to the function is invalid” .the token is start with TlRMTVNTUAACAAAADAA, Any idea. i have one more doubt ,while running application ,am getting authentication required window ,which username and password ,i have to give ,whether tomcat username/password or windows username/password. i have tried both ,am getting error as invalid token, kindly help me.this.

          • Mike Azazel

            Hi vignesh,

            the code posted above shows how i implemented it. its been in a productive environment for about half a year and works flawlessly.

            i havent wored with struts before, i guess you kinda have to figure that part out yourself.

          • vignesh kumar

            Hi mike ,
            Ya sure mike ,Now am working on spring based application ,i will figure it out .thank you

          • Mike Azazel

            you should be able to use my code as a drop in solution when working on a spring application
            let me know if you got it to work

          • vignesh kumar

            com.sun.jna.platform.win32.Win32Exception: The token supplied to the function is invalid am getting this exception ,i have generated the Token as a byte array

          • Santiago Barandiaran

            Hi Mike, I´m trying to do the same as you and been stuck for a few days..
            Now I´m trying with Waffle which seems to be very easy for getting the logged in Windows user of the user who´s making the request to my webApp but I´m receiving “NT AUTHORITYANONYMOUS LOGON” as principal.
            As I can see, you are requesting the principal from this request Object:

            FacesContext.getCurrentInstance().getExternalContext().getRequest();

            What´s the dependency of this “FacesContext” because I imported these two:

            compile “com.sun.faces:jsf-api:2.2.9”
            compile “com.sun.faces:jsf-impl:2.2.9”

            but I´m getting NullPointerException on FacesContext.getCurrentInstance().

            Please, any help would be REALLY appreciatted.
            Thank you very much.

            Best regards.

            Santiago.

          • Mike Azazel

            Hi Santiago,

            its javax.faces.context.FacesContext and it resides inside jsf-api (in my case 2.2.13)

            those are my jsf dependencies:

            com.sun.faces
            jsf-api
            2.2.13

            com.sun.faces
            jsf-impl
            2.2.13

            I hope this information is helpful to you.
            Greetings
            Mike

          • Santiago Barandiaran

            Thank you for your response.
            I realized that I have to use that if it´s a jsf application, that´s not my case.
            The problem that I have is the following:

            I have a tomcat deployed on my PC, this server is on my Companies domain. When I make a request to the login page through my pc, I receive the principal name without the need of entering my credentials on the prompt… if I make a request from another computer, the prompt for entering the credentials appear.
            Do you know if it´s possible with Waffle to receive the Windows user without the need of entering the credentials??

            Thank you very much for your help, It´s been a week since I started with this and I don´t know how to continue.

          • Mike Azazel

            hi santiago

            what ur experiencing is basic authentication. for single sign on (no prompts), waffle uses the ntlm protocol.

            you need to have a negotiatesecurityfilter, so ntlm is used if supported by browser, and basic authentication when its not.

            therefor, i have the following in my web.xml :

            SecurityFilter
            waffle.servlet.NegotiateSecurityFilter

            principalFormat
            fqn

            SecurityFilter
            /*

            btw, in firefox u need to add the application server to
            ‘about:config’, ‘network.automatic-ntlm-auth.trusted-uris’

            hope that helps.

          • Santiago Barandiaran

            Thank you for your help Mike, That´s the only configuration you have to get it working??

            Did you have to create a servicePrincipalName, keytab, or something like that??

          • Mike Azazel

            yeah, if you work with a jsf-webapp its all thats needed.

            appearently you dont use jsf, im afraid theres no way around tinkering around with it a bit^^ there might be some useful on that info in the main article above

            good luck

      • Armand Welsh

        On the server, you can use SPNEGO filter, to wrap any component of your website inside a Kerberos authenticated session. If you provide a mechanism to prompt for username/password, then you can even fallback to ntlm if the Kerberos authentication fails. Then IE, Chrome, and Firefox all will pass you credentials (transparently) to the server, provided the browser has trusted authentications enabled, and the server is in the local security policy for the browser.