Ho avuto la necessità di integrare liferay 6.2 con un cas server per la gestione della login centralizzata. In questo post vedremo come autenticarci tramite facebook/linkedin attravero il cas.
Per prima cosa scarichiamo il cas-overlay (nel mi caso quello relativo alla versione 5.0.4)
Modifichiamo il build.gradle aggiungendo le seguenti dipendenze
compile "org.apereo.cas:cas-server-support-ldap:${project.'cas.version'}"
compile "org.apereo.cas:cas-server-support-pac4j-webflow:${project.'cas.version'}"
La prima è relativa al supporto ldap (facoltativa) la seconda è relativa alle librerie pac4j che saranno utilizzate dal cas server per delegare l'autenticazione presso sistemi terzi (facebook/linkdin nel nostro caso)
Una volta compilato e deployato il war, modifichiamo il file application.properties
Aggiungiamo le properties relative a linkedin
cas.authn.pac4j.linkedIn.id=<app_id>
cas.authn.pac4j.linkedIn.secret=<app_secret>
cas.authn.pac4j.linkedIn.fields=id,first-name,last-name,email-address,picture-url,positions
cas.authn.pac4j.linkedIn.scope=r_basicprofile,r_emailaddress
e quelle relative a facebook (i fields e gli scope compilati sono quelli approvati automaticamente alla creazione dell'app)
cas.authn.pac4j.facebook.fields=id,name,first_name,last_name,gender,locale,languages,link,timezone,updated_time,verified,email,picture
cas.authn.pac4j.facebook.id=<app_id>
cas.authn.pac4j.facebook.secret=app_secret>
cas.authn.pac4j.facebook.scope=email,public_profile,user_friends
Settiamo le url del cas (verranno usate per le url di redirect)
cas.server.name=https://<cas-server-host>:<port>
cas.server.prefix=https://<cas-server-host>:<port>/cas-server
Aggiungiamo la seguente property. Questa verrà utilizzata per far si che il principal venga popolato con un indicazione
del sistema su cui ci siamo autenticati
cas.authn.pac4j.typedIdUsed=true
Es: org.pac4j.oauth.profile.facebook.FacebookProfile#<facebook_principal_id>
Abilitiamo il cas (utilizzando il protollo cas3) a rilasciare gli attributi opzionali a sistemi terzi (nel nostro caso Liferay)
cas.view.cas3.releaseProtocolAttributes=true
e configuriamo gli attributi che andranno propagati di default
cas.authn.attributeRepository.defaultAttributesToRelease=id,access_token,firstName,lastName,emailAddress,email,first_name,last_name,gender,locale
La configurazione del cas è terminata.
Ora dobbiamo modificare Liferay per recepire queste configurazioni.
Creiamo un ext plugin e aggiungiamo alla cartella ext-lib/portal
cas-client-core-3.4.1.jar (la versione utlizzata dal cas 5.0.4, rinominandola il cas-client-core.jar) cas-client-integration-tomcat-v7-3.4.1.jar
Facciamo override del cas filter modificando il metoto
protected TicketValidator getTicketValidator(long companyId)
throws Exception {
TicketValidator ticketValidator = _ticketValidators.get(companyId);
if (ticketValidator != null) {
return ticketValidator;
}
String serverName = PrefsPropsUtil.getString(companyId,
PropsKeys.CAS_SERVER_NAME, PropsValues.CAS_SERVER_NAME);
String serverUrl = PrefsPropsUtil.getString(companyId,
PropsKeys.CAS_SERVER_URL, PropsValues.CAS_SERVER_URL);
String loginUrl = PrefsPropsUtil.getString(companyId,
PropsKeys.CAS_LOGIN_URL, PropsValues.CAS_LOGIN_URL);
Cas30ProxyTicketValidator cas30ProxyTicketValidator = new Cas30ProxyTicketValidator(
serverUrl);
Map<String, String> parameters = new HashMap<String, String>();
parameters.put("serverName", serverName);
parameters.put("casServerUrlPrefix", serverUrl);
parameters.put("casServerLoginUrl", loginUrl);
parameters.put("redirectAfterValidation", "false");
cas30ProxyTicketValidator.setCustomParameters(parameters);
_ticketValidators.put(companyId, cas30ProxyTicketValidator);
return cas30ProxyTicketValidator;
} in modo da ulizzare Cas30ProxyTicketValidator invece di Cas20ProxyTicketValidator. In questo modo anche Liferay utilizzerà il protocollo CAS3 per la verifica del ticket.
Modifichiamo il processFilter dopo la parte di validazione
Assertion assertion = ticketValidator.validate(ticket, serviceUrl);
if (assertion != null) {
AttributePrincipal attributePrincipal = assertion
.getPrincipal();
login = attributePrincipal.getName();
if (_log.isInfoEnabled()) {
_log.info("Cas Login string [" + login + "]");
}
//in questa mappa sono contenuti tutti gli attributi propagati da cas
Map<String, Object> attributes = attributePrincipal
.getAttributes();
if (_log.isInfoEnabled()) {
_log.info("Cas attributes for social login" + attributes);
}
//nel caso di una login direttamente da cas e non da social network
if (Validator.isEmailAddress(login)) {
session.setAttribute(WebKeys.CAS_LOGIN, login);
} else {
String[] casProfile = StringUtil.split(login,
StringPool.POUND);
String className = casProfile[0];
String id = casProfile[1];
ProfileBean profileBean = new ProfileBean();
String accessToken = MapUtil.getString(attributes,
CasConstants.ACCESS_TOKEN);
profileBean.setAccessToken(accessToken);
profileBean.setId(id);
if (StringUtil.equalsIgnoreCase(className,
CasConstants.FACEBOOK_PAC4J_CLASSNAME)) {
String emailAddress = MapUtil.getString(attributes,
CasConstants.FACEBOOK_EMAIL);
String firstName = MapUtil.getString(attributes,
CasConstants.FACEBOOK_FIRST_NAME);
String lastName = MapUtil.getString(attributes,
CasConstants.FACEBOOK_LAST_NAME);
String gender = MapUtil.getString(attributes,
CasConstants.FACEBOOK_GENDER);
String locale = MapUtil.getString(attributes,
CasConstants.FACEBOOK_LOCALE);
profileBean.setEmailAddress(emailAddress);
profileBean.setFirstName(firstName);
profileBean.setLastName(lastName);
profileBean.setGender(gender);
profileBean.setLocale(locale);
profileBean.setType(CasConstants.FACEBOOK_TYPE);
} else if (StringUtil.equalsIgnoreCase(className,
CasConstants.LINKEDIN_PAC4J_CLASSNAME)) {
String emailAddress = MapUtil.getString(attributes,
CasConstants.LINKEDIN_EMAIL);
String firstName = MapUtil.getString(attributes,
CasConstants.LINKEDIN_FIRST_NAME);
String lastName = MapUtil.getString(attributes,
CasConstants.LINKEDIN_LAST_NAME);
profileBean.setEmailAddress(emailAddress);
profileBean.setFirstName(firstName);
profileBean.setLastName(lastName);
profileBean.setType(CasConstants.LINKEDIN_TYPE);
}
session.setAttribute(CasConstants.PROFILE_BEAN, profileBean);
}
In questo modo recuperiamo gli attributi propagati dal cas, popoliamo un oggetto che verrà messo in sessione e che sarà utilizzato dalle classi di autologin per creare/loggare l'utente.
Di seguito un'implementazione della FacebookAutoLogin
@Override
protected String[] doLogin(HttpServletRequest request,
HttpServletResponse response) throws Exception {
HttpSession session = request.getSession();
ProfileBean profileBean = (ProfileBean) session
.getAttribute(CasConstants.PROFILE_BEAN);
String[] credentials = new String[3];
if (Validator.isNotNull(profileBean) && profileBean.isFacebook()) {
long companyId = PortalUtil.getCompanyId(request);
User user = getUser(session, companyId);
if (user == null) {
user = _addUser(companyId, profileBean);
}
credentials[0] = String.valueOf(user.getUserId());
credentials[1] = user.getPassword();
credentials[2] = Boolean.FALSE.toString();
}
return credentials;
}
protected User getUser(HttpSession session, long companyId)
throws PortalException, SystemException {
ProfileBean profileBean = (ProfileBean) session
.getAttribute(CasConstants.PROFILE_BEAN);
if (Validator.isNotNull(profileBean.getEmailAddress())) {
session.removeAttribute(CasConstants.PROFILE_BEAN);
return UserLocalServiceUtil.fetchUserByEmailAddress(companyId,
profileBean.getEmailAddress());
} else {
long facebookId = GetterUtil.getLong(profileBean.getId());
if (facebookId > 0) {
return UserLocalServiceUtil.fetchUserByFacebookId(companyId,
facebookId);
}
}
return null;
}
private User _addUser(long companyId, ProfileBean profileBean)
throws PortalException, SystemException {
long creatorUserId = 0;
boolean autoPassword = true;
String password1 = StringPool.BLANK;
String password2 = StringPool.BLANK;
boolean autoScreenName = true;
String screenName = StringPool.BLANK;
String emailAddress = profileBean.getEmailAddress();
long facebookId = GetterUtil.getLong(profileBean.getId());
String openId = StringPool.BLANK;
Locale locale = LocaleUtil.getDefault();
String firstName = profileBean.getFirstName();
String middleName = StringPool.BLANK;
String lastName = profileBean.getLastName();
int prefixId = 0;
int suffixId = 0;
boolean male = Validator.equals(profileBean.getGender(), "male");
int birthdayMonth = Calendar.JANUARY;
int birthdayDay = 1;
int birthdayYear = 1970;
String jobTitle = StringPool.BLANK;
long[] groupIds = null;
long[] organizationIds = null;
long[] roleIds = null;
long[] userGroupIds = null;
boolean sendEmail = true;
ServiceContext serviceContext = new ServiceContext();
User user = UserLocalServiceUtil.addUser(creatorUserId, companyId,
autoPassword, password1, password2, autoScreenName, screenName,
emailAddress, facebookId, openId, locale, firstName,
middleName, lastName, prefixId, suffixId, male, birthdayMonth,
birthdayDay, birthdayYear, jobTitle, groupIds, organizationIds,
roleIds, userGroupIds, sendEmail, serviceContext);
user = UserLocalServiceUtil.updateLastLogin(user.getUserId(),
user.getLoginIP());
user = UserLocalServiceUtil
.updatePasswordReset(user.getUserId(), false);
user = UserLocalServiceUtil.updateEmailAddressVerified(
user.getUserId(), true);
return user;
} Se tutto è andato a buon fine ci trovermo autenticati con il nostro account social su liferay