Why use Spring Security (Formerly known as Acegi)?
Acegi Spring Security is a pretty complete solution for all kinds of security needs. It is very configurable and supports many authentication sources out of the box. These include: database queries, LDAP queries and CAS. It can be a bit complex to set up, but following the how to below should get you started quickly.
The example below shows how you can use Acegi Spring Security in combination with Wicket-auth-roles. Acegi Spring Security takes care of the authentication, Wicket-auth-roles does authorization. By this I mean that Acegi Spring Security looks up the user (including roles, full name, etc.), validates the password, and keeps track of the current user in the session. Wicket-auth-roles validates whether the current user has access to a particular page, or even a particular component.
The advantage of this setup is that you get to use a lot of Acegi Spring Security functionality out of the box. There are the mentioned authentication sources, but you can also add security to services that are called by your Wicket application. You do this by adding an Acegi security Spring Security proxy to your spring beans. Acegi's security Spring Security proxies allow role based access (e.g. access granted if you have role x) but can also filter the results of a service call (e.g. remove data from a list that must not be seen by current user). See the Acegi Spring Security manual for more information.
Why not use Spring Security?
A new Wicket project is currently in the works. You can read more about it on http://wicketstuff.org/confluence/display/STUFFWIKI/Wicket-Security. Please investigate whether it will suit your needs better.
For those still wanting to use
...
A new Wicket project is currently in the works. You can read more about it on http://wicketstuff.org/confluence/display/STUFFWIKI/Wicket-Security. Please investigate whether it will suite your needs better.
For those still wanting to use Acegi, there is an howto on getting Swarm working with Acegi http://wicketstuff.org/confluence/display/STUFFWIKI/Swarm+and+Acegi+HowTo
...
Spring Security, there is an howto on getting Swarm working with Spring Security http://wicketstuff.org/confluence/display/STUFFWIKI/Swarm+and+Acegi+HowTo
See the examples below for how to setup your project.
- Wicket 1.3.5, Spring 2.5.5, Spring Security 2.0.4 on JDK 5.0
- Wicket 1.2.6, Spring 2.0.5, Acegi 1.0.2 on JDK 1.4
Example Wicket 1.3.5
This example uses Maven 2 for dependency management.
Maven 2 pom.xml
Code Block | ||||
---|---|---|---|---|
| ||||
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>2.0.4</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-support</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket</artifactId>
<version>${wicket.version}</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-spring</artifactId>
<version>${wicket.version}</version>
<exclusions>
<exclusion>
<artifactId>spring</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-spring-annot</artifactId>
<version>${wicket.version}</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-extensions</artifactId>
<version>${wicket.version}</version>
</dependency>
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-auth-roles</artifactId>
<version>${wicket.version}</version>
</dependency>
|
A nasty thing of Spring security is that is brings in its own Spring version 2.0.8. Since we want 2.5.5 we need to exclude it.
Spring Security setup
Since Spring Security has a very exhaustive documentation available I refer to the manual for in depth information about Spring Security. This example merely shows how to configure Spring Security with Wicket.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
<filter-name>wicket.filter</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>wicket.filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
|
It is important to add springSecurityFilterChain mapping higher in code than the Wicket filter mappin. Wicket filter is only passing filter call down by filter chain if it is unable to handle request itself.
Spring security version 3 and wicket 1.4
Code Block | ||||
---|---|---|---|---|
| ||||
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>example</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext-security.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>wicket.example</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>org.wicket.example.WicketApplication
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>wicket.example</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
|
Spring 2 context
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
<bean id="myApplication" class="com.foo.bar.MyApplication" />
<bean id="filterChainProxy" class="org.springframework.security.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter
</value>
</property>
</bean>
<bean id="httpSessionContextIntegrationFilter"
class="org.springframework.security.context.HttpSessionContextIntegrationFilter">
<property name="allowSessionCreation" value="false"/>
</bean>
<security:authentication-provider alias="authenticationManager">
<security:user-service>
<security:user password="admin" name="admin" authorities="ROLE_ADMIN" />
</security:user-service>
</security:authentication-provider>
</beans>
|
Spring 3 context
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<security:http create-session="never" auto-config="true" >
<security:remember-me/>
<security:intercept-url pattern="/**"/>
</security:http>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider>
<!-- TODO change this to reference our real user service -->
<security:user-service>
<security:user name="admin" password="admin"
authorities="ROLE_ADMIN, ROLE_USER" />
<security:user name="user" password="user"
authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
<security:global-method-security secured-annotations="enabled" />
</beans>
|
The only filter we need defined from Acegi is the HttpSessionContextIntegrationFilter. This filter will ensure that the SecurityContext is transported to and from the HttpSession onto the Thread context. All authorization is delegated to the wicket-auth-roles module which uses Annotations (@AuthorizeInstantiation).
Using the authentication-provider XML element we register an AuthenticationManager in the Spring context. In this case we use a simple in-memory user service using the user-service element.
Wicket setup
WebSession
Code Block | ||
---|---|---|
| ||
public class MyAuthenticatedWebSession extends AuthenticatedWebSession {
private static final Logger logger = Logger.getLogger(MyAuthenticatedWebSession.class);
@SpringBean(name="authenticationManager")
private AuthenticationManager authenticationManager;
public MyAuthenticatedWebSession(Request request) {
super(request);
injectDependencies();
ensureDependenciesNotNull();
}
private void ensureDependenciesNotNull() {
if (authenticationManager == null) {
throw new IllegalStateException("AdminSession requires an authenticationManager.");
}
}
private void injectDependencies() {
InjectorHolder.getInjector().inject(this);
}
@Override
public boolean authenticate(String username, String password) {
boolean authenticated = false;
try {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
SecurityContextHolder.getContext().setAuthentication(authentication);
authenticated = authentication.isAuthenticated();
} catch (AuthenticationException e) {
logger.warn(format("User '%s' failed to login. Reason: %s", username, e.getMessage()));
authenticated = false;
}
return authenticated;
}
@Override
public Roles getRoles() {
Roles roles = new Roles();
getRolesIfSignedIn(roles);
return roles;
}
private void getRolesIfSignedIn(Roles roles) {
if (isSignedIn()) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
addRolesFromAuthentication(roles, authentication);
}
}
private void addRolesFromAuthentication(Roles roles, Authentication authentication) {
for (GrantedAuthority authority : authentication.getAuthorities()) {
roles.add(authority.getAuthority());
}
}
}
|
Spring 2 Application
Code Block | ||
---|---|---|
| ||
public class MyWebApplication extends AuthenticatedWebApplication implements ApplicationContextAware {
private ApplicationContext context;
boolean isInitialized = false;
@Override
protected void init() {
if (!isInitialized) {
super.init();
setListeners();
isInitialized = true;
}
}
private void setListeners() {
addComponentInstantiationListener(new SpringComponentInjector(this, context));
}
@Override
public Class<?> getHomePage() {
return HomePage.class;
}
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
@Override
protected Class<? extends WebPage> getSignInPageClass() {
return LoginPage.class;
}
@Override
protected Class<? extends AuthenticatedWebSession> getWebSessionClass() {
return MyAuthenticatedWebSession.class;
}
}
|
Spring 3 Application
Code Block | ||
---|---|---|
| ||
public class MyWebApplicationSpring3 extends AuthenticatedWebApplication {
boolean isInitialized = false;
@Override
protected void init() {
if (!isInitialized) {
super.init();
setListeners();
isInitialized = true;
}
}
private void setListeners() {
addComponentInstantiationListener(new SpringComponentInjector(this));
}
@Override
public Class<?> getHomePage() {
return HomePage.class;
}
@Override
protected Class<? extends WebPage> getSignInPageClass() {
return LoginPage.class;
}
@Override
protected Class<? extends AuthenticatedWebSession> getWebSessionClass() {
return MyAuthenticatedWebSession.class;
}
}
|
Code Block | ||
---|---|---|
| ||
class LoginForm extends Form {
private String username;
private String password;
public LoginForm(String id) {
super(id);
setModel(new CompoundPropertyModel(this));
add(new RequiredTextField("username"));
add(new PasswordTextField("password"));
}
@Override
protected void onSubmit() {
AuthenticatedWebSession session = AuthenticatedWebSession.get();
if(session.signIn(username, password)) {
setDefaultResponsePageIfNecessary();
} else {
error(getString("login.failed"));
}
}
private void setDefaultResponsePageIfNecessary() {
if(!continueToOriginalDestination()) {
setResponsePage(getApplication().getHomePage());
}
}
}
|
Code Block | ||
---|---|---|
| ||
@AuthorizeInstantiation("ROLE_ADMIN")
public class SecuredPage extends WebPage {
//omitted, since not relevant
}
|
As stated before the above code examples show the very minimal and a basic setup of how to configure Spring Security with Wicket. For more advanced configuration regarding authentication-provider mechanisms I refer to Spring Security manual.
Example Wicket 1.2.6
This example is extracted from a production system running on Wicket 1.2.6, Spring 2.0.5 and Acegi 1.0.2 on a 1.4 JVM. Wicket-auth-roles requires Java 5, but is quite simple to port it to Java 1.4 by removing everything related to annotations. If you do not want to port Wicket-auth-roles, this example requires that you use Java 5.
Classpath
The code in this HOWTO works with the following jars. Newer and older versions probably work, but you'll have to try to find out.
...
In addition, you'll need to inject some Spring beans into your wicket classes. This example assumes (but does not show) you are injecting these into the application. You'll need more jars for this. See here for more information on integrating Wicket with Spring.
Acegi basic setup
First of all you need to set up Acegi. Somewhere in your Spring configs add:
...
Note |
---|
TODO: describe how roles are structured in LDAP tree. |
Wicket setup
Your application's session should extend wicket.authentication.AuthenticatedWebSession
from Wicket-auth-roles instead of Wicket's WebSession.
...
In order to weave the extended session into you your Wicket application, your application should extend Wicket-auth-roles's Authenticated AuthenticatedWebApplication. (See method getWebSessionClass
below.) The other function of the appliction instance is to configure which components need authentication. There are 2 ways to do so. This example runs on Java 1.4 and therefore you can only use the {MetaDataRoleAuthorizationStrategy}}. If you are running on Java 5 or higher, you can also use the annotations based approach. Look at the source code in Wicket-auth-roles-example for inspiration.
...
No Format |
---|
login.errors.alreadysignedin=You are already singed in, please singout if you which to sign as another user. login.errors.invalidCredentials=Invalid user name and/or password. |
Hiding components
We have now seen how to completely disallow access to a page (or any component) and redirect to the signin page as soon as it is accessed. I will now show how to hide components on a page depending on the roles of the user.
Note |
---|
TODO: finish this section with a menu as example |
Questions?
Please send questions to the wicket user mailing list.