Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Monday, April 18, 2016

Kerberos with groups on Tomcat 7

To provide Single-SignOn (SSO) to your web service on a linux server in a windows environment (Active Directory) you can add a login filter to tomcat to accept Kerberos tokens. It's really quite simple, if you set it up correctly, which is not simple to figure out.

Ack: Compiled from http://portlandlanguagecraft.com/ and https://pixabay.com/en/chain-gold-power-connection-rights-307886/

I did it using a custom SPNEGO filter to also extract AD groups from the kerberos tokens.

Mostly follow https://tomcat.apache.org/tomcat-7.0-doc/windows-auth-howto.html but with many additional tweaks.

You will need to create/edit the following files (On Ubuntu):

/etc/tomcat7/web.xml
/etc/tomcat7/login.conf
/etc/tomcat7/krb5.conf
/etc/tomcat7/mykeytab.keytab
/usr/share/tomcat7/libs/spnego-r7.jar

(These files are also found in /var/lib/tomcat7/conf).

Step-by-step (First section just for login and second section for getting groups too):

AUTHENTICATION (Login):


Download the Spnego HTTP filter:
Available from https://sourceforge.net/projects/spnego/. Put the file under /usr/share/tomcat7/libs/spnego-r7.jar to make tomcat load it on startup.

On the Active Directory (AD) / Kerberos Key Distribution Center (KDC) / Windows server:

Add service user:
Add a service user to let you linux server "log in" and validate kerberos tokens.

Link SPN to service user:
SPN (Service Principal Names) are identifiers for users or hosts. We need to add the ones representing our server. NOTE: The SPN is case sensitive and you must use the same case everywhere. The command is on the form:
setspn.exe -A HTTP/<HOSTNAME> DOMAIN\<SERVICE USER>

So on your windows/AD server enter the following

setspn.exe -A HTTP/myserver DOMAIN\myserviceuser
setspn.exe -A HTTP/myserver.domain.local DOMAIN\myserviceuser

to link the SPN HTTP/myserver to the user myserviceuser.

Generate keytab:
The keytab is file which stores SPNs/usernames and password for them.

According to the apache tutorial you should do this on your windos/AD server using the ktpass tool. I found it was better to use the ktab.exe that comes with java on windows:
ktab -a HTTP/<HOSTNAME> <SERVICE USER PASSWORD> -k <OUTPUT FILE> -n 0
e.g.
ktab -a HTTP/myserver.domain.local myservicepassword -k mykeytab.keytab -n 0

Note the n 0 flag which sets key version number, it needs to be 0 for tomcat/Spnego to find the key.

Put the mykeytab.keytab file under /etc/tomcat7/mykeytab.keytab on your linux server.


We also need to make the computers in the windows network trust our server, to do this we can use Group Policy on the AD server. But we'll get back to that later.


On the linux server (as sudo):

Add a filter to /etc/tomcat7/web.xml:

<!-- ======================== SPNEGO filter ==============================-->
  <filter>
    <filter-name>SpnegoHttpFilter</filter-name>
    <filter-class>net.sourceforge.spnego.SpnegoHttpFilter</filter-class>

    <init-param>
        <param-name>spnego.allow.basic</param-name>
        <param-value>true</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.allow.localhost</param-name>
        <param-value>false</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.allow.unsecure.basic</param-name>
        <param-value>true</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.login.client.module</param-name>
        <param-value>com.sun.security.jgss.krb5.initiate</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.krb5.conf</param-name>
        <param-value>conf/krb5.conf</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.login.conf</param-name>
        <param-value>conf/login.conf</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.preauth.username</param-name>
        <param-value>SERVICE_USER_USERNAME</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.preauth.password</param-name>
        <param-value>SERVICE_USER_PASSWORD</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.login.server.module</param-name>
        <param-value>com.sun.security.jgss.krb5.accept</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.prompt.ntlm</param-name>
        <param-value>true</param-value>
    </init-param>
  
    <init-param>
        <param-name>spnego.logger.level</param-name>
        <param-value>1</param-value>
    </init-param>
</filter>


You need to replace SERVICE_USER_NAME and SERVICE_USER_PASSWORD with the ones you use to create your keytab. spnego.allow.basic, spnego.prompt.ntlm are true to let users who haven't logged into windows to log in (WARNING: username and password are sent in cleartext/base64 to the linux server!). spnego.allow.unsecure.basic needs to be true if you don't use https, which you should do.

  <filter-mapping>
    <filter-name>SpnegoHttpFilter</filter-name>
    <url-pattern>*</url-pattern>
  </filter-mapping>


To apply filter to all files.


