NF11 Single SignOn
Feature: Single Sign-On
Goal: Security
Developer: Logilite Technologies
Goal
iDempiere 11 has added support for single sign-On. Single sign-on on help to get authenticated with third party identity provider like Azure MSAL or AWS cognito server. This make requirement to store credentials of employee in database and make more secure against threat exposing user data.
iDempiere provides services to implement plugin for any SSO providers. The goal of this How-To is to show how to develop plug-in project to support new SSO. For creating blank plugin project, refer guide as Get Your Plugins Running
logilite has developed and made available plugins for MSAL (Azure) and AWS Cognito.
Not implemented yet
The current version of SSO does not implement changes on: - SOAP and REST web services, these keep working with AD_User authentication - SSO provider per tenant is not implemented, this would require a mechanism to identify the tenant on the URL
How to Enable SSO
Deepak Pansheriya has published plugins for Azure and Cognito, you can start using it with these plugins:
You can find instructions about how to configure AWS Cognito and the plugin within iDempiere in the provided file:
AmazonCognitoConfiguration.docx
How to implement SSO
Adding new item in SSO Provider drop down
On SSO configuaraion screen has SSO Provider field which list all SSO provider implementations available as plugin. first step is to add your SSO provider name in this drop down. For same follow below steps.
- If you are adding new identity provider support, first step is to add SSO (identity) Provider Name in SSO Provider List on Reference.
- For same login as System user in system tenant.
- Open Reference window and locate record with name "SSO Provider List"
- Next go to tab list validation and create new entry
- Provide unique search key and name. Search key is used in your factory to identify configured SSO for your implementation
Adding dependencies in your plugin
- open MANIFEST.MF of your plugin project and on dependency tab under required plugins, add following plugin dependencies as shown below
- org.adempiere.base
- org.eclipse.jetty.servlet-api
- org.adempiere.plugin.utils
Implementing SSO Factory
For implementing SSO factory, add new class implementing ISSOPrincipleFactory as below. you should add condition in which case the object of your SSO provider (ISSOPrinciple) implementor should be returned. Mostly we used SSO Provider configured on sso configuration screen.
1import org.adempiere.base.sso.ISSOPrinciple;
2import org.adempiere.base.sso.ISSOPrincipleFactory;
3import org.compiere.model.I_SSO_PrincipleConfig;
4
5import com.logilite.sso.test.principle.MSSOPrinciple;
6
7public class MySSOFactory implements ISSOPrincipleFactory
8{
9 @Override
10 public ISSOPrinciple getSSOPrincipleService(I_SSO_PrincipleConfig config)
11 {
12 if ("MYSSOPROVIDER".equalsIgnoreCase(config.getSSO_Provider()))
13 return new MSSOPrinciple(config);
14 return null;
15 }
16}
Implement SSO Provider
SSO Provider class implements ISSOPrinciple interface.
ISSOPrinciple Method:
hasAuthenticationCode
- Method has request (HttpServletRequest) , response (HttpServletResponse) as a parameter.
- Method will return true, if a request has an authentication code from SSO provider, other wise false.
getAuthenticationToken
- Method has request (HttpServletRequest) , response (HttpServletResponse) and redirectMode (String) as a parameter.
- Method will send a request to SSO provider with the code & redirect URL to get the auth token & and save token in session.
- Token is save in session with attribute name SSOPrinciple.SSO_PRINCIPLE_SESSION_NAME.
isAuthenticated
- Method has request (HttpServletRequest) , response (HttpServletResponse) as a parameter.
- Method will return true, if session has auth token, other wise false.
redirectForAuthentication
- Method has request (HttpServletRequest) , response (HttpServletResponse) and redirectMode (String) as a parameter.
- Method will redirect request to the SSO provider for authentication.
isAccessTokenExpired
- Method has request (HttpServletRequest) , response (HttpServletResponse) as a parameter.
- Method will return true, if auth token is expired, other wise false.
refreshToken
- Method has request (HttpServletRequest) , response (HttpServletResponse) and redirectMode (String) as a parameter.
- Method will send request to SSO Provider to refresh auth token & update token in session.
removePrincipleFromSession
- Method has request (HttpServletRequest) , response (HttpServletResponse) as a parameter.
- Method will remove auth token from session.
getUserName
- Method has result (Object) as a parameter, result is token save in session.
- Method will return user name/email from token, as per System Configurator USE_EMAIL_FOR_LOGIN.
getLanguage
- Method has result (Object) as a parameter, result is token save in session.
- Method will return Login Language from token.
SSOUtils Method:
getRedirectedURL
- Method take redirectMode (String) , config (I_SSO_PrincipleConfig) as a parameter.
- Method will return Redirect URIs (String) from SSO Provider configured (config) as per redirectMode .
- We have Redirect Mode as below.
- SSOUtils.SSO_MODE_OSGI - Apache Felix Web Console Bundles
- SSOUtils.SSO_MODE_MONITOR - iDempiere Monitor
- SSOUtils.SSO_MODE_WEBUI - Web login
1import java.io.IOException;
2import java.text.ParseException;
3
4import javax.servlet.http.HttpServletRequest;
5import javax.servlet.http.HttpServletResponse;
6
7import org.adempiere.base.sso.ISSOPrinciple;
8import org.adempiere.base.sso.SSOUtils;
9import org.compiere.model.I_SSO_PrincipleConfig;
10import org.compiere.model.MSysConfig;
11import org.compiere.util.Language;
12
13/**
14 * in core {@link #BridgeFilter}, {@link #AdempiereMonitorFilter}, {@link #SSOWebuiFilter} filter
15 * use ISSOPrinciple methods to log-in/authenticated users using the SSO provider.
16 */
17public class MSSOPrinciple implements ISSOPrinciple
18{
19
20 private I_SSO_PrincipleConfig config;
21
22 public MSSOPrinciple(I_SSO_PrincipleConfig ssoConfig)
23 {
24 setConfig(ssoConfig);
25 }
26
27 /**
28 * When a request comes to any filter class, a request is checked for
29 * authentication code from the SSO provider.
30 */
31 @Override
32 public boolean hasAuthenticationCode(HttpServletRequest request, HttpServletResponse response)
33 {
34 // Check request has authentication code
35 return false;
36 }
37
38 /**
39 * if {@code #hasAuthenticationCode(HttpServletRequest, HttpServletResponse)} returns true, then
40 * request is sent to the SSO provider with the code & response redirect URL to get the
41 * authenticated user token & and saved token in the session attribute
42 * {@link #ISSOPrinciple.SSO_PRINCIPLE_SESSION_NAME}
43 */
44 @Override
45 public void getAuthenticationToken(HttpServletRequest request, HttpServletResponse response, String redirectMode) throws Throwable
46 {
47 /**
48 * we have three redirectMode
49 * 1. SSOUtils.SSO_MODE_OSGI - Apache Felix Web Console Bundles
50 * 2. SSOUtils.SSO_MODE_MONITOR - iDempiere Monitor
51 * 3. SSOUtils.SSO_MODE_WEBUI - Web login
52 */
53 // get redirect URL for SSO.
54 String sso_Redirect_URL = SSOUtils.getRedirectedURL(redirectMode, getConfig());
55 // SSO authentication token code
56
57 // request.getSession().setAttribute(ISSOPrinciple.SSO_PRINCIPLE_SESSION_NAME, token);
58 }
59
60 /**
61 * if {@code #hasAuthenticationCode(HttpServletRequest, HttpServletResponse)} returns false,
62 * then current session attribute {@link #ISSOPrinciple.SSO_PRINCIPLE_SESSION_NAME}
63 * have a token or not.
64 */
65 @Override
66 public boolean isAuthenticated(HttpServletRequest request, HttpServletResponse response)
67 {
68 // Check request session is authenticated
69 return false;
70 }
71
72 /**
73 * if {@code #isAuthenticated(HttpServletRequest, HttpServletResponse)} returns false,
74 * then the current request is redirected to the SSO provider for login/authentication.
75 */
76 @Override
77 public void redirectForAuthentication(HttpServletRequest request, HttpServletResponse response, String redirectMode) throws IOException
78 {
79 // Code for redirect to SSO provider
80 }
81
82 /**
83 * Check the token stored in a session attribute
84 * {@link #ISSOPrinciple.SSO_PRINCIPLE_SESSION_NAME} is expired?
85 */
86 @Override
87 public boolean isAccessTokenExpired(HttpServletRequest request, HttpServletResponse response)
88 {
89 // check token expired date/time.
90 return false;
91 }
92
93 /**
94 * {@code #isAccessTokenExpired(HttpServletRequest, HttpServletResponse)} return true, then
95 * sent a request to SSO Provider to refresh the current token
96 */
97 @Override
98 public void refreshToken(HttpServletRequest request, HttpServletResponse response, String redirectMode) throws Throwable
99 {
100 // Code for a token refresh
101 }
102
103 /**
104 * If there is any error SSO login/authenticating we remove the token and other save data from the
105 * request session.
106 */
107 @Override
108 public void removePrincipleFromSession(HttpServletRequest httpRequest)
109 {
110 // code to remove a token from the session or other info saved during SSO login.
111 }
112
113 /**
114 * return authenticate user name/email from token to validate in iDempiere
115 */
116 @Override
117 public String getUserName(Object result) throws ParseException
118 {
119 // check if IDempiere login validate by email/user name
120 boolean isEmailLogin = MSysConfig.getBooleanValue(MSysConfig.USE_EMAIL_FOR_LOGIN, false);
121 // Return the login user name/email.
122 return null;
123 }
124
125 /**
126 * return {@link #Language} for login user, can be changed from Role panel
127 */
128 @Override
129 public Language getLanguage(Object result) throws ParseException
130 {
131 // Return login language/user IDempiere default method Language.getBaseLanguage();
132 return null;
133 }
134
135 public I_SSO_PrincipleConfig getConfig()
136 {
137 return config;
138 }
139
140 public void setConfig(I_SSO_PrincipleConfig config)
141 {
142 this.config = config;
143 }
144}
Create Component Definition
- Create OSGI-INF Folder in plugin if does not exist
- Right click in OSGI-INF > New > Other..
- Search Component Definition
- Click Next
- Enter Class, Name and File name as shown in image below
- Click Finish
- Click Add Property with below details
- Name: service.ranking
- Type: Integer
- Values: 10
- Select Services section
- Provide Services > click Add
- Search ISSOPrincipleFactory
- Click OK
- Component Definition myssofactory.xml look as below
1<?xml version="1.0" encoding="UTF-8"?>
2<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="com.logilite.sso.test.factory.MySSOFactory">
3 <implementation class="com.logilite.sso.test.factory.MySSOFactory"/>
4 <property name="service.ranking" type="Integer" value="10"/>
5 <service>
6 <provide interface="org.adempiere.base.sso.ISSOPrincipleFactory"/>
7 </service>
8</scr:component>
Technical Info: IDEMPIERE-5346