Below is my first implementation of an JSR-196 login module using the liferay user cookies.
The problem is that I was not able to deploy this code on glassfish. The reason was that the code depends on the util-java.jar and portal-impl.jar. And I was not able to provide these libraries in a Glassfish Domain/Lib Folder, without breaking the liferay web module. I found no solution how to provide the necessary libraries on glassfish in a way that the Liferay Web module still deploys without class-not-found exceptions. So now I try another solution with a separate helperServlet providing the user login information.
But maybe the code is helpful for somebody.
1
2package com.imixs.workflow.liferay;
3
4import java.io.UnsupportedEncodingException;
5import java.security.Key;
6import java.util.HashMap;
7import java.util.HashSet;
8import java.util.Map;
9import java.util.Set;
10import java.util.StringTokenizer;
11import java.util.logging.Level;
12import java.util.logging.Logger;
13
14import javax.faces.context.FacesContext;
15import javax.security.auth.Subject;
16import javax.security.auth.callback.Callback;
17import javax.security.auth.callback.CallbackHandler;
18import javax.security.auth.message.AuthException;
19import javax.security.auth.message.AuthStatus;
20import javax.security.auth.message.MessageInfo;
21import javax.security.auth.message.MessagePolicy;
22import javax.security.auth.message.callback.CallerPrincipalCallback;
23import javax.security.auth.message.callback.GroupPrincipalCallback;
24import javax.security.auth.message.module.ServerAuthModule;
25import javax.servlet.http.HttpServletRequest;
26import javax.servlet.http.HttpServletRequestWrapper;
27import javax.servlet.http.HttpServletResponse;
28import javax.servlet.http.HttpServletResponseWrapper;
29
30import com.liferay.portal.kernel.exception.PortalException;
31import com.liferay.portal.kernel.exception.SystemException;
32import com.liferay.portal.kernel.util.GetterUtil;
33import com.liferay.portal.model.User;
34import com.liferay.portal.service.CompanyLocalServiceUtil;
35import com.liferay.portal.service.UserLocalServiceUtil;
36import com.liferay.portal.util.CookieKeys;
37import com.liferay.portal.util.PortalUtil;
38import com.liferay.util.CookieUtil;
39import com.liferay.util.Encryptor;
40import com.liferay.util.EncryptorException;
41
42/**
43 * This Class is a JSR-196 based ServerAuthModul using liferay portal service
44 * api to authenticate users against Liferay Portal Server
45 *
46 *
47 * @author rsoika,
48 */
49@SuppressWarnings("unchecked")
50public class LiferayAuthModule implements ServerAuthModule {
51
52 private static final int DEBUG_TRACE = 1;
53 private static final int DEBUG_LOGIN = 2;
54 private static final int DEBUG_ASSOCIATION = 4;
55
56 private static final HashMap debugStagesMap = new HashMap();
57 static {
58 debugStagesMap
59 .put("all", DEBUG_TRACE + DEBUG_LOGIN + DEBUG_ASSOCIATION);
60 debugStagesMap.put("trace", DEBUG_TRACE);
61 debugStagesMap.put("login", DEBUG_LOGIN);
62 debugStagesMap.put("association", DEBUG_ASSOCIATION);
63
64 }
65 private int debugStagesMask;
66
67 protected static final Class[] supportedMessageTypes = new Class[] {
68 javax.servlet.http.HttpServletRequest.class,
69 javax.servlet.http.HttpServletResponse.class };
70
71 protected final Logger logger = Logger.getLogger(LiferayAuthModule.class
72 .getName());
73
74 protected Map options;
75 protected CallbackHandler handler;
76 protected MessagePolicy requestPolicy;
77 protected MessagePolicy responsePolicy;
78
79 private static final String IS_MANDATORY_INFO_KEY = "javax.security.auth.message.MessagePolicy.isMandatory";
80 private static final String AUTH_TYPE_INFO_KEY = "javax.servlet.http.authType";
81 protected static final String ASSIGN_GROUPS_OPTIONS_KEY = "assign.groups";
82 private static String DEBUG_STAGES_OPTIONS_KEY = "debug.stages";
83
84 private static final String LIFERAY_PORTAL_USER = "liferay.user.object";
85
86 protected static final String SAVED_REQUEST_ATTRIBUTE = "javax.security.auth.message.SavedHttpRequest";
87
88 protected String[] assignedGroups;
89 protected boolean isMandatory;
90
91 /**
92 * Remove method specific principals and credentials from the subject.
93 *
94 * @param messageInfo
95 * a contextual object that encapsulates the client request and
96 * server response objects, and that may be used to save state
97 * across a sequence of calls made to the methods of this
98 * interface for the purpose of completing a secure message
99 * exchange.
100 *
101 * @param subject
102 * the Subject instance from which the Principals and credentials
103 * are to be removed.
104 *
105 * @exception AuthException
106 * If an error occurs during the Subject processing.
107 */
108 public void cleanSubject(MessageInfo messageInfo, Subject subject)
109 throws AuthException {
110 if (subject != null) {
111 logInfo(DEBUG_TRACE, "openid.do_clean_subject");
112 subject.getPrincipals().clear();
113 }
114 }
115
116 /**
117 * Secure a service response before sending it to the client.
118 *
119 * This method is called to transform the response message acquired by
120 * calling getResponseMessage (on messageInfo) into the mechanism-specific
121 * form to be sent by the runtime.
122 * <p>
123 * This method conveys the outcome of its message processing either by
124 * returning an AuthStatus value or by throwing an AuthException.
125 *
126 * @param messageInfo
127 * A contextual object that encapsulates the client request and
128 * server response objects, and that may be used to save state
129 * across a sequence of calls made to the methods of this
130 * interface for the purpose of completing a secure message
131 * exchange.
132 *
133 * @param serviceSubject
134 * A Subject that represents the source of the service response,
135 * or null. It may be used by the method implementation to
136 * retrieve Principals and credentials necessary to secure the
137 * response. If the Subject is not null, the method
138 * implementation may add additional Principals or credentials
139 * (pertaining to the source of the service response) to the
140 * Subject.
141 *
142 * @return An AuthStatus object representing the completion status of the
143 * processing performed by the method. The AuthStatus values that
144 * may be returned by this method are defined as follows:
145 *
146 * <ul>
147 * <li> AuthStatus.SEND_SUCCESS when the application response
148 * message was successfully secured. The secured response message
149 * may be obtained by calling getResponseMessage on messageInfo.
150 *
151 * <li> AuthStatus.SEND_CONTINUE to indicate that the application
152 * response message (within messageInfo) was replaced with a
153 * security message that should elicit a security-specific response
154 * (in the form of a request) from the peer.
155 *
156 * This status value serves to inform the calling runtime that (to
157 * successfully complete the message exchange) it will need to be
158 * capable of continuing the message dialog by processing at least
159 * one additional request/response exchange (after having sent the
160 * response message returned in messageInfo).
161 *
162 * When this status value is returned, the application response must
163 * be saved by the authentication module such that it can be
164 * recovered when the module's validateRequest message is called to
165 * process the elicited response.
166 *
167 * <li> AuthStatus.SEND_FAILURE to indicate that a failure occurred
168 * while securing the response message and that an appropriate
169 * failure response message is available by calling
170 * getResponseMeessage on messageInfo.
171 * </ul>
172 *
173 * @exception AuthException
174 * When the message processing failed without establishing a
175 * failure response message (in messageInfo).
176 *
177 * @author this method was initial implemented by monzillo
178 */
179 public AuthStatus secureResponse(MessageInfo messageInfo,
180 Subject serviceSubject) throws AuthException {
181
182 boolean wrapped = false;
183 HttpServletRequest r = (HttpServletRequest) messageInfo
184 .getRequestMessage();
185 while (r != null && r instanceof HttpServletRequestWrapper) {
186 r = (HttpServletRequest) ((HttpServletRequestWrapper) r)
187 .getRequest();
188 wrapped = true;
189 }
190 if (wrapped) {
191 messageInfo.setRequestMessage(r);
192 }
193 wrapped = false;
194 HttpServletResponse s = (HttpServletResponse) messageInfo
195 .getResponseMessage();
196 while (s != null && s instanceof HttpServletResponseWrapper) {
197 s = (HttpServletResponse) ((HttpServletResponseWrapper) s)
198 .getResponse();
199 wrapped = true;
200 }
201 if (wrapped) {
202 messageInfo.setResponseMessage(s);
203 }
204
205 return AuthStatus.SEND_SUCCESS;
206 }
207
208 /**
209 * Authenticate a received service request. This method conveys the outcome
210 * of its message processing either by returning an AuthStatus value or by
211 * throwing an AuthException.
212 *
213 * @param messageInfo
214 * A contextual object that encapsulates the client request and
215 * server response objects, and that may be used to save state
216 * across a sequence of calls made to the methods of this
217 * interface for the purpose of completing a secure message
218 * exchange.
219 *
220 * @param clientSubject
221 * A Subject that represents the source of the service request.
222 * It is used by the method implementation to store Principals
223 * and credentials validated in the request.
224 *
225 * @param serviceSubject
226 * A Subject that represents the recipient of the service
227 * request, or null.
228 *
229 * @return An AuthStatus object representing the completion status of the
230 * processing performed by the method. The AuthStatus values that
231 * may be returned by this method are defined as follows:
232 *
233 * <ul>
234 * <li>AuthStatus.SUCCESS when the application request message was
235 * successfully validated.
236 *
237 * <li>AuthStatus.SEND_SUCCESS to indicate that
238 * validation/processing of the request message successfully
239 * produced the secured application response message (in
240 * messageInfo). The secured response message is available by
241 * calling getResponseMessage on messageInfo.
242 *
243 * <li>AuthStatus.SEND_CONTINUE to indicate that message validation
244 * is incomplete, and that a preliminary response was returned as
245 * the response message in messageInfo.
246 *
247 * When this status value is returned to challenge an application
248 * request message, the challenged request must be saved by the
249 * authentication module such that it can be recovered when the
250 * module's validateRequest message is called to process the request
251 * returned for the challenge.
252 *
253 * <li>AuthStatus.SEND_FAILURE to indicate that message validation
254 * failed and that an appropriate failure response message is
255 * available by calling getResponseMessage on messageInfo.
256 * </ul>
257 *
258 * @exception AuthException
259 * When the message processing failed without establishing a
260 * failure response message (in messageInfo).
261 */
262 public AuthStatus validateRequest(MessageInfo messageInfo,
263 Subject clientSubject, Subject serviceSubject) throws AuthException {
264
265 assert (messageInfo.getMap().containsKey(IS_MANDATORY_INFO_KEY) == isMandatory);
266
267 HttpServletRequest request = (HttpServletRequest) messageInfo
268 .getRequestMessage();
269
270 /*
271 * First test if do we have an valid token?
272 */
273 User identifier = getUserObject(messageInfo);
274 if (identifier == null) {
275 // No!
276 // If the request is protected than consume the portal user object
277 if (isMandatory) {
278 logInfo(DEBUG_TRACE, "consume portal user object");
279 identifier = consumePortalUserObject(request);
280 } else {
281 // the request is not protected so simple succeed the
282 // request...
283 return AuthStatus.SUCCESS;
284 }
285 }
286
287 // if we now have a valid token, we can set the caller principal
288 if (identifier != null) {
289 // set the caller principal now
290 String id = "" + identifier.getUserId();
291 setCallerPrincipal(id, clientSubject);
292 messageInfo.getMap().put(AUTH_TYPE_INFO_KEY, "LiferayPortal");
293 return AuthStatus.SUCCESS;
294 } else {
295 // we can not authenticate because we have no portal user object and
296 // the request is mandatory!
297 logInfo(DEBUG_LOGIN, "user not authenticated");
298 return AuthStatus.SEND_FAILURE;
299 }
300
301 }
302
303 /**
304 * Get the one or more Class objects representing the message types
305 * supported by the module.
306 *
307 * @return An array of Class objects, with at least one element defining a
308 * message type supported by the module.
309 */
310 public Class[] getSupportedMessageTypes() {
311 return supportedMessageTypes;
312 }
313
314 /**
315 * Module specific options as configured in options Map
316 *
317 * openid.session_type=sessionType
318 *
319 * openid.content.type = contenttype value set in Accept header of identity
320 * page request.
321 *
322 *
323 * debug-stages=all or subset {trace,form,idpage,association,checkid,trust}
324 * trace - log trace of the message processing form - log login form
325 * processing association - log openid association processing checkid - log
326 * check id processing trust - trusted server evaluation all - log all of
327 * the above.
328 *
329 * shared options:
330 *
331 *
332 * assign.groups=groupList shared groups added as a side-effect of
333 * authentication.
334 *
335 */
336
337 public void initialize(MessagePolicy requestPolicy,
338 MessagePolicy responsePolicy, CallbackHandler handler, Map options)
339 throws AuthException {
340
341 this.requestPolicy = requestPolicy;
342 this.responsePolicy = responsePolicy;
343 this.isMandatory = requestPolicy.isMandatory();
344 this.handler = handler;
345 this.options = options;
346 this.assignedGroups = parseAssignGroupsOption(options);
347
348 debugStagesMask = parseDebugStagesOption(options);
349
350 }
351
352 /*
353 *
354 *
355 * Helper Methods
356 */
357
358 /**
359 * This Method consumes the User Object from Liferay Portal. There for the
360 * method test for the cookie ID. The user id will be decrypted by the
361 * Encryptor class and the User object is receifed through the method call
362 * UserLocalServiceUtil.getUser()
363 *
364 * @param request
365 * @return
366 */
367 private User consumePortalUserObject(HttpServletRequest request) {
368 logInfo(DEBUG_TRACE, "consumePortalUserObject....");
369 Key key;
370 try {
371 // read default compnayID from portal...
372 Long lCompanyID = PortalUtil.getDefaultCompanyId();
373 logInfo(DEBUG_ASSOCIATION, "consumePortalUserObject CompanyId="
374 + lCompanyID);
375 key = CompanyLocalServiceUtil.getCompany(lCompanyID).getKeyObj();
376
377 // read the portal id from the cookie....
378 String c_id = GetterUtil.getString(CookieUtil.get(request,
379 CookieKeys.ID));
380 // decrypt the id...
381 long userId = GetterUtil.getLong(Encryptor.decrypt(key,
382 hexStringToStringByAscii(c_id)));
383
384 // now get the portal user object...
385 User user = UserLocalServiceUtil.getUser(userId);
386
387 // store user object into session
388 request.getSession().setAttribute(LIFERAY_PORTAL_USER, user);
389
390 logInfo(DEBUG_ASSOCIATION,
391 "consumePortalUserObject UserID=" + user.getUserId());
392 logInfo(DEBUG_ASSOCIATION, "consumePortalUserObject FullName="
393 + user.getFullName());
394 return user;
395
396 } catch (PortalException e1) {
397 logInfo(DEBUG_ASSOCIATION,
398 "consumePortalUserObject Error=" + e1.getMessage());
399 e1.printStackTrace();
400 } catch (SystemException e1) {
401 logInfo(DEBUG_ASSOCIATION,
402 "consumePortalUserObject Error=" + e1.getMessage());
403 e1.printStackTrace();
404 } catch (EncryptorException e1) {
405 logInfo(DEBUG_ASSOCIATION,
406 "consumePortalUserObject Error=" + e1.getMessage());
407 e1.printStackTrace();
408 }
409 return null;
410 }
411
412 private boolean checkLogCriteria(int criteria) {
413 return (criteria != 0 && ((debugStagesMask & criteria) == criteria));
414 }
415
416 private void logInfo(int criteria, String tag) {
417 if (checkLogCriteria(criteria)) {
418 logger.log(Level.INFO, tag);
419 }
420 }
421
422
423
424 private static int parseDebugStagesOption(Map options) {
425 int bitMap = 0;
426 if (options != null) {
427 String option = ((String) options.get(DEBUG_STAGES_OPTIONS_KEY));
428 if (option != null) {
429 StringTokenizer tokenizer = new StringTokenizer(option, ",");
430 while (tokenizer.hasMoreTokens()) {
431 String token = tokenizer.nextToken();
432 Integer value = (Integer) debugStagesMap.get(token);
433 if (value != null)
434 bitMap += value.intValue();
435 }
436 }
437 }
438 return bitMap;
439 }
440
441 private String[] parseAssignGroupsOption(Map options) {
442 String[] groups = new String[0];
443 if (options != null) {
444 String groupList = (String) options.get(ASSIGN_GROUPS_OPTIONS_KEY);
445 if (groupList != null) {
446 StringTokenizer tokenizer = new StringTokenizer(groupList,
447 " ,:,;");
448 Set<String> groupSet = null;
449 while (tokenizer.hasMoreTokens()) {
450 if (groupSet == null) {
451 groupSet = new HashSet<String>();
452 }
453 groupSet.add(tokenizer.nextToken());
454 }
455 if (groupSet != null && !groupSet.isEmpty()) {
456 groups = groupSet.toArray(groups);
457 }
458 }
459 }
460 return groups;
461 }
462
463 private boolean setCallerPrincipal(String caller, Subject clientSubject) {
464 boolean rvalue = true;
465 boolean assignGroups = true;
466
467 // create CallerPrincipalCallback
468 CallerPrincipalCallback cPCB = new CallerPrincipalCallback(
469 clientSubject, caller);
470
471 if (cPCB.getName() == null && cPCB.getPrincipal() == null) {
472 assignGroups = false;
473 }
474
475 try {
476 handler.handle((assignGroups ? new Callback[] {
477 cPCB,
478 new GroupPrincipalCallback(cPCB.getSubject(),
479 assignedGroups) } : new Callback[] { cPCB }));
480
481 logInfo(DEBUG_ASSOCIATION, "caller_principal:" + cPCB.getName()
482 + " " + cPCB.getPrincipal());
483
484
485 logInfo(DEBUG_ASSOCIATION, "assigned_Groups:" + assignedGroups);
486
487 } catch (Exception e) {
488 // should not happen
489 logger.log(Level.WARNING, "jmac.failed_to_set_caller", e);
490 rvalue = false;
491 }
492
493 return rvalue;
494 }
495
496 /**
497 * This method verifies if the current session holds an validated User
498 * Object
499 *
500 * @param request
501 * @return
502 */
503 public User getUserObject(MessageInfo messageInfo) {
504
505 HttpServletRequest request = (HttpServletRequest) messageInfo
506 .getRequestMessage();
507
508 User identifier = (User) request.getSession().getAttribute(
509 LIFERAY_PORTAL_USER);
510 return identifier;
511
512 }
513
514 public String hexStringToStringByAscii(String hexString) {
515 byte[] bytes = new byte[hexString.length() / 2];
516 for (int i = 0; i < hexString.length() / 2; i++) {
517 String oneHexa = hexString.substring(i * 2, i * 2 + 2);
518 bytes[i] = Byte.parseByte(oneHexa, 16);
519 }
520 try {
521 return new String(bytes, "ASCII");
522 } catch (UnsupportedEncodingException e) {
523 throw new RuntimeException(e);
524 }
525 }
526
527}
Be kell jelentkezni ahhoz, hogy ez helytelenként legyen megjelölve.