Create/edit the login.conf:

com.sun.security.jgss.krb5.initiate {
    com.sun.security.auth.module.Krb5LoginModule required;
};

com.sun.security.jgss.krb5.accept {
    com.sun.security.auth.module.Krb5LoginModule required
    doNotPrompt=true
    useKeyTab=true
    principal="HTTP/myserver.domain.local@DOMAIN.LOCAL"
    keyTab="/var/lib/tomcat7/conf/mykeytab.keytab"
    storeKey=true
    isInitiator=false;
};


Differently from the apache tutorial, the initate object should only contain the module, otherwise tomcat will throw a parse error on startup.


Create/edit the krb5.conf:

[libdefaults]
default_realm = DOMAIN.LOCAL
default_keytab_name = FILE:/etc/tomcat7/mykeytab.keytab
default_tkt_enctypes = rc4-hmac,aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96
default_tgs_enctypes = rc4-hmac,aes256-cts-hmac-sha1-96,aes128-cts-hmac-sha1-96
forwardable = true

[realms]
DOMAIN.LOCAL = {
        kdc = 192.168.1.5:88
}

[domain_realm]
domain.local = DOMAIN.LOCAL
.domain.local = DOMAIN.LOCAL

[login]
        krb4_convert = true
        krb4_get_tickets = false


The kdc parameter should support the hostname of the KDC/AD server e.g. kdc.domain.local, but mine had trouble with DNS lookup for it, luckily IP works fine.

NOTE: Make sure time is within a few minutes of the AD server, consider installing a NTP client to keep in sync.

AUTHORIZATION (roles/groups):


You will need to create/edit the following files (On Ubuntu):

/var/lib/tomcat7/webapps/ROOT/WEB-INF/web.xml
/usr/share/tomcat7/libs/bcprov-jdk15on-147.jar
/usr/share/tomcat7/libs/spnego-pac.jar

Active Directory adds a blob to their kerberos tokens called PAC (Privilege Attribute Certificate), which includes a users roles. We can extract these roles from our ticket so we don't have to do an additional LDAP request (which is the normal way).

