Using Acegi Security within Portlet Environments: Authentication and Authorization Guide

splattersquadSecurity

Nov 17, 2013 (3 years and 6 months ago)

82 views

Using
Acegi Security
within Portlet Environments
:

Authentication and Authorization

Guide


Using Acegi Security within Portlet Environments: Authentication and Authorization Guide

1

Problem

................................
................................
................................
................................
..

2

Solution

................................
................................
................................
................................
..

2

Integrating Liferay Portal with Acegi Security Framework

................................
...................

2

Delegating Liferay Authentication to Acegi Security Framework

................................
........

2

Liferay auto.login.hooks

................................
................................
................................
....

3

Configuring Acegi Security for Authentication

................................
................................
.

4

Propagating Acegi SecurityContext From Liferay Portal To Portlet Web Applications

.....

18

Intercepting Portlet Calls

................................
................................
................................
..

18

Retrieving SecurityContext on Portlet Side

................................
................................
.....

19

Integrating JBoss Por
tal with Acegi Security Framework

................................
...................

21

JBoss Authentication and Creating a Valid Acegi Authentication Token

...........................

21

Configuring JBoss P
ortal to use LDAP

................................
................................
............

22

Configuring Acegi Security in JBoss Portal
-
Server Web Application

............................

23

Propagating Acegi SecurityContext from

JBoss Portal to Portlet Web Applications

..........

24

Acegi Authorization within Portlets

................................
................................
.....................

26

Protecting Portlet Resources

................................
................................
............................

26

Method Level Security

................................
................................
................................
.....

32

Domain Instance Level Security (ACLs)

................................
................................
.........

34

After Invo
cation Security

................................
................................
................................
.

36

Management of ACL Entries

................................
................................
...........................

38

ACL Tables and Sample Entries

................................
................................
......................

40

Sample Portlet Web Application

................................
................................
..........................

40

Sample Porlet Web Application JBoss Version

................................
...............................

45

Running JSF Portlets in JBoss
Portal 2.6.1

................................
................................
......

47


Author

:

Kenan Sevindik

Blog


:

http://ksevindik.blogspot.com

E
-
mail


:

ksevindik@gmail.com

Version

:

0.1


Liferay Portal and Acegi Security Framework are two
popular open source products for
enterprise systems. Liferay provides its own authentication and authorization mechanism by
default

and it is possible to delegate authentication to third parties.


Acegi Security Framework is very popular among enterprise w
eb applications. It provides
several options for authentication and authorization requirements of web applications.


Liferay Portal is
a portlet container. As you know portlet containers are themselves typical
web applications, in which it is possible to
deploy portlets, which are also web applications.
Therefore, it should be
, at least theoretically,

possible to employ Acegi Security Framework
in all those web applications.


Problem


The biggest problem in
using

Acegi Security Framework out of the box in
a portal
environment is that Acegi is based on ServletFilter, HttpServletRequest, and
HttpServletResponse constructs. Unfortunately, there is no ServletFilter like mechanism in
portal environment. Moreover, portlets deal with PortletRequest and PortletResp
onse objects
to perform their
jobs.

Finally, there is no URL resource like concept for portlet requests and
responses.


Solution


The solution consists of two parts. The first part is configuring portal web application, in th
is
case Liferay,
to use Acegi S
ecurity for its authentication needs. Acegi will also secure HTTP
requests coming to portal. By that way Acegi will guarantee that all requests arriving at portal
originates from authenticated users. As a result there will always be a SecurityContext,
cont
aining a valid Authentication token
for

current web
request.


The second part of the solution is to propagate that SecurityContext with valid Authentication
object to individual portlet web application from portlet
container that

is Liferay Portal.

As we
s
tated previously, both portlet containers and portlets are typical web applications. Portal
s
need to

provide an implementation specific way to
communicate with

those portlet web
applications. Usually there is
a well

known servlet configured in portlet web
application, and
portal calls this servlet, and inside it web requests are converted into PortletRequest objects
and target portlet is invoked.


If we are able to intercept the point in which this
communication

is initiated, put Acegi
SecurityContext objec
t into HTTP request, retrieve it in that well known servlet, and finally
make it available

to Acegi again in portlet side, then it
is

possible to protect individual portlet
resources, possible to run method level and domain instance level
authorization

fea
tures in
portlet web application.


I will explain in the following sections how this interception is achieved using aspects in
Liferay portal

side, and SecurityContext object is retrieved and made available again in
individual portlet web application.


Ple
ase note that, although I explained this solution for Liferay Portal, there is virtually no
obstacle

not to employ it for other JSR
-
168 compliant portlet containers.

They all employ a
similar way to perform communication between t
hem and their deployed por
tlets.


Integrating Liferay Portal with Acegi Security Framework

Delegating Liferay Authentication to Acegi Security Framework


As portlet containers are typical web applications, there is no problem in employing Acegi
Security Framework for authenticating

and authorizing HTTP requests coming into them.


Liferay auto.login.hooks



Liferay portal

also

provides a
separate
hooking mechanism to fetch valid credentials from
any
third party security system. Inside this hook method, we have to populate current use
r info to
Liferay if that user’s information doesn’t exist before.


Although there is such a hook mechanism,
in

our solution,
I prefer not to
depend on it, and
employ Acegi

SecurityContextHolder
AwareRequestFilter

instead. This filter wraps current
HttpServ
letRequest object and return values for
getRemoteUser()

and
isUserInRole()

methods using its own SecurityContext owned Authentication object. Liferay depends on
those HttpServletRequest methods to determine if current request needs authentication, and
when

a valid username is returned from getRemoteUser() method Liferay will decide that
current request is already authenticated, and won’t try to authenticate it.


Here, I would like to mention about how to configure hooking mechanism if you don’t want
to use


though I
don’t

see any valid reason not to use it
-

SecurityContextHolderAwareRequestFilter.


Please note that if you use SecurityContextHolderAwareRequestFilter, hooking will never get
into action, because Liferay wont continue authentication process a
s it will receive a valid
username from getRemoteUser() method.



There is an available

com.liferay.portal.security.auth.AutoLogin

interface to create auto
login hooks in Liferay. It has a one method called login which takes HttpServletRequest,
HttpServlet
Response as input
parameters

and return user’s credentials as
String [
].



public

String[] login(HttpServletRequest req, HttpServletResponse res)




throws

AutoLoginException {



try

{



String[] credentials =
null
;




String screenName = SecurityContextHo
lder.
getContext
()






.getAuthentication().getName();




if

(screenName !=
null
) {




User user =
null
;




String companyId = PortalUtil.
getCompanyId
(req);

if
(org.apache.commons.lang.StringUtils.
isEmpty
(companyId) {





companyId = (String)req.getSession(
).

getServletContext().getInitParameter(

"company_id"
);




}





try

{

user = UserLocalServiceUtil.
getUserById
(companyId,








screenName);




}
catch

(NoSuchUserException nsue) {





user = addUser(companyId, screenName);




}





credentials =
new

Stri
ng[3];





credentials[0] = String.
valueOf
(user.getUserId());




credentials[1] = user.getPassword();




credentials[2] = Boolean.
TRUE
.toString();



}




return

credentials;


}
catch

(Exception e) {




throw

new

AutoLoginException(e);


}

}


In this login m
ethod, we get Authentication
object from

SecurityContext, and
query user
object from Liferay

database
, and

return credentials
String [
] constructed with attributes from
user found. As you see, i
f
user

doesn’t exist we just create it in Liferay database.

Al
l of the
source
code

and configuration files are available as attachment.

You can examine addUser
method in detail.


Caution:

addUser() method heavily depend on Liferay 4.2.2 release. There are major changes
to Liferay API in 4.3 release, and you will nee
d to change code accordingly if you try it with
another version


After creating auto login hook, we need to configure Liferay to use it. There is an
auto.login.hook
s

property in
portal.
properties

file, which can be
overridden

in
ROOT/WEB
-
INF/classes/portal
-
ext.properties
.


auto.login.hooks=com.liferay.portal.security.acegi.AcegiAutoLoginHook


Configuring Acegi Security for Authentication


In order to run Acegi Security Framework in Liferay Portal, we first need to define Spring’s
ContextLoaderListener to cr
eate application context during portal startup.

Just add following
part into ROOT/WEB
-
INF/web.xml file of your Liferay install.


<
context
-
param
>



<
param
-
name
>
contextConfigLocation
</
param
-
name
>


<
param
-
value
>



classpath:/appcontext/spring
-
beans
.
acegi
-
port
le
t
-
liferay
.xml



</
param
-
value
>

</
context
-
param
>


<
listener
>


<
listener
-
class
>

org.springframework.web.context.ContextLoaderListener

</
listener
-
class
>

</
listener
>


After that configuration, we need to add filter definition into web.xml
.

And add its filter

mapping as the first mapping in

the

web.xml file.



<
filter
>



<
filter
-
name
>
AcegiSecurityFilter
</
filter
-
name
>



<
filter
-
class
>

org.acegisecurity.util.FilterToBeanProxy

</
filter
-
class
>



<
init
-
param
>




<
param
-
name
>
targetBean
</
param
-
name
>




<
param
-
value
>
f
ilterChainProxy
</
param
-
value
>



</
init
-
param
>


</
filter
>


<
filter
-
mapping
>




<
filter
-
name
>
AcegiSecurityFilter
</
filter
-
name
>




<
url
-
pattern
>
/*
</
url
-
pattern
>

</
filter
-
mapping
>



FilterToBeanProxy

just

delegates calls to
targetBean

defined in Applicat
ionContext.
By
that way we are able to define Filter instances as Spring managed beans.


Let’s now walk over
some
important parts of Acegi

bean configurations that make
Authentication work.



Caution:

I won’t cover every detail of
Acegi;

just provide enoug
h information to ease
understanding of how authentication works. You can refer Acegi’s reference documentation
or API for further details.



<
bean
id
=
"filterChainProxy"



class
=
"org.acegisecurity.util.FilterChainProxy"
>



<
property
name
=
"filterInvocationDe
finitionSource"
>




<
value
>





CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON





PATTERN_TYPE_APACHE_ANT





/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationPr
ocessingFilter,anonymousProcessingFilter,securityContextHolderAwareRequestF
ilter
,exceptionTranslationFilter,filterSecurityInterceptor




</
value
>



</
property
>


</
bean
>


As we stated earlier, Acegi is based on ServletFilter objects to perform its job. Requests
matched by above filter
-
mapping, all requests for the above case, are deleg
ated to
filterChainProxy. filterChainProxy
invokes each filter in sequence.

Please note that, the

order
of those filters is

very important for Acegi to function properly.


First filter is
httpSessionContextIntegrationFilter
, which populates SecurityContext
Holder
with any available SecurityContext object obtained from current HttpSession. It
places

SecurityContext into HttpSession at the end of each web request.



<
bean
id
=
"logoutFilter"



class
=
"org.acegisecurity.ui.logout.LogoutFilter"
>


<
constructor
-
arg
>



<
value
>
/login.jsp
</
value
>


</
constructor
-
arg
>



<
constructor
-
arg
>



<
list
>




<
bean
class
=
"org.acegisecurity.ui.logout.SecurityContextLogoutHandler"
/>



</
list
>


</
constructor
-
arg
>



<
property
name
=
"filterProcessesUrl"
>



<
value
>
/c/portal/logout
</
value
>


</
property
>

</
bean
>


logoutFilter

is used to log a principal out. First constructor
-
arg defines where to go after
logout.
filterProcessesUrl

identifies which URL must be processed to perform logout. We
define it as
/c/portal/logout

as it is Liferay Porta
l’s logout URL.


<
bean
id
=
"authenticationProcessingFilter"



class
=
"com.liferay.portal.security.acegi.LiferayUserPopulatingAuthent
icationProcessingFilter"
>




<
property
name
=
"authenticationManager"
>




<
ref
bean
=
"authenticationManager"
/>



</
property
>




<
property
name
=
"authenticationFailureUrl"
>




<
value
>
/login.jsp?error
=true
</
value
>



</
property
>




<
property
name
=
"defaultTargetUrl"
>




<
value
>
/index.html
</
value
>



</
property
>


</
bean
>


authenticationProcessingFilter

is one of cornerstone filters in Ace
gi. It starts authentication
process, and delegates actual work to its authenticationManager. We configure it with
authenticationFailureUrl

which tells where to go if authentication fails, and
defaultTargetUrl

which tells where to go if authentication succ
eeds unless there isn’t any
saved targetUrl found in current HttpSession. There is also another important parameter that
needs to be mentioned here, although we use its default value in our configuration;
filterProcessesUrl

indicates in which URL request a
uthentication processing starts. By
default it has is
/j_acegi_security_check
.


Acegi is able to perform different types of authentications, form based, basic, certificate based
etc. We us
e form based authentication in this configuration
. Acegi provides
or
g.acegisecurity.ui.webapp.AuthenticationProcessingFilter

class for form based
authentication.



LiferayUserPopulatingAuthenticationProcessingFilter

extends from
AuthenticationProcessingFilter, and overrides its
onSuccessfulAuthentication

method.

We
populat
e user information with Liferay database if it doesn’t already exist there.


protected

void

onSuccessfulAuthentication(HttpServletRequest request,




HttpServletResponse response, Authentication authResult)




throws

IOException {


try

{



acegiAutoLoginHo
ok
.login(request, response);


}
catch

(AutoLoginException e) {



throw

new

AuthenticationServiceException(

"Authentication procedure cannot continue."
,e);


}

}


We have reused AcegiAutoLoginHook class in order to populate Liferay database with user
informa
tion.



<
bean
id
=
"exceptionTranslationFilter"



class
=
"org.acegisecurity.ui.ExceptionTranslationFilter"
>




<
property
name
=
"authenticationEntryPoint"
>




<
bean













class
=
"org.acegisecurity.ui.webapp.

AuthenticationProcessingFilterEntryPoint"
>





<
property
name
=
"loginFormUrl"
>





<
value
>
/login.jsp
</
value
>




</
property
>


</
bean
>



</
property
>




<
property
name
=
"accessDeniedHandler"
>




<
bean




class
=
"org.acegisecurity.ui.AccessDeniedHandlerImpl"
>





<
property
name
=
"errorPage"
>






<
value
>
/access
Denied.jsp
</
value
>





</
property
>




</
bean
>



</
property
>


</
bean
>





exce
ptionTranslationFilter

handles any
AccessDeniedException

and
AuthenticationException

thrown in the filter chain.

When an AccessDeniedException
thrown, user is redirected to errorP
age defined
in
accessDeniedHandler

bean which is
defianed as inner bean in this case
. When an AuthenticationException
occurs user is
redirected to loginFormUrl defined in
authenticationEntryPoint

bean.




<
bean
id
=
"filterSecurityInterceptor"



class
=
"org.a
cegisecurity.intercept.web.FilterSecurityInterceptor"
>




<
property
name
=
"authenticationManager"
>




<
ref
bean
=
"authenticationManager"
/>



</
property
>




<
property
name
=
"accessDecisionManager"
>




<
ref
bean
=
"accessDecisionManager"
/>



</
property
>






<
p
roperty
name
=
"objectDefinitionSource"
>




<
value
>





CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON





\
A/c/portal/login
\
Z=ROLE_PORTAL_USER





\
A/c/portal/logout
\
Z=ROLE_PORTAL_USER





\
A/c/portal/layout.*
\
Z=ROLE_PORTAL_USER





\
A/group/.*
\
Z=ROLE_PORTAL_US
ER





\
A.*
\
Z=ROLE_ANONYMOUS,ROLE_PORTAL_USER




</
value
>



</
property
>






<
property
name
=
"alwaysReauthenticate"
>




<
value
>
true
</
value
>



</
property
>


</
bean
>


The last filter in the chain is
filterSecurityInterceptor
.

It perfoms authorization of web
re
quests. Actually real work is done by
accessDecisionManager
. Who can reach at what
re
sources are given as
objectDefini
tionSource
.

Acegi has a very powerful URL pattern
mechanism to define secured web resources. Let’s review our protected resources here
qui
ckly:


/c/portal/login
=ROLE_PORTAL_USER

/c/portal/logout
=ROLE_PORTAL_USER

/c/portal/layout.*
=ROLE_PORTAL_USER

/group/.*
=ROLE_PORTAL_USER

.*
=ROLE_ANONYMOUS,ROLE_PORTAL_USER


As you see, requests to login and logout URLs are protected and only allowed for us
ers
having role named
ROLE_PORTAL_USER
. Liferay only redirects unauthenticated users to
its login
URL
, and this means

that

they are unauthenticated by Acegi, either. Acegi will send
that users to
our custom
login page
, instead of Liferay’s login page,

and
force
them

to login
before letting them reach at login URL of Liferay. When they are authenticated, Liferay
redirects them to its main entry page.


Many
of Liferay

requests have a prefix of
/c/portal/layout.*

and
/group/.*
.

Liferay expects
those who want t
o reach at resources with that prefix to be authenticated
. Therefore their users
are expected to have role ROLE_PORTAL_USER to be able to acess them. Any
unauthenticated requests will first be redirected to Acegi’s
login

and to the target URL
only
after su
ccessful authentication.

We let all other remaining requests to be reached with any
user.
ROLE_ANONYMOUS

identifies unauthenticated users.


I would like to note that filterSecurityInterceptor tries to authenticate users before every
request check. You can
configure that behaviour with
alwaysReauthenticate

property. This
may cause slow performance unless you don’t cache your user info queried by Acegi from
corresponding user realm. Acegi has EHCache suppport by default for such needs.


It is time to explain
some other bean configurations apart from filters. As I said above,
authenticationProcessingFilter handles authentication, and filterSecurityInterceptor handles
protecting URL resources, but they don’t do actual work. Real work is done by
authenticationMan
ager
, and
accessDecisionManager

beans.



<
bean
id
=
"authenticationManager"



class
=
"org.acegisecurity.providers.ProviderManager"
>




<
property
name
=
"providers"
>




<
list
>





<
ref
bean
=
"ldapAuthenticationProvider"
/>





<
ref
bean
=
"anonymousAuthenticationPr
ovider"
/>





<
ref
bean
=
"testingAuthenticationProvider"
/>




</
list
>



</
property
>



</
bean
>


When a request to
/j_acegi_security_check

URL comes in, authenticationProcessingFilter
enter into scene, gets username

(
j_username

request parameter by default)

and password

(
j_password
request parameter by default)

from web request, creates an Authentication token
and calls authenticationManager to perform actual authentication. authenticationManager
bean has several providers, and asks each one if they are able

to authenticate current
Authentication token until one of them accepts it.


We are going to authenticate against an LDAP user realm,
so we

configured

ldapAuthenticationProvider for this purpose. The other providers,
anonymousAuthenticationProvider
, and
te
stingAuthenticationProvider

beans are for
authenticating anonymous requests, and running standalone integration tests respectively.
Please note that, you shouldn’t configure a testingAuthenticationProvider in a production
environment.



<
bean
id
=
"accessDe
cisionManager"



class
=
"org.acegisecurity.vote.AffirmativeBased"
>




<
property
name
=
"allowIfAllAbstainDecisions"
>




<
value
>
false
</
value
>



</
property
>




<
property
name
=
"decisionVoters"
>




<
list
>





<
ref
bean
=
"roleVoter"
/>




</
list
>



</
property
>



</
bean
>



<
bean
id
=
"roleVoter"
class
=
"org.acegisecurity.vote.RoleVoter"
/>


When our web request is authenticated, the second step is authorization of it. As we
mentioned above filterSecurityInterceptor is configured to authorize URL resources. Actually,
it
asks its configured
accessDecisionManager

bean to authorize current user
.
Acegi Security
Framework provides several

different

AccessDecisionManager

types, each for different
purposes. We configured an AffirmativeBased accessDecisionManager bean here, which

grants access if any of its decisionVoter objects returns an affirmative response.



Many of
remaining bean configurations are there for LDAP a
ccess. I wont go into detail about
how to configure LDAP access, but shortly explain the directory structure her
e, so that it will
be clear from which directory object Acegi
queries for users during login, and extracts user
roles etc.


I use Open LDAP as Directory Server and
JX
plorer to access and modify it.

As you see
below, Our base DN is
dc=ksevindik,dc=com
.






There are individual

users

defined under ksevindik
organization

directory object, and one
object with
organizationalUnit

type is created for defining roles.




Each user has
inetOrgPerson
,
organizationalPerson
, and
person

types.




Under groups organi
zationalUnit object, we define
d
roles

(portal_user, reader and writer)

and
assign
ed

users to those roles. Each role object is of type
groupOfNames

and has one or more
member

attribute
s

having user
s’ distinguished names

as values.



You may have already no
ticed that we didn’t do any configuration on Liferay Portal.
The
entire

configuration done so far is related with Acegi Security.
You just need to copy related
jars
, login.jsp, accessDenied.jsp, acegi_logo.jpg, liferay_logo.jpg

files
, and only change
web.x
ml as mentioned previously.
Let’s now execute authentication process on our sample
configur
ation and see the outcome.




We typed
http://localhost:8080/web/guest/home

URL into our address bar. As
/web/
guest/home is defined accessible by anonymous users Acegi let us to reach at that page.

In other words, it is a publicly accessible page conforming to usual Liferay behaviour.


Let’s click Sign In on the top left corner.

As you see below, we have reached a
t our custom
login page instead of Liferay’s own login page. This is because when we click at Sign In link,
Liferay redirects us to /c/portal/login URL which is currently
protected by

Acegi;

it is only
allowed to access by users having roles ROLE_PORTAL_US
ER. As current web request is
anonymous, that is unauthenticated Acegi sends us to its own login page for authentication.









We typed username and password, and clicked Login. As this is the first time for
testuser

to
log into Liferay Portal, it sh
owed us itsTerms of Use page. This means we have successfuly
logged into Liferay Portal!

Let’s accept it and go to our default home.




Propagating Acegi SecurityContext From Liferay Portal To Portlet
Web Applications


The first part of the solution is tr
ivial and there is no special thing done apart from usual
Acegi Security configuration process in a typical
JEE
web application. We even didn’t touch
at Liferay Portal’s internal configurations.


I will give details about the second part of the solution in

this section. The second part is
about propagating SecurityContext from portal to
individual

portlets. As I mentioned at the
Solution section, each portlet container has its own way of invoking their portlets. They
usually perform inter ServletContext com
munication with a well known servlet instance in
those portlet web applications.

Intercepting Portlet Calls


Liferay caches each deployed portlet instance by wrapping them with its
com.liferay.port
let.CachePortlet

class, and each call
to
processAction

and
render

methods
always passes from those CachePortlet instances.

I created an aspect to intercept those
processAction and render methods and placed currently available SecurityContext object
i
n
to
HttpServletRequest object

with key
HttpSessionContextIntegrat
ionFilter.ACEGI_SECURITY_CONTEXT_KEY.

It is possible
to access actual HttpServletRequest instances from
Liferay’s own implementation of
PortletRequest classes.

before
() :
execution
(

public

void

com.liferay.portlet.CachePortlet.processAction(..))

{






O
bject[] args =
thisJoinPoint
.getArgs();



ActionRequest request = (ActionRequest) args[0];



ActionRequestImpl actionReqImpl = (ActionRequestImpl) request;




actionReqImpl.getHttpServletRequest().setAttribute(











HttpSessionContextIntegrationFilter
.ACEGI_SECURITY_CONTEXT_KEY,





SecurityContextHolder.getContext());



}



before
() :
execution
(

public

void

com.liferay.portlet.CachePortlet.render(..))

{






Object[] args =
thisJoinPoint
.getArgs();



RenderRequest request = (RenderRequest)args[0];



RenderRequestImpl renderReqImpl = (RenderRequestImpl)request;



renderReqImpl.getHttpServletRequest().setAttribute(





HttpSessionContextIntegrationFilter.ACEGI_SECURITY_CONTEXT_KEY,





SecurityContextHolder.getContext());


}


CachePortlet class is conta
ined in portal
-
ejb.jar.
We need to weave that jar with A
spectJ

compiler

and replace that weaved jar with old one in ROOT/WEB
-
INF/lib folder.




<target

name=
"weave
-
portal
-
ejb"
>



<iajc

debug=
"true"


sourceroots=
"${aspects
-
src
-
code
-
dir}/com/liferay/portal/s
ecurity/aspects"





inpath=
"portal
-
ejb.jar"





outjar=
"portal
-
ejb
-
weaved.jar"
>





<classpath

refid=
"all
-
lib"

/>




<classpath>





<fileset

dir=
"D:/work/tools/liferay
-
portal
-
tomcat/webapps/ROOT/WEB
-
INF/lib"
>






<include

name=
"*.jar"
/>





</fileset>




</classpath>



</iajc>


</target>

Retrieving SecurityContext on Portlet Side


On portlet web application side we need to extract that request attribute and place it again into
SecurityContextHolder. In order to do this we extend
Liferay’s
com.liferay.por
tal.kernel.servlet.PortletServlet

because this is the class of well known
servlet instances defined in portlet web applications.


public

class

SecurityContextAwarePortletServlet
extends

PortletServlet {



private

Log
logger

= LogFactory.
getLog
(getClass());



public

void

service(HttpServletRequest req, HttpServletResponse res)




throws

IOException, ServletException {




initializeSecurityContext(req);



super
.service(req, res);


}



private

void

initializeSecurityContext(HttpServletRequest req) {



try

{




Object obj = req.getAttribute(

HttpSessionContextIntegrationFilter.
ACEGI_SECURITY_
CONTEXT_KEY
);





if
(obj !=
null
) {





obj = serializeDeserialize(obj);





if
(
logger
.isDebugEnabled()) {






logger
.debug(

"SecurityContext object found :"

+ obj);





}












SecurityContextHolder.
setContext
(

(SecurityContext)obj);




}
else

{





if
(
logger
.isDebugEnabled()) {






logger
.debug(

"SecurityContext object not found."
);





}




}



}
catch

(Exception ex) {




logger
.error(

"Exception during security con
text processing"
, ex);



}


}




private

Object serializeDeserialize(Object obj)

throws

IOException, ClassNotFoundException {



ObjectInputStream in =
null
;



ObjectOutputStream out =
null
;



try

{




ByteArrayOutputStream bout =
new

ByteArrayOutputStream
();




out =
new

ObjectOutputStream(bout);




out.writeObject(obj);




ByteArrayInputStream bin =

new

ByteArrayInputStream(bout.toByteArray());




in =
new

ObjectInputStream(bin);




obj = in.readObject();




return

obj;



}
finally

{




try

{





out.clo
se();




}
catch
(Exception ex) {}




try

{





in.close();




}
catch
(Exception ex) {}



}


}

}



Integrating JBoss Portal with Acegi Security Framework


Our solution applies to JBoss portal, too. As it is stated at the beginning of this document,
with a s
mall effort
,

Acegi Security Framework can easily be
fully integrated with

any JSR
-
168 compatible portal
.

Let’s walk over the steps that are needed for JBoss Portal.

JBoss Authentication and Creating
a

Valid Acegi Authentication
Token




JBoss Portal uses
web container security to perform its authentication.
It uses form based
authentication.
You can configure LDAP as authentication backend and as user realm. Acegi
Security Framework provides container adapters to delegate its authentication to web
containe
rs. However, this approach has several drawbacks, including necessity for copying
acegi
-
security.jar and several of its dependents into common library folders in container.




In our solution we let JBoss perform its own authentication using web container

security. It
will use LDAP as authentication backend. After a user authenticated, JBoss places a valid
Subject entity into JNDI with name “env/security/subject”.


We will look up JNDI for the Subject, create a valid Acegi Authentication using that Subject

entity, and place that Authentication token into SecurityContextHolder.


Configuring JBoss
Portal
to use LDAP


Steps to configure LDAP are as follows.




Change
conf/identity/identity
-
config.xml

entry into
conf/identity/ldap_identity
-
config.xml

in
server
\
d
efault
\
deploy
\
jboss
-
portal.sar
\
META
-
INF
\
jboss
-
service.xml

file, and
modify ldap_identity
-
config.xml file to according to your LDAP settings.



Modify
server
\
default
\
deploy
\
jboss
-
portal.sar
\
conf
\
login
-
config.xml

file. First
comment
login
-
module

element with
c
ode="org.jboss.portal.identity.auth.IdentityLoginModule"

attribute. After that,
uncomment
login
-
module

element with
code="org.jboss.portal.identity.auth.SynchronizingLDAPExtLoginModule"
attribute. Finally update that login
-
module’s configuration to reflec
t your LDAP
settings. This

login
-
module is used to sync JB
oss user and role tables with LDAP
entries.

Please be careful that, LDAP entry which corresponds to
admin

role must have cn=”Admin”
value.
First letter “A” must be
upper case

here
.

Configuring Acegi

Security in
JBoss
Portal
-
Server Web Application


This part is actually optional. It is not necessary to propagate SecurityContext to portlet web
applications. You may need this part when you need to configure Acegi in portal web
application of JBoss.


In
order to run Acegi Security Framework in portal
-
server web application, we first need to
define Spring’s ContextLoaderListener to create application context during portal startup. Just
add following part into WEB
-
INF/web.xml file.


<
context
-
param
>



<
param
-
name
>
contextConfigLocation
</
param
-
name
>


<
param
-
value
>



classpath:/appcontext/spring
-
beans.
acegi
-
portle
t
-
jboss
.xml



</
param
-
value
>

</
context
-
param
>


<
listener
>


<
listener
-
class
>

org.springframework.web.context.ContextLoaderListener

</
listener
-
class
>

</
l
istener
>


After that configuration, we need to add filter definition into web.xml.

And add its filter
mapping as the first mapping in the web.xml file.



<
filter
>



<
filter
-
name
>
AcegiSecurityFilter
</
filter
-
name
>



<
filter
-
class
>

org.acegisecurity.util.Filt
erToBeanProxy

</
filter
-
class
>



<
init
-
param
>




<
param
-
name
>
targetBean
</
param
-
name
>




<
param
-
value
>
filterChainProxy
</
param
-
value
>



</
init
-
param
>


</
filter
>


<
filter
-
mapping
>




<
filter
-
name
>
AcegiSecurityFilter
</
filter
-
name
>




<
url
-
pattern
>
/*
</
url
-
pattern
>

</
filter
-
mapping
>



FilterToBeanProxy

just delegates calls to
targetBean

defined in ApplicationContext. By
that way we are able to define Filter instances as Spring managed beans.


Let’s look at spring
-
beans.acegi
-
portlet
-
jboss.xml file.
I have de
veloped
org.acegisecurity.portlet.jboss.auth.JbossIntegrationFilter to query JNDI for a valid Subject
entity, and use it to form a valid Acegi Authentication token. By that way, Acegi doesn’t
involve in authentication process at first place, and only waits

for a valid Subject to be placed
into JNDI tree.



<
bean
id
=
"filterChainProxy"



class
=
"org.acegisecurity.util.FilterChainProxy"
>



<
property
name
=
"filterInvocationDefinitionSource"
>




<
value
>





CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON





PATTERN_TY
PE_APACHE_ANT





/**=jbossIntegrationFilter,anonymousProcessingFilter




</
value
>



</
property
>


</
bean
>




<
bean
id
=
"jbossIntegrationFilter"



class
=
"org.acegisecurity.portlet.jboss.auth.JbossIntegrationFilter"
>



<
property
name
=
"securityContextPopulator
"
>




<
ref
local
=
"

securityContextPopulator"
/>



</
property
>


</
bean
>




<
bean
id
=
"securityContextPopulator"
class
=
"org.acegisecurity.portlet.jboss.context.SecurityContextPopulator"
>



<
property
name
=
"subjectToAuthenticationTransformer"
>




<
ref
local
=
"sub
jectToAuthenticationTransformer"
/>



</
property
>


</
bean
>



<
bean
id
=
"subjectToAuthenticationTransformer"
class
=
"org.acegisecurity.portlet.jboss.context.SubjectToAuthenticatio
nTransformer"
>

</
bean
>


As you see there are only two filters configured in
filt
erChainProxy
. First is
jbossIntegrationFilter
, and the second is
anonymousProcessingFilter
.
jbossIntegrationFilter

uses
securityContextPopulator

and
subjectToAuthenticationTransformer

beans to perform its job. If there isn’t any valid
Authentication token
placed into SecurityContextHolder, anonymousProcessingFilter puts an
AnonymousAuthentication token into it.


Add following jars into WEB
-
INF/lib folder.


acegi
-
portlet.jar

acegi
-
security.jar

cglib
-
nodep.jar

commons
-
codec.jar

commons
-
digester.jar

commons
-
di
scovery.jar

commons
-
lang.jar

commons
-
logging.jar

jakarta
-
oro.jar

log4j.jar

spring.jar


Finally, don’t forget to add those JVM parameters
-
Dtarget.platform=dev
-
Duser.language=en
-
Duser.country=US

appropriate point in bin
\
run.bat file.

Propagating Acegi Sec
urityContext from
JBoss

Portal to Portlet
Web Applications


JBoss portal uses PortletContainerImpl class to wrap any deployed portlet instance and
invokes it later using that PortletContainer instance. If we are able to intercept just before this
portlet
invocation, obtain a valid Authentication using JNDI registered Subject, and then
populate SecurityContextHolder with that Authentication object.


The important point here is class loading issues. We dont want to copy acegi
-
security and any
of its related
jars into shared folders of JBoss. Each portlet web application, and portal
application (portal
-
server.war) itself should posses their copies of those jars. By that way, we
will have complete isolation between jars versions of those applications. However,
when we
keep jars at web application classloader level, then our aspect which intercepts
PortletContainerImpl.dispatch(..), which is contained by portal
-
portlet
-
lib.jar located in
\
server
\
default
\
deploy
\
jboss
-
portal.sar
\
lib

folder, will have problem at acc
essing those classes
such as Acegi’s Authentication, and SecurityContextHolder classes in portal web
application’s WEB
-
INF/lib folder. We need to load them, using web application’s classloader,
and execute necessary logic to propagate SecurityContext into
portlet application.



PortletInvocationResponse
around
(PortletInvocation invocation) :



execution
(
public

PortletInvocationResponse
org.jboss.portal.portlet.impl.jsr168.PortletContainerImpl.dispatch(..)) &&
args
(invocation) {






PortletContainer portle
tContainer =
(PortletContainer)
thisJoinPoint
.getTarget();






try

{




propagateSecurityContextToPortlet(

portletContainer.getApplication());



}
catch
(Exception ex) {




//just ignore to let execution of target continue...



}






PortletInvocationRespo
nse response =
proceed
(invocation);






try

{




clearSecurityContextToPortlet(

portletContainer.getApplication());



}
catch
(Exception ex) {




//just ignore...



}






return

response;


}


As you see from above code piece, we have an around advice for
PortletContainerImpl.dispatch(..)

method. Before letting target proceed, we

propagate
SecurityContext from portal to portlet web application’s SecurityContextHolder. When the
execution of method completes, we need to clear SecurityContextHolder of that por
tlet web
application.


You need to replaced weaved portal
-
portlet
-
lib.jar with old one in
server
\
default
\
deploy
\
jboss
-
portal.sar
\
lib

folder, and put aspectjrt.jar into that folder as well.


Caution:

If it is suitable for you to

place related jars into comm
on library folder
(
server
\
default
\
deploy
\
jboss
-
portal.sar
\
lib
) of JBoss Portal, then you don’t need this aspect.
All you need to enable Acegi Security in your portal
-
server.war web application as explained
above.
If you choose this approach you must remov
e acegi
-
security.jar from your portlet web
applications own WEB
-
INF/lib folder, and JBoss portal web application’s WEB
-
INF/lib
folder.

Otherwise, SecurityContext couldn’t be transferred from one SecurityContextHolder
(loaded by web app classloader of porta
l
-
server.war) to another one (loaded by a portlet web
app classloader).

There must be only one copy of
a
cegi
-
security.jar in shared lib folder.

Acegi Authorization
within

Portlets

Protecting Portlet Resources


As I said at the
beginning
, there is no URL re
source like concept in portlets. Therefore we
need to devise another means to authorize incoming portlet requests. One
possibility

is to use
portlet modes. We
might

intercept portlet requests before they arrive at their final portlet
destination and check
if currently authenticated user has access rights for the current portlet
mode.


Spring Portlet MVC provides a mechanism to intercept portlet requests.

I won

t give a
detailed explanation of Spring Portlet MVC here.
Y
o
u

may look at Spring Application
Frame
work’s Reference Documentation for it.


In short, Spring Portlet MVC is built around
DispatcherPortlet
. DispatcherPortlet resolves
incoming portlet requests to appropriate
Handlers

based on
hand
ler mappings

defined in its
WebA
pplicationContext.



Inside t
hose handler mappings, we can define interceptors that implements
org.springframework.web.portlet.HandlerInterceptor

interface, which can pre or post
process portlet requests.

One out of the box handler mapping class is
org.springframework.web.portlet.hand
ler.PortletModeHandlerMapping

by which we are
able to map portlet requests to appropriate handlers based on portlet modes.


Spring Portlet MVC provides us with a way to

map requests to already existing portlets
with
its

org.springframework.web.portlet.mvc
.PortletWrappingController

class.

By that way
we can easily configure our already existing JSF Portlet within Spring Portlet MVC
environment.


I have created a
PortletSecurityInterceptor

class which implements HandlerInterceptor
interface mentioned above.
PortletSecurityInterceptor is
devised to be
configured as

a pre
-
processor in
interceptor
s

list of portletModeHandlerMapping bean. It

expects
portletMode=role* key v
alue pairs as input parameter, and checks current portlet request’s
portlet mode against the
m. Current user is only allowed if he/she has role which is listed in
those pairs.

Otherwise, request processing is cancelled, and accessDeniedPage is
rendered
instead.



<
bean
id
=
"portletSecurityInterceptor"
class
=
"org.acegisecurity.portlet.interceptors.P
ortletSecurityInterceptor"
>



<
property
name
=
"portletModeRoleMappingSource"
>




<
value
>





VIEW=ROLE_READER,ROLE_WRITER





EDIT=ROLE_WRITER




</
value
>



</
property
>



<
property
name
=
"accessDeniedPage"
>




<
value
>
/accessDenied.jsp
</
value
>



</
property
>


</
bean
>



For example, in the above bean configuration users with ROLE_READER or
ROLE_WRITER roles will be able to access portlet resource when current portlet mode is
VIEW. On the other hand, only users with ROLE_WRITER role will be able to access portlet

resource when current portlet mode is EDIT.

accessDenied.jsp will be shown to other users.



<
bean
id
=
"portletModeHandlerMapping"
class
=
"org.springframework.web.portlet.handler.PortletModeHandlerMapping"
>



<
property
name
=
"interceptors"
>




<
list
>





<
re
f
bean
=
"portletSecurityInterceptor"
/>




</
list
>



</
property
>



<
property
name
=
"portletModeMap"
>




<
map
>





<
entry
key
=
"view"
value
-
ref
=
"basicJspPortlet"
/>





<
entry
key
=
"edit"
value
-
ref
=
"basicJspPortlet"
/>




</
map
>



</
property
>


</
bean
>


We wire our

portletSecurityInterceptor into portletModeHandlerMapping. Dispatcher
Portlet
uses this handlerMapping to resolve current portlet request to its destination. Our
portletSecurityInterceptor is applied as pre
-
processor in this case.

Appropriate requests with

VIEW or EDIT mode is targeted to our basicJspPortlet.



<
bean
id
=
"basicJspPortlet"
class
=
"org.springframework.web.portlet.mvc.PortletWrappingController"
>



<
property
name
=
"portletClass"
>




<
value
>
com.ksevindik.acegi.portlet.samples.basicjsp.BasicJspPortl
et
</
value
>



</
property
>



<
property
name
=
"useSharedPortletConfig"
>




<
value
>
false
</
value
>



</
property
>



<
property
name
=
"portletName"
>




<
value
>
basicJspPortlet
</
value
>



</
property
>


</
bean
>


Let’s see our testuser in action.






We logged in as tes
tuser, and clicked Add Content to add our Basic JSP Portlet

which is
located in Acegi Portlet Integration Samples category. When you add basicJspPortlet, you
will see the message “
You don’t have access rights for this portlet!
” in its window. This is
becau
se testuser is only a member of
portal_user

group in LDAP, which corresponds to
ROLE_PORTAL_USER for Acegi
,

and this is only enough to login to portal itself. H
owever
our portlet requires users to have ROLE_READER or ROLE_WRITER roles, which
correspond to
reader and writer groups in LDAP respectively, during view mode.


Let’s add our user to reader group in LDAP, refresh our page and see the result.






As you see in the above snapshot we are now able to reach at our basicJspPortlet, and see its
welcome
message. We also see current username in that page. At the bottom of page we see
another message which says; “
You would see Edit link here if you had appropriate
permission!
” Let’s look at basicJspPortlet’s JSP source to understand what is going on.


<%@
t
aglib
uri
=
"http://acegisecurity.org/authz"
prefix
=
"authz"
%>

<%@
taglib
uri
=
"http://java.sun.com/portlet"
prefix
=
"portlet"
%>

<
h3
>
Welcome To Acegi Portlet Integration Samples
</
h3
>

<
b
>
User:
</
b
><
authz:authentication
operation
=
"username"
></
authz:authentication
>

<
br
>

<
br
>

<
authz:authorize
ifAllGranted
=
"ROLE_WRITER"
>

<
a
href
=
"
<
portlet:renderURL

portletMode
=
"Edit"
/>
"
>
Edit
</
a
>

</
authz:authorize
>


<
authz:authorize
ifNotGranted
=
"ROLE_WRITER"
>

<
font
style
=
"font
-
weight: bold; color: red;"
>

You would see Edit link here
if you had appropriate permission!

</
font
>

</
authz:authorize
>


As you may have already noticed it there is an Edit link rendered via portlet:renderURL tag,
but only if current user has ROLE_WRITER role. We use Acegi Taglib to perform such
controls in JSP
pages.

Authz:authorize tag checks if current user is granted to specified
role(s), and renders enclosed content only if so.

If user doesn’t have ROLE_WRITER role,
this time we render previously mentioned warning message in our page. Let’s then add our
test
user into writer group in LDAP and see what is going on.




As you see, currently only ksevindik user is member of writer group. Now we are adding test
user, too.

After adding testuser, you don’t need to re
-
login or restart Liferay. All you need to
do is
refreshing current page!




Okay.We are now able to see Edit link. When we click this link, we will be sent to another
page in our basicJspPortlet, but in EDIT mode. Our portletSecurityInterceptor will only allow
this happen if current
user has

role which

is
l
isted in its mode=role(s) pairs, otherwise it will
show us accessDeniedPage, which we saw when our testuser hadn’t any appropriate role to
view portlet at the
beginning
.

You can test it with taking Edit link’s rendering process outside
of authz:author
ize tag, and see and click it when testuser isn’t listed in writer group.

Method Level Security


I will mention about how to protect service methods using Acegi Security in this section.
Method level security and domain instance level security, which will
be mentioned in the next
section don’t require any arrangement specific to portlet environment.



<
bean
id
=
"methodSecurityInterceptor"



class
=
"org.acegisecurity.intercept.method.aopalliance.MethodSecurityI
nterceptor"
>



<
property
name
=
"validateConfigAttr
ibutes"
>




<
value
>
false
</
value
>



</
property
>



<
property
name
=
"authenticationManager"
>




<
ref
bean
=
"authenticationManager"
/>



</
property
>



<
property
name
=
"accessDecisionManager"
>




<
ref
bean
=
"accessDecisionManager"
/>



</
property
>



<
property
name
=
"objectDefinitionSource"
>




<
ref
bean
=
"objectDefinitionSource"
/>



</
property
>


</
bean
>


Acegi Security uses AOP proxy to wrap specified service beans managed by Spring and
authorize method calls according to
specified

method attributes. methodSecurityIn
terceptor is
defined for this purpose. It uses accessDecisionManager to authorize current method
invocations
.



<
bean
id
=
"autoProxyCreator"



class
=
"org.springframework.aop.framework.autoproxy.BeanNameAutoProxyC
reator"
>



<
property
name
=
"beanNames"
>




<
va
lue
>
*Service
</
value
>



</
property
>



<
property
name
=
"exposeProxy"
>




<
value
>
true
</
value
>



</
property
>



<
property
name
=
"proxyTargetClass"
>




<
value
>
true
</
value
>



</
property
>



<
property
name
=
"interceptorNames"
>




<
value
>

aclManagementInterceptor,

meth
odSecurityInterceptor,

transactionInterceptor

</
value
>



</
property
>


</
bean
>


I use Spring’s
BeanNameAutoProxyCreator

to automatically wrap service beans based on
specified beanNames pattern with interceptor list. You can have a look at Spring’s Reference

Documentation for a detailed explanation related concepts mentioned here.


There are several ways to specify method attributes, such as inside bean configuration file,
using Jakarta Commons Attributes, or Java Annotations if you are able to use Java 5.

I
prefer
to use Java 5 annotations.



<
bean
id
=
"objectDefinitionSource"



class
=
"org.acegisecurity.intercept.method.MethodDefinitionAttributes"
>



<
property
name
=
"attributes"
>




<
ref
local
=
"attributes"
/>



</
property
>


</
bean
>



<
bean
id
=
"attributes"



cla
ss
=
"org.acegisecurity.annotation.SecurityAnnotationAttributes"
/>


public

interface

LibraryService {


@Secured
({
"ROLE_READER"
,
"ROLE_WRITER"
})


public

List<Book> getAvailableBooks();

}


We define allowed roles for this method to be executed using
org.acegis
ecurity.annotation.Secured

annotation of Acegi.

As you see from above sample,
LibraryService.getAvailableBooks() can only be executed with users having
ROLE_READER or ROLE_WRITER roles.

Otherwise you will get
AccessDeniedException
.


You can test method lev
el security out of container using Spring’s
AbstractDependencyInjectionSpringContextTests

class.

It is enough to create a
TestingAuthentication token with appropriate roles and place it into SecurityContextHolder
for Acegi to perform its job. TestingAuthen
ticationToken is validated only by
testingauthenticationProvider which is provided into authenticationManager.



public

void

testGetAvailableBooksForRoleReader() {



Authentication auth =
new

TestingAuthenticationToken(





"portal_user
"
,





"
portal_user
"
,





new

GrantedAuthority[] {

new

GrantedAuthorityImpl(
"ROLE_PORTAL_USER
"
)
});



SecurityContextHolder.
getContext
().setAuthentication(auth);



try {




List<Book> books =
libraryService
.getAvailableBooks();




fail(
"Execution of method should have been f
ailed.
"
);



} catch(AccessDeniedException ex) {



} catch(Exception ex) {

fail(
"This exception is not expected :
"

+
ex.getMessage()
);



}






}

Domain Instance Level Security (ACLs)


It is possible to protect access to individual domain objects using Aceg
i Security Framework.
It is also called as ACL (Access Control Lists) security. Access Control Lists define who
(based one username or roles) can perform what operations (read, write, create, administer) on
a specific domain object.

It is a bit more comple
x part of Acegi compared with other parts we
mentioned above. I will mention about

only

important parts of A
CL Security configuration
here.


public

interface

LibraryService {


@Secured
({
"ROLE_READER"
,
"ROLE_WRITER"
,
"ACL_UPDATE"
})


@Transactional
(propagation
=Propagation.
REQUIRED
)


public

void

updateBook(Book book);

}


LibraryService.updateBook

method can only be executed by users with ROLE_READER
or ROLE_WRITER roles. We already know that as it is covered in method level security. But
there is an additional v
alue exists in the Secured annotation; ACL_UPDATE indicates that
any domain object provided as input parameter to the updateBook method should be checked
against ACL entries and the operation should only be allowed if there
are only appropriate
rights

for
the current user which corresponds to that domain object.


<
bean
id
=
"accessDecisionManager"



class
=
"org.acegisecurity.vote.AffirmativeBased"
>




<
property
name
=
"allowIfAllAbstainDecisions"
>




<
value
>
false
</
value
>



</
property
>




<
property
name
=
"decision
Voters"
>




<
list
>





<
ref
bean
=
"basicRoleVoter"
/>





<
ref
bean
=
"aclReadVoter"
/>





<
ref
bean
=
"aclUpdateVoter"
/>





<
ref
bean
=
"aclDeleteVoter"
/>




</
list
>



</
property
>



</
bean
>


I already mentioned about
accessDecisionManager

in this document, b
ut if you compare
above bean configuration with the previous one, you will notice that there are additional
decisionVoters

defined here. Actually I had removed those from the previous one

intentionally

in order to simplify explanation
of

that section.

aclR
eadVoter
,
aclUpdateVoter
,
and
aclDeleteVoter

beans are
consulted

during

those domain instance level security checks.



<
bean
id
=
"aclUpdateVoter"
class
=
"org.acegisecurity.vote.BasicAclEntryVoter"
>



<
property
name
=
"processConfigAttribute"
>




<
value
>
ACL_UPD
ATE
</
value
>



</
property
>



<
property
name
=
"aclManager"
>




<
ref
bean
=
"aclManager"
/>



</
property
>



<
property
name
=
"requirePermission"
>




<
list
>





<
ref
local
=
"org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"
/>





<
ref
local
=
"org.acegisecur
ity.acl.basic.SimpleAclEntry.WRITE"
/>




</
list
>



</
property
>


</
bean
>


Looking at any of those voters’ bean configuration will help us to understand what is going
on. aclUpdateVoter processes only ACL_UPDATE attribute. This corresponds to what we
have s
een in method decleration of updateBook. It will use
aclManager

bean to fetch any
available

ACL entries for the current domain object, and if there is any ACL entry for it, they
must be WRITE or ADMINISTRATION p
ermissions

corresponding

to

the current user

s
username

or
his owned roles.







<
bean
id
=
"aclManager"
class
=
"org.acegisecurity.acl.AclProviderManager"
>



<
property
name
=
"providers"
>




<
list
>





<
ref
local
=
"basicAclProvider"
/>




</
list
>



</
property
>


</
bean
>




<
bean
id
=
"basicAclProvider"
class
=
"org.acegisecurity.acl.basic.BasicAclProvider"
>



<
property
name
=
"basicAclDao"
>




<
ref
local
=
"basicAclExtendedDao"
/>



</
property
>



<
property
name
=
"basicAclEntryCache"
>




<
ref
local
=
"basicAclEntryCache"
/>



</
property
>


</
bean
>


aclManager

bean is co
nfigured to lookup ACL entries. Above it refers to basicAclProvider
for the search.
basicAclProvider

looks at database for those ACL entries, and caches
previously queried entries against performance issues.

After Invocation Security


It is also possible t
o protect domain objects returned from service methods. This is called as
after invocation security
.


<
bean
id
=
"methodSecurityInterceptor"



class
=
"org.acegisecurity.intercept.method.aopalliance.MethodSecurityI
nterceptor"
>



<
property
name
=
"validateConfigA
ttributes"
>




<
value
>
false
</
value
>



</
property
>



<
property
name
=
"authenticationManager"
>




<
ref
bean
=
"authenticationManager"
/>



</
property
>



<
property
name
=
"accessDecisionManager"
>




<
ref
bean
=
"accessDecisionManager"
/>



</
property
>



<
property
na
me
=
"afterInvocationManager"
>




<
ref
bean
=
"afterInvocationManager"
/>



</
property
>



<
property
name
=
"objectDefinitionSource"
>




<
ref
bean
=
"objectDefinitionSource"
/>



</
property
>


</
bean
>


We define an
afterInvocationManager

and wire it to our
methodSec
urityInterceptor

bean.



<
bean
id
=
"afterInvocationManager"



class
=
"org.acegisecurity.afterinvocation.AfterInvocationProviderManag
er"
>



<
property
name
=
"providers"
>




<
list
>





<
ref
local
=
"afterAclRead"
/>





<
ref
local
=
"afterAclCollectionRead"
/>




</
list
>



</
property
>


</
bean
>



<
bean
id
=
"afterAclRead"



class
=
"org.acegisecurity.afterinvocation.BasicAclEntryAfterInvocation
Provider"
>






<
property
name
=
"aclManager"
>




<
ref
local
=
"aclManager"
/>



</
property
>



<
property
name
=
"requirePermission"
>




<
list
>





<
ref
local
=
"org.acegisecurity.acl.basic.SimpleAclEntry.ADMINISTRATION"
/>





<
ref
local
=
"org.acegisecurity.acl.basic.SimpleAclEntry.READ"
/>




</
list
>



</
property
>


</
bean
>



<
bean
id
=
"afterAclCollectionRead"



class
=
"org.acegisecurity.afteri
nvocation.BasicAclEntryAfterInvocation
CollectionFilteringProvider"
>






<
property
name
=
"aclManager"
>




<
ref
local
=
"aclManager"
/>



</
property
>



<
property
name
=
"requirePermission"
>




<
list
>





<
ref
local
=
"org.acegisecurity.acl.basic.SimpleAclEntry.ADM
INISTRATION"
/>





<
ref
local
=
"org.acegisecurity.acl.basic.SimpleAclEntry.READ"
/>




</
list
>



</
property
>


</
bean
>


afterInvocationManager

refers to
afterAclRead

and
afterAclCollectionRead

beans to
authorize any returned domain objects from service meth
ods. afterAclRead checks if current
user has READ or ADMINISTRATION
permissions

for the single domain object which is to
be returned. If user doesn

t have enough
permission

it throws an
AccessDeniedException
.
afterAclCollectionRead bean checks a Collection

of domain objects and removes any object
that is not allowed to read or administer.


public

interface

LibraryService {


@Secured
({
"ROLE_READER"
,
"ROLE_WRITER"
,
"AFTER_ACL_COLLECTION_READ"
})


public

List<Book> getAvailableBooks();




@Secured
({
"ROLE_READER"
,
"ROLE_WRITER"
,
"AFTER_ACL_READ"
})


public

Book getBook(Long id);

}


AFTER_ACL_COLLECTION_READ and AFTER_ACL_READ config attributes enables
after invocation security for service method calls.

It is also possible to test ACL security
issues out of container u
sing already mentioned Spring integration test classes.


Management of ACL Entries


One of the issues with ACL security is to manage (insert, delete) ACL entries for the domain
objects which are created and deleted during user scenarios. For example, we m
ay have such a
security requirement; any Book object which is privately added to library should only be
accessed by its
owner that

is user who added that book into the library. Similar to creation, we
may need to remove previously created ACL entries becau
se of the removal of corresponding
domain objects from the system, or because of some

other

security requirement.


Acegi provides a BasicAclExtendedDao interface and JdbcExtendedDaoImpl as its concrete
implementation to provide

methods for

management

of AC
L entries in the database.
However, it would be very nice to have a higher level mechanism to decleratively specify
when to create or delete those ACL entries, and automatically create or delete them.


We had developed such a mechanism in one of my previou
s projects. I have revised the
solution and adapted it to support Java 5 annotations. Let’s walk over the solution.


public

interface

LibraryService {


@Secured
({
"ROLE_READER"
,
"ROLE_WRITER"
})


@AclCreate
(recipientMaskPairs={
"ROLE_READER=r"
,
"ROLE_WRITER=rw"
})


@
Transactional
(propagation=Propagation.
REQUIRED
)


public

void

insertBook(Book book);




@Secured
({
"ROLE_READER"
,
"ROLE_WRITER"
})


@AclCreate
(recipientMaskPairs={
"principal=rwc"
})


@
Transactional
(propagation=Propagation.
REQUIRED
)


public

void

insertPriva
teBook(Book book);





@Secured
({
"ROLE_READER"
,
"ROLE_WRITER"
,
"ACL_DELETE"
})


@AclDelete
(processDomainObjectClass=Book.
class
)


@
Transactional
(propagation=Propagation.
REQUIRED
)


public

void

deleteBook(Book book);

}


When a new book object inserted using inse
rtBook method, we declare with
AclCreate

annotation that, users with ROLE_READER will have READ, and users with
ROLE_WRITER will have WRITE permisson on that object.


If we create that object using insertPrivateBook method, then AclCreate annotation decla
res
that only current user(principal) will have READ, WRITE and CREATE permissions, and no
one else will be able to access that book object.


When we delete a book instance from database,
AclDelete

also instructs that its
corresponding ACL entries should a
lso be deleted.
processDomainObjectClass

attribute is
available for both annotations, and is used to specify exactly for what type of domain objects
those annotations are applicable. If not specified they are applicable for all types.







<
bean
id
=
"aclMa
nagementInterceptor"
class
=
"org.acegisecurity.acl.basic.management.

interceptor.AclManagementInterceptor"
>



<
property
name
=
"disabled"
>




<
value
>
false
</
value
>



</
property
>



<
property
name
=
"aclAttributeSource"
>




<
ref
local
=
"aclAttributeSource"
/>



</
pr
operty
>



<
property
name
=
"aclService"
>




<
ref
local
=
"aclService"
/>



</
property
>


</
bean
>



<
bean
id
=
"aclAttributeSource"
class
=
"org.acegisecurity.acl.basic.management.

attributes.AclAttributeSource"
>



<
property
name
=
"attributes"
>




<
ref
local
=
"aclManag
ementAnnotationAttributes"
/>



</
property
>


</
bean
>



<
bean
id
=
"aclManagementAnnotationAttributes"
class
=
"org.acegisecurity.acl.basic.management.

attributes.annotation.AclManagementAnnotationAttributes"
/>


We employ
aclManagementInterceptor

to intercept th
ose service method calls and process
any available AclCreate and AclDelete attributes. Real work is done by aclService.

You can
look at autoProxyCreator bean mentioned previously to see how this interceptor is configured.



<
bean
id
=
"aclService"
class
=
"org
.acegisecurity.acl.basic.management.interceptor.AclServiceImpl"
>



<
property
name
=
"basicAclExtendedDao"
>




<
ref
local
=
"basicAclExtendedDao"
/>



</
property
>



<
property
name
=
"objectAccessDecisionManager"
>




<
ref
bean
=
"accessDecisionManager"
/>



</
property
>


</
bean
>


aclService

bean uses
basicA
clExtendedDao

to perform its database inserts and deletes.
Apart from basic ACL entry insertions and deletetions, aclService provides methods to get
existing ACLs of a domain object, or check if a domain object is upd
ateable or deleteable. It
is also possible to update permission masks of existing ACL entries.


Supported permission mask characters for recipientMaskPairs are as follows;


rR

SimpleAclEntry.READ

wW

SimpleAclEntry.WRITE

dD

SimpleAclEntry.DELETE

aA

Simpl
eAclEntry.ADMINISTRATION

cC

SimpleAclEntry.CREATE

nN

SimpleAclEntry.NOTHING


You can combine them in any order.

ACL Tables and Sample Entries


There are two tables used by ACL infrastructure. There is enough information in Reference
guide of Acegi Secur
ity about how to create and configure those tables in your own database.

Here I want to show sample entries here in order to make the big picture a bit
clearer
.


The first table is acl_object_identity. It keeps which domain objects have ACL entries and
typ
e of those entries.




For example, domain objects with com.ksevindik.acegi.portlet.samples.libraryservice.Book
type with ids 1, 2 and 3 have acl entries in our sample. Notice that object_identity is kept in
className + “:” + id format. It is therefore v
ery important for your domain objects to have
surrogate keys accessible with getId() method.


The second table is called as acl_permission which keeps which permissions
, corresponding
to acl entries in the first table,

are defined for specific users or rol
es.




For example, records with id 1,2 and 3 in acl_object_identity have permission READ (2) for
role ROLE_READER, and permission READ + WRITE (6) for ROLE_READER. Moreover,
acl_object_identity
record

with id 3

has

permission READ + WRITE

for principal t
estuser.


Sample Portlet Web Application


I have created a sample portlet web application to demonstrate all features mentioned in this
document. It contains two sample portlets, namely basicJspPortlet which is a JSP based
portlet and libraryServicePortle
t which is developed using JSF.

Sample application has two
versions, one is for Liferay, and the other is for JBoss. Differeces are at optional portlet
configuration files, faces
-
config.xml, and at web.xml.

I have used Liferay version as the
reference to e
xplain it in this document, and here. There is also a JBoss section at the end of
this part in which I put some important notes to point differences.


I have already mentioned about
first one during authentication and portlet resource
authorization section
s. I will focus on the second portlet, and explain several details related
with
its
configuration using DispatcherPortlet
, use of Acegi JSF components which
correspond to Acegi Taglibs, and show demonstrate ACL related features.


We have following definiti
on in portlet.xml. As you see we configure DispatcherPortlet as
portlet
-
class,
and provide

it with contextConfigLocation to load actual portlet and security
interceptor beans.

This portlet supports two modes: VIEW, EDIT.



<
portlet
>


<
portlet
-
name
>
l
ibraryServicePortlet
</
portlet
-
name
>


<
portlet
-
class
>

org.springframework.web.portlet.DispatcherPortlet

</
portlet
-
class
>


<
init
-
param
>


<
name
>
contextConfigLocation
</
name
>


<
value
>
classpath:/appcontext/libraryServicePortlet.xml
</
val
ue
>


</
init
-
param
>


<
supports
>


<
mime
-
type
>
text/html
</
mime
-
type
>


<
portlet
-
mode
>
view
</
portlet
-
mode
>


<
portlet
-
mode
>
edit
</
portlet
-
mode
>


</
supports
>


<
portlet
-
info
>


<
title
>
Library Service Portlet
</
title
>


</
portlet
-
info
>


</
portlet
>


This is its corresponding servlet definition in web.xml file. We have configured our
SecurityContextAwarePortletServlet as servlet
-
class instead of PortletServlet class. By that
way we guarantee that any available Secur
ityContext object will be set into the
SecurityContextHolder on portlet’s side.



<
servlet
>



<
servlet
-
name
>
libraryServicePortlet
</
servlet
-
name
>



<
servlet
-
class
>

com.liferay.portal.kernel.servlet.SecurityContextAwarePortletServlet

</
servlet
-
class
>



<
init
-
param
>




<
param
-
name
>
portlet
-
class
</
param
-
name
>




<
param
-
value
>

org.springframework.web.portlet.DispatcherPortlet

</
param
-
value
>



</
init
-
param
>



<
load
-
on
-
startup
>
0
</
load
-
on
-
startup
>


</
servlet
>



<
servlet
-
mapping
>



<
servlet
-
name
>
libraryServicePortlet
</
servlet
-
name
>



<
url
-
pattern
>
/libraryServicePortlet/*
</
url
-
pattern
>


</
servlet
-
mapping
>


Each DispatcherPortlet instance has its own WebApplicationContext in addition to parent
WebApplicationContext if available. libraryServicePortlet.xml is the context
file to configure
its own WebApplicationContext.





<
bean
id
=
"libraryServicePortlet"
class
=
"org.springframework.web.portlet.mvc.PortletWrappingController"
>



<
property
name
=
"portletClass"
>




<
value
>

org.apache.myfaces.portlet.MyFacesGenericPortlet

</
valu
e
>



</
property
>



<
property
name
=
"useSharedPortletConfig"
>




<
value
>
false
</
value
>



</
property
>



<
property
name
=
"initParameters"
>




<
props
>





<
prop
key
=
"default
-
view"
>

/libraryServiceView.jsp

</
prop
>




</
props
>



</
property
>



<
property
name
=
"portl
etName"
>




<
value
>
libraryServicePortlet
</
value
>



</
property
>


</
bean
>


We define
libraryServicePortlet

with Spring Portlet MVC
’s

PortletWrappingController

class
. By that way, we are able to make our actual portlet as JSF enabled.

We have also
defined por
tletModeHandlerMapping bean
,

and it resolves portlet requests with VIEW and
EDIT modes to our libraryServicePortlet, just after applying portletSecurityInterceptor.



<
bean
id
=
"portletModeHandlerMapping"
class
=
"org.springframework.web.portlet.handler.Portl
etModeHandlerMapping"
>



<
property
name
=
"interceptors"
>




<
list
>





<
ref
bean
=
"portletSecurityInterceptor"
/>




</
list
>



</
property
>



<
property
name
=
"portletModeMap"
>




<
map
>





<
entry
key
=
"view"

value
-
ref
=
"libraryServicePortlet"
/>





<
entry
key
=
"e
dit"

value
-
ref
=
"libraryServicePortlet"
/>




</
map
>



</
property
>


</
bean
>



<
bean
id
=
"portletSecurityInterceptor"
class
=
"org.acegisecurity.portlet.interceptors.PortletSecurityInterceptor"
>



<
property
name
=
"portletModeRoleMappingSource"
>




<
value
>





VI
EW=ROLE_READER,ROLE_WRITER





EDIT=ROLE_WRITER




</
value
>



</
property
>



<
property
name
=
"accessDeniedPage"
>




<
value
>
/accessDenied.jsp
</
value
>



</
property
>


</
bean
>


portletSecurityInterceptor only allows users with ROLE_READER or ROLE_WRITER roles
to

access in VIEW mode, and only with ROLE_WRITER role in EDIT portlet mode.


Let’s log into Liferay portal and run our libraryServicePortlet. First we login with a user only
having ROLE_READER.




Here is an excerpt from libraryServiceView.jsp;




<
h:outpu
tText
value
=
"Available Books"
></
h:outputText
>



<
h:dataTable
value
=
"#{libraryService.availableBooks}"

var
=
"book"
border
=
"1"
>




...



</
h:dataTable
>

All a
vailable books for this user is listed. Actually, I
had

inserted three sample books

into the
database,

but

w
e are able to see

only two of them;
third one isn’t shown

here
.

If you
remember, t
his is because we employ after invocation security to
getAvailableBooks service

method and that third book is only accessible for users with ROLE_WRITER.

Let’s

add one
public and one private books into the library with testuser. Public books are viewable for all
users with ROLE_READER or ROLE_WRITER
roles. However
, private books are only
accessible by their owners. Hence, when we login with another user even with
ROLE_WR
ITER role, we will not be able to see that privately added book in the list.




Analysis Patterns book was added as
public

and Enterprise Security with Acegi was added as
private. Let’s login with a user having ROLE_WRITER and see the results.




As I st
ated
previously, we

are able to see Secret book which isn’t
accessible

by testuser, but
we cannot see Enterprise Secutiy with Acegi in the list. This is because, that book has an ACL
entry which
permits to only

principal
testuser for

seeing
that book.


As
you may have already noticed that
,

we are not able to see a Next button at the bottom of
the page.




<
acegijsf:authorize
ifAllGranted
=
"ROLE_WRITER"
>




<
h:commandButton
id
=
"btnNextPage"


action
=
"next"
value
=
"Next"
></
h:commandButton
>



</
acegijsf:authorize
>




<
br
>



<
b
>
User:
</
b
><
acegijsf:authentication

operation
=
"username"
></
acegijsf:authentication
>


We are able to achieve this and printing principal name as well with Acegi JSF Components.
Acegi JSF is developed by
Çağatay Çivici,but I modified

to
make

it
work in portlet
environments.


Sample Porlet Web A
pplication JBoss Version


To configure portlet web application to be deployable into JBoss portal, we need to create
portlet
-
instances.xml, and acegisamples
-
object.xml files as optional JBoss files. Por
tlet
-
instances.xml is used to create portlet instances referring to portlet definitions in standard
portlet.xml file, and acegisamples
-
object.xml (*
-
object.xml) is used to place those portlet
instances into a region in portal windows.


There is also no ent
ry for
faces
-
context
-
factory

in faces
-
config.xml file for JSF portlets to be
run inside JBoss portal. However, we need to add a
context
-
param

as explained below into
your web.xml.


You will see a similar window after you deploy our sample into JBoss Portal
. As you see,
both portlets give access denied message as nobody has logged in.




After
you

login with a user having appropriate

access rights, you will see a similar screen to
below screenshot.



Running JSF Portlets in JBoss Portal 2.6.1


JBoss portal

comes bundled with a JSF impl. If you want to use your own implementation,
there are two choices of yours. First is to replace that JSF impl with your own version in
jboss
-
web.deployer
, remove your jars from your WEB
-
INF/lib folder, and use those made as
common. The second way is to keep your own jars, and tell JBoss portal that you will use
your own JSF implemenation for your portlet. In order to tell this, add following context
-
param into your web.xml file.



<
context
-
param
>



<
param
-
name
>

org.jboss.jbossfaces.WAR_BUNDLES_JSF_IMPL

</
param
-
name
>



<
param
-
value
>
true
</
param
-
value
>


</
context
-
param
>