Today we will see how can we integrate Jira with our corp SSO.
And SSOnCookie class which retrieves cookie values set by Corp SSO. Usually SSO will set authenticated user information as either Domain Cookie or will return to redirected call as encrypted token, what ever be the case we need to read cookie information set by SSO
Code here:
<authenticator class="com.inmobi.pratik.sso.CustomSSOAuthenticator"/>
JIRA integrate with SSO system Seraph, the Atlassian authentication library. Seraph is a very simple, pluggable J2EE web application security framework developed by Atlassian and used in our products.
Seraph allows you to write custom authenticators which will accept the login credentials of your existing single sign-on system.
So on a very high level, we need to write a custom Authenticator class which should extend DefaultAuthenticator. And then we need to override login() to check if user has a valid Jira session or is authenticated by our corp SSO.
Next how do we do that?? Here are step by step details
- Create a jira plugin project:
Although this is not mandatory but I prefer this way, by creating plugin project we have access to all Atlassian libraries.
- Create CustomSSOAuthenticator class which will handle login authentication of Jira:
CustomSSOAuthenticator class will extend com.atlassian.seraph.auth.DefaultAuthenticator abstract class and thus have to implement 2 methods: a) Principal getUser(String username) and b) boolean authenticate(Principal user, String password)
Apart from this we also have to override Principal getUser(HttpServletRequest request,
HttpServletResponse response) method which gets called for every authentication
Now some code:
package com.pratik.jira.sso;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.atlassian.crowd.embedded.api.CrowdService;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.seraph.auth.AuthenticatorException;
import com.atlassian.seraph.auth.DefaultAuthenticator;
public class CustomSSOAuthenticator extends DefaultAuthenticator {
//For Logging purpose
private final static Log log = LogFactory
.getLog(CustomSSOAuthenticator.class);
// Randomly generated serial id
private static final long serialVersionUID = -7673520003580149993L;
@Override
protected boolean authenticate(Principal user, String password)
throws AuthenticatorException {
// This method probably never gets called for SSO use case
log.info("Inside custom authenticate. User is " + user + "Password is "
+ password);
return true;
}
@Override
protected Principal getUser(String username) {
// Validating username
log.info("Inside custom getUser. User is " + username);
return getCrowdService().getUser(username);
}
private CrowdService getCrowdService() {
return ComponentAccessor.getCrowdService();
}
public Principal getUser(HttpServletRequest request,
HttpServletResponse response) {
log.info("Inside getUser of CustomSSOAuthenticator");
Principal user = null;
try {
//Check if user is already logged in and has a valid session
if (request.getSession() != null
&& request.getSession().getAttribute(
DefaultAuthenticator.LOGGED_IN_KEY) != null) {
log.info("Session found; user already logged in");
user = (Principal) request.getSession().getAttribute(
DefaultAuthenticator.LOGGED_IN_KEY);
}
else {
//Get Cookie from SSO (Will write SSOnCookie class in a while)
SSOnCookie ssoCookie = SSOnCookie.getSSOCookie(request);
log.info("Got SSOnCookie " + ssoCookie);
//Check if it is a valid cookie
if (ssoCookie != null && !ssoCookie.isExpired()) {
log.info("Trying seamless Single Sign-on...");
String username = ssoCookie.getLoginId();
log.info("Got username " + username);
if (username != null) {
user = getUser(username);
log.info("Logged in via SSO, with User " + user);
request.getSession().setAttribute(
DefaultAuthenticator.LOGGED_IN_KEY, user);
request.getSession().setAttribute(
DefaultAuthenticator.LOGGED_OUT_KEY, null);
}
}
else {
log.warn("SSOCookie is null; redirecting");
// user was not found, or not currently valid
return null;
}
}
} catch (Exception e) // catch class cast exceptions
{
log.error("Exception: " + e, e);
}
return user;
}
}
And SSOnCookie class which retrieves cookie values set by Corp SSO. Usually SSO will set authenticated user information as either Domain Cookie or will return to redirected call as encrypted token, what ever be the case we need to read cookie information set by SSO
Code here:
package com.pratik.jira.sso;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class SSOnCookie {
//For logging
private final static Log log = LogFactory.getLog(SSOnCookie.class);
//Variable to hold if cookie is expired
boolean isExpired;
//Login ID of user
String loginId;
public SSOnCookie(String loginId,boolean isExpired){
this.loginId=loginId;
this.isExpired=isExpired;
}
public static SSOnCookie getSSOCookie(HttpServletRequest request)
{
try{
//From request object get cookie details and from cookie get username/email
//Jira takes username and not email for authentication
log.debug("Username----"+userName);
return new SSOnCookie(userName, false);
}
catch(Exception e){
log.error("Exception -- "+e);
return null;
}
public void setExpired(boolean isExpired) {
this.isExpired = isExpired;
}
public void setLoginId(String loginId) {
this.loginId = loginId;
}
public boolean isExpired()
{
return this.isExpired;
}
/** Return the username implied by the cookie in the request. */
public String getLoginId()
{
return this.loginId;
}
@Override
public String toString() {
return "SSOnCookie [isExpired=" + isExpired + ", loginId=" + loginId
+ "]";
}
}
- Build/Deploy project. This will create a jar file. Copy jar file to <Jira Installation Dir>/atlassian-jira/WEB-INF/lib
- Change seraph-config.xml
seraph-config.xml file is located under - <Jira Installation Dir>/atlassian-jira/WEB-INF/classes
Make following changes in file:
<param-name>login.url</param-name>
<param-value>Your SSO URL</param-value>
<param-name>link.login.url</param-name>
<param-value>Your SSO URL</param-value>
<authenticator class="com.inmobi.pratik.sso.CustomSSOAuthenticator"/>
If you have to implement logout from SSO as well just change logout.url in seraph-config.xml
And then restart your Jira Server... Thats All!!! SSO is now configured with Jira
Some other important points:
- If you want all login to happen via SSO only then you will have to disable login gadget in jpm.xml (<Jira Installation Dir>/atlassian-jira/WEB-INF/classes/jpm.xml):
<key>jira.disable.login.gadget</key>
<default-value>true</default-value>
<type>boolean</type>
<admin-editable>false</admin-editable>
<sysadmin-editable>false</sysadmin-editable>
</property>- In Jira if user directly opens login url which is <jira.corp.abc.com/login.jsp> then SSO does not work and it will display Jira login page to user which will block login via SSO. To stop this we need to put a small hack in login.jsp. In login.jsp just before <html> tag put a check to see if user is logged in. If user is logged in do nothing else redirect user to your SSO page. Something like this:
if (request.getSession() != null
&& request.getSession().getAttribute(
DefaultAuthenticator.LOGGED_IN_KEY) != null) {
//Do nothing
}
else{
response.sendRedirect(redirectURL);
}
Thats all. Do let me know if you have any comments and Happy Coding!!