To do this we need a custom build of the spnego library by Ricardo Martín Camarero (rickyepoderi) (see http://blogs.nologin.es/rickyepoderi/index.php?/archives/73-SPNEGOKerberos-in-JavaEE-PAC.html) which utilizes JaasLounge and Bouncy Castle ASN1 to extract the PAC roles.
I've added support for fetching a users kerberos token when using Basic Auth as well as adding compressed PAC from another library.

The spnego-pac source code is available from github (https://github.com/asmund1/spnego-pac), and the final binaries used in this project from https://github.com/asmund1/spnego-pac/blob/master/jars/spnego-pac.jar and https://github.com/asmund1/spnego-pac/blob/master/jars/bcprov-jdk15on-147.jar (additional library needed).

On the linux server (as sudo):

Copy spnego-pac.jar and bcprov-jdk15on-147.jar to /usr/share/tomcat7/libs/ so that tomcat loads it on startup. NOTE: Remove the original spnego jar if you have it there already.

The PAC contains only the numerical representation for each role for the user, you can use this directly in your servlets, but I added some aliases for my roles. I did this in the webapp web.xml, but it should work in the global web.xml too (/etc/tomcat7/web.xml):

    <context-param>
        <param-name>myserver_write_role</param-name>
        <param-value>S-1-5-21-123456789-1234567890-1234567890-1234</param-value>
        <description>Alias for write access role</description>
    </context-param>


To get the value for your roles, check your tomcat log (/var/lib/tomcat7/logs/catalina.out) after login using kerberos, the library prints the SIDs found for a user.
You might have to change the log level since they are printed at FINER level. Do this by appending
net.sourceforge.spnego.SpnegoAuthenticator = FINER
to the bottom of /etc/tomcat/logging.properties file and restarting tomcat.

GET USER AND ROLES (CODE):


The username/SPN of the logged in user and his/her roles are added to the request object, so to fetch them in Java servlets / JSP use the following lines of code:

For Java Servlet:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletContext;

public class MyServlet extends HttpServlet {

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {

        // Fetches username of logged in user
        req.getRemoteUser()

         // Check if user has write access
        ServletContext context = req.getServletContext();
        if (!req.isUserInRole(context.getInitParameter("myserver_write_role"))) {
            resp.sendError(resp.SC_FORBIDDEN);
            return;
        }
    }
}


For JSP:
<%= request.getRemoteUser() %>
and
<%= request.isUserInRole(request.getServletContext().getInitParameter("myserver_write_role")) %>

TRUSTED SERVER:


To get the kerberos token from windows you need to be on the trusted server list. Chrome and IE use a common list while firefox and other browsers have their own.

To add you server as trusted in IE (and Chrome) open Internet Options -> Security -> Local intranet -> Sites button -> Advanced button
Enter the url for your server and press the Add button.

To do the same for firefox do the following:
1. Open Firefox, and type "about:config" in the Address Bar.
2. In the Search field, type "negotiate".
3. Set the following fields:
      network.negotiate-auth.trusted-uris  myserver.domain.local
      network.negotiate-auth.delegation-uris myserver.domain.local

(https://bugzilla.mozilla.org/show_bug.cgi?id=520668)

To add  your server for IE and Chrome for all windows machines in the intranet, you can use Group Policy: https://www.serverknowledge.net/group-policy/adding-trusted-sites-internet-explorer-using-group-policy-gpo/

TEST:


On the linux server (as sudo):
Start/Restart tomcat server to load changes in config and load jars:
service tomcat7 restart

Then tail the log output for any errors:
tail -f /var/lib/tomcat7/logs/catalina.out

Then navigate to your server in IE/Chrome. You should not be prompted for username or password. If you go to the server from a non-windows logged on computer (e.g. mobile phone) you should get a popup asking you to enter username and password. If you enter the incorrect password you should get a white page, with correct credentials you should see your content.

Sources:

http://spnego.sourceforge.net/spnego_tomcat.html
http://spnego.sourceforge.net/pre_flight.html
http://spnego.sourceforge.net/reference_docs.html
http://spnego.sourceforge.net/client_keytab.html
http://spnego.sourceforge.net/ExampleSpnegoAuthenticatorValve.java
http://spnego.sourceforge.net/HelloKeytab.java
https://sourceforge.net/p/spnego/discussion/1003769/thread/98e5ea01/

http://jaaslounge.sourceforge.net/howto/SSO_Tomcat_Howto.pdf
http://www.oracle.com/technetwork/articles/idm/weblogic-sso-kerberos-1619890.html
http://stackoverflow.com/questions/20152000/get-ad-groups-with-kerberos-ticket-in-java
https://tomcat.apache.org/tomcat-7.0-doc/realm-howto.html#JNDIRealm
http://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/lab/part6.html
http://kerberos.996246.n3.nabble.com/kinit-Cannot-contact-any-KDC-for-realm-EXAMPLE-COM-while-getting-initial-credentials-td19145.html
http://serverfault.com/questions/166768/kinit-wont-connect-to-a-domain-server-realm-not-local-to-kdc-while-getting-in
http://stackoverflow.com/questions/31877027/kerberos-cannot-find-key-of-appropriate-type-to-decrypt-ap-rep-rc4-with-hmac

https://docs.google.com/document/d/1G7WAaYEKMzj16PTHT_cIYuKXJG6bBcrQ7QQBQ6ihOcQ/edit#heading=h.yh8m8tkjdx9h
http://stackoverflow.com/questions/3568635/android-authenticating-with-kerberos

http://stackoverflow.com/questions/2518256/override-intranet-compatibility-mode-ie8

Friday, February 13, 2015

Mockito for Play 2 Framework

Mocking is great! :)
To use Mockito to mock stuff for your tests in Play 2 Framework do the following:

1. Add mockito as dependency in Build.scala:
val appDependencies = Seq(
    ...,
    "org.mockito" % "mockito-all" % "1.10.19"
)
Find latest version from https://github.com/mockito/mockito/blob/master/doc/release-notes/official.md

2. Add imports to your JUnit java file:
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.mockito.Mockito.*;

Note the static import of Mockito. This lets you call mock and when without "Mockito." prefix, like most of the tutorials do.

3. Create mocks:
Either by putting @RunWith(MockitoJUnitRunner.class) on your test class and using @Mock for class members. E.g.:
@RunWith(MockitoJUnitRunner.class)
public class HomeControllerTest {

    @Mock
    HomeForm mockHome;

or by creating the mocks in your code:
HomeForm mockHome = mock(HomeForm.class);

4. Set up mock behavior:
List mockNames = (List) mock(List.class);
when(mockNames.get(0)).thenReturn("bob");
when(mockCompany.getNames()).thenReturn(mockNames);


5. Run using play test, you might want to run play clean first to make sure mockito is downloaded.

Nice feature:
If you've used googlemock - Google C++ Mocking Framework and miss the "Uninteresting function call encountered" messages, for example for debugging, you can get verbose output from mocks by adding withSettings().verboseLogging() like this:
HomeForm mockHome = mock(HomeForm.class, withSettings().verboseLogging());

Sources:
http://www.javacodegeeks.com/2013/05/junit-and-mockito-cooperation.html
http://stackoverflow.com/questions/11802088/how-do-i-enable-mockito-debug-messages
http://www.baeldung.com/mockito-behavior

Wednesday, May 28, 2014

Java Listeners and Adapters, almost anonymous functions

Java allows developers to easily add listeners to different events.
e.g. button.addMouseListener(this);
However, often you don't want to implement a listener interface for your class or add an inner class
MyClass implements MouseListener
MyClass {
    private class ListenerClass implements MouseListener {
            public void mouseClicked(MouseEvent e) {}
            public void mouseEntered(MouseEvent e) {}
            .
    }
}

With all the functions required by the interface cluttering up your code. Often you don't even need more than one or two of the functions.

Enter Adapters.
Adapters are classes which are made to match a listeners interface and only that. They do nothing when called, but the developer will override the function he needs:
button.addMouseListener( new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
    }
}


This adds flexibility to Java, almost like the anonymous functions of JavaScript.

Source: https://blogs.oracle.com/CoreJavaTechTips/entry/listeners_vs_adapters
http://docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html

Friday, December 9, 2011

Java Enum from value

There are different levels of using Enums in Java. From the simplest one:
public enum Color {
WHITE, BLACK, RED, YELLOW, BLUE;
}

to using constructor, values and methods:
public enum Color {
WHITE(21), BLACK(22), RED(23), YELLOW(24), BLUE(25);

private int code;

private Color(int c) {
code = c;
}

public int getCode() {
return code;
}

Note that the constructor can only be private or package default, to 'create' an Enum follow below:

To get an Enum from a value (e.g. after storing the value in DB) you can use valueOf() which looks up an Enum based on the given string. Or use values() to look it up directly in the array of possible values, e.g.:Color.values()[2];

Note: valueOf() may throw IllegalArgumentException so, check for that if you don't want a runtime exception.

Edit: From pk's comment and link below I discovered the correct way to use the valueOf() method is to pass the enum name, not the value. E.g. Color.valueOf("WHITE") would give the Color enum WHITE, instead of Color.valueOf("21") which would not. Thanks. :)

Code and thoughts found at: http://javahowto.blogspot.com/2008/04/java-enum-examples.html
http://stackoverflow.com/questions/2418729/whats-the-best-practice-to-look-up-java-enums
http://www.coderanch.com/t/401264/java/java/cast-int-enum

Java abstract classes

Pretty basic stuff, but nicely explained:

In Java:
- Abstract classes have one or more abstract method.
- Cannot be instantiated, but can:
  - Have a constructor (callable from subclasses using super()).
  - Be referenced (Figure f = new Rectangle()).
- Subclasses of abstract classes (Rectangle extends Figure) must implement the abstract methods or must themselves be abstract.

Read the tutorial: http://www.java-samples.com/showtutorial.php?tutorialid=288

Friday, July 16, 2010

Alternative to Eclipse IDE

So, getting tired of eclipse's infinite number of program/system halting tasks?

Maybe you should try Netbeans, it's not just for Java development. There are also plugins for PHP, Ruby, C/C++, and Groovy (whatever that is).

Check it out at:
http://netbeans.org/

Tuesday, December 15, 2009

Getting the auto increment id from SQL insert in Java

Often you need the id or autoincremented values of a row you have inserted into the database. To get this in Java do the following:
String sql = "INSERT INTO table (column1, column2) values(?, ?)";
stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);


The key here is the Statement.RETURN_GENERATED_KEYS. Now you can get the auto incremented keys by doing the following:
rs= stmt.getGeneratedKeys();
rs.next();
auto_id = rs.getInt(1);


It's also possible to do this with regular statements (not prepareStatements) like this:
stmt = conn.createStatement();
stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);


Solution source: http://stackoverflow.com/questions/1376218/is-ther-a-way-to-retrieve-the-autoincrement-id-from-a-prepared-statement

Thursday, August 20, 2009

Setting up environment for J2ME programming in Ubuntu

To program for JAVA J2ME using Eclipse under Ubuntu following the guides in the following links should help with any problems encountered. This assumes you have already installed Eclipse. (can't remember the exact problems I encountered, but you have to edit a settings file somewhere or making a symlink (Se the ProGuard links))

http://www.autexier.de/jmau/dev/j2me/j2me.html
http://eclipseme.org/docs/refPrefJ2ME.html
http://blogninja.com/doc/libproguard-java/manual/wtk.html
http://dev.eclipse.org/newslists/news.eclipse.dsdp.mtj/msg00507.html