Ask Questions and Find Answers
Important:
Ask is now read-only. You can review any existing questions and answers, but not add anything new.
But - don't panic! While ask is no more, we've replaced it with discuss - the new Liferay Discussion Forum! Read more here here or just visit the site here:
discuss.liferay.com
Liferay 7.4-ga112, Keycloak & OpenID Connect : Login not working
Hello Everyone, I am trying to Connect Liferay with Keycloak, But after login, I get the below screen.
And the error:
2024-06-04 11:36:37.734 ERROR [http-nio-9015-exec-5][StatusDisplayContext:83] Error: null
2024-06-04 11:37:52.841 WARN [http-nio-9015-exec-8][PortalImpl:6128] null
java.lang.NullPointerException: null
at com.liferay.portal.security.sso.openid.connect.internal.OIDCUserInfoProcessor._getUserId(OIDCUserInfoProcessor.java:514) ~[bundleFile:?]
at com.liferay.portal.security.sso.openid.connect.internal.OIDCUserInfoProcessor.processUserInfo(OIDCUserInfoProcessor.java:60) ~[bundleFile:?]
at com.liferay.portal.security.sso.openid.connect.internal.OpenIdConnectAuthenticationHandlerImpl.processAuthenticationResponse(OpenIdConnectAuthenticationHandlerImpl.java:143) ~[bundleFile:?]
at com.liferay.portal.security.sso.openid.connect.internal.servlet.filter.auto.login.OpenIdConnectAutoLoginFilter.processFilter(OpenIdConnectAutoLoginFilter.java:87) [bundleFile:?]
at com.liferay.portal.kernel.servlet.BaseFilter.doFilter(BaseFilter.java:40) [portal-kernel.jar:?]
This is my OpenIdUserInfoConnectorClass:
package com.abc.xyz.portal.security.sso.oidc;
import com.abc.xyz.portal.security.sso.oidc.util.UserGroupUtil;
import com.abc.xyz.portal.security.sso.oidc.util.UserUtil;
import com.liferay.petra.string.StringBundler;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.exception.UserEmailAddressException;
import com.liferay.portal.kernel.json.JSONArray;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.model.Company;
import com.liferay.portal.kernel.model.Group;
import com.liferay.portal.kernel.model.User;
import com.liferay.portal.kernel.service.CompanyLocalService;
import com.liferay.portal.kernel.service.ContactLocalService;
import com.liferay.portal.kernel.service.GroupLocalService;
import com.liferay.portal.kernel.service.UserGroupLocalService;
import com.liferay.portal.kernel.service.UserLocalService;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.security.sso.openid.connect.OpenIdConnectServiceException;
import com.liferay.portal.security.sso.openid.connect.OpenIdConnectServiceException.UserMappingException;
import com.liferay.portal.security.sso.openid.connect.internal.OIDCUserInfoProcessor;
import com.liferay.portal.security.sso.openid.connect.internal.exception.StrangersNotAllowedException;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
immediate = true,
property = {"service.ranking:Integer=100"},
service = OIDCUserInfoProcessor.class
)
public class PortalOpenIdConnectUserInfoProcessor extends OIDCUserInfoProcessor {
private static final String USER_INFORMATION = "userInformation";
private static final String SCREEN_NAME = "username";
private static final String EMAIL_ADDRESS = "email";
private static final String FIRST_NAME = "firstName";
private static final String LAST_NAME = "lastName";
private static final String PERSON_ID = "personId";
private static final String ROLES = "roles";
private static final String INDUSTRY = "industry";
public long processUserInfo(UserInfo userInfo, long companyId)
throws PortalException {
_log.debug("userInfo: " + userInfo.toJSONObject().toJSONString());
//Keycloak returns response in JSON format, so creating JSON object from response. Later extracting industry name from the JSON Object
JSONObject userInfoJSON = JSONFactoryUtil.createJSONObject(userInfo.toJSONObject().toJSONString());
JSONObject customUserInfo = userInfoJSON.getJSONObject(USER_INFORMATION);
checkUserInfoValidity(customUserInfo);
String screenName = customUserInfo.getString(SCREEN_NAME);
String emailAddress = customUserInfo.getString(EMAIL_ADDRESS);
String firstName = customUserInfo.getString(FIRST_NAME);
String lastName = customUserInfo.getString(LAST_NAME);
String personId = customUserInfo.getString(PERSON_ID);
_log.info("ScreenName ooutput -1 =====================================================>>"+screenName);
screenName = getUsernamefromEmailAddress(screenName);
_log.info("ScreenName ooutput=====================================================>>"+screenName);
checkUserProfileValidity(screenName, emailAddress, firstName, lastName, personId);
//ADDED FOR MULTI-TENANCY. IDENTIFYING THE SITE(GROUP) FROM INDUSTRY NAME SO THAT THE USER CAN BE ADDED TO MEMBERSHIP OF THE SITE
String industry = userInfoJSON.getString(INDUSTRY);
Group site = null;
long groupId = 0;
try
{
site = _groupLocalService.getFriendlyURLGroup(companyId, StringPool.SLASH + industry); //find site(group) that has friendly url /industry_name
groupId = site.getGroupId();
}
catch (PortalException e)
{
_log.error("Error occured while trying to find site from tenant:"+industry, e);
}
JSONArray roles = customUserInfo.getJSONArray(ROLES);
String[] roleNames = getRoleNames(roles);
long[] userGroupIds = UserGroupUtil.getUserGroupIds(companyId, roleNames, _userGroupLocalService);
User user = null;
String industryUserScreenName = screenName+StringPool.AT+industry;
user = _userLocalService.fetchUserByScreenName(companyId, industryUserScreenName);
if (user != null) {
UserUtil.updateUser(user, companyId, industryUserScreenName, emailAddress, firstName, lastName, personId, userGroupIds, _companyLocalService, _userLocalService, _userGroupLocalService, _contactLocalService, groupId);
return user.getUserId();
}
checkAddUser(companyId, emailAddress);
user = UserUtil.addUser(companyId, industryUserScreenName, emailAddress, firstName, lastName, personId, userGroupIds, _companyLocalService, _userLocalService, _contactLocalService, groupId);
return user.getUserId();
}
private String[] getRoleNames(JSONArray roles) {
String[] roleNames = new String[0];
if (Validator.isNotNull(roles)) {
roleNames = new String[roles.length()];
for (int i = 0; i < roles.length(); i++) {
roleNames[i] = roles.getString(i);
}
}
return roleNames;
}
private void checkUserInfoValidity(JSONObject customUserInfo) throws UserMappingException {
if (customUserInfo == null) {
throw new OpenIdConnectServiceException.UserMappingException("Invalid OIDC user info: userInformation claim is missing!");
}
String message = customUserInfo.getString("message");
String status = customUserInfo.getString("status");
String data = customUserInfo.getString("data");
if (Validator.isNotNull(message)) {
StringBundler sb = new StringBundler(9);
sb.append("Unable to map OpenId Connect user to the portal, ");
sb.append("Received error data: ");
sb.append("{status=");
sb.append(status);
sb.append(", message=");
sb.append(message);
sb.append(", data=");
sb.append(data);
sb.append("}");
throw new OpenIdConnectServiceException.UserMappingException(
sb.toString());
}
}
private void checkUserProfileValidity(String screenName, String emailAddress, String firstName, String lastName,
String personId) throws UserMappingException {
if (Validator.isNull(screenName) || Validator.isNull(firstName) || Validator.isNull(lastName)
|| Validator.isNull(personId)) {
StringBundler sb = new StringBundler(9);
sb.append("Unable to map OpenId Connect user to the portal, ");
sb.append("missing or invalid profile information: ");
sb.append("{screenName=");
sb.append(screenName);
sb.append(", emailAddress=");
sb.append(emailAddress);
sb.append(", firstName=");
sb.append(firstName);
sb.append(", lastName=");
sb.append(lastName);
sb.append(", personId=");
sb.append(personId);
sb.append("}");
throw new OpenIdConnectServiceException.UserMappingException(
sb.toString());
}
}
public static String getUsernamefromEmailAddress(String userName){
try{
if(userName.contains("@")){
String email = userName;
int index = email.indexOf('@');
email = email.substring(0,index);
return email;
}else{
return userName;
}
} catch (Exception e) {
e.printStackTrace();
}
return userName;
}
protected void checkAddUser(long companyId, String emailAddress)
throws PortalException {
Company company = _companyLocalService.getCompany(companyId);
if (!company.isStrangers()) {
throw new StrangersNotAllowedException(companyId);
}
if (!company.isStrangersWithMx() &&
company.hasCompanyMx(emailAddress)) {
throw new UserEmailAddressException.MustNotUseCompanyMx(
emailAddress);
}
}
@Reference
private CompanyLocalService _companyLocalService;
@Reference
private UserLocalService _userLocalService;
@Reference
private UserGroupLocalService _userGroupLocalService;
@Reference
private ContactLocalService _contactLocalService;
@Reference
private GroupLocalService _groupLocalService;
private static Log _log = LogFactoryUtil.getLog(PortalOpenIdConnectUserInfoProcessor.class);
}
To execute this, I have added below property in com.liferay.portal.security.sso.openid.connect.internal.OpenIdConnectServiceHandlerImpl.config file under osgi/configs.
_oIDCUserInfoProcessor.target = "(component.name=com.abc.xyz.portal.security.sso.oidc.PortalOpenIdConnectUserInfoProcessor)"
I am trying to identify the issue, but couldn't find anything. Can someone help me on this.
Best Regards
Akash
Hi Akash,
Taking a quick look at the exception, it seems still the original method is called which throws an NPE from the https://github.com/liferay/liferay-portal/blob/7.4.3.120-ga120/modules/apps/portal-security-sso/portal-security-sso-openid-connect-impl/src/main/java/com/liferay/portal/security/sso/openid/connect/internal/OIDCUserInfoProcessor.java#L514 line.
This could be investigated to find the reason.
I's also suspicious that the parameters are different for your processUserInfo method.
Here you can find a public custom OIDC user resolver: https://github.com/fabian-bouche-liferay/oidc-user-resolver/tree/master.
Regards,
Zsigmond
Thank you for your answer Rab,
The exception is coming in the default Liferay class, not in the class I have overwritten. Our custom class is not getting executed (overriding).
In 7.2 this is how we were using to inforce our custom class to override the liferay's class.
com.liferay.portal.security.sso.openid.connect.internal.OpenIdConnectServiceHandlerImpl.config file under osgi/configs.
_oIDCUserInfoProcessor.target = "(component.name=com.abc.xyz.portal.security.sso.oidc.PortalOpenIdConnectUserInfoProcessor)"
But this is not the case in the 7.4.
We do not think, this should the the correct file name "com.liferay.portal.security.sso.openid.connect.internal.OpenIdConnectServiceHandlerImpl.config " under osgi/configs to resolve this issue.
Powered by Liferay™