this piece of work is done by now. Here are my experiences so far. In most cases one can take Spring Security and use it as given. There are plenty of providers and mechanisms, that are supported (like LDAP, JAAS, CAS, digest, form authentication etc.) and would fulfil one's needs in 90% of cases. When it gets tough? Like in our case, when you e.g. would like to use some custom security providers or load access definitions from a file, describing your access rules in some other way.
Back again to a normal case... The new feature presented in Spring Security is a new configuration syntax, which allows you to skip defining all the beans in a Spring manner. A minimal configuration looks like:
Code...
<http config="true">
<intercept-url pattern="/**" access="ROLE_USER"/>
</http>
Looks fine doesn't it? Than you have to specify an authentication provider to map a user with a valid password to the roles. And that's it. So what is when you'd like to override some filter in the filter chain? Or to define a custom ports mapping for HTTP and HTTPS? Or would like to use a custom JSF login page? I'll tell you. You end very soon by good old ACEGI Security configuration syntax with beans.
There was one issues that posed special degree of difficulty: how do I configure a custom JSF login page using Spring Security authentication mechanism without having to reimplement the whole authentication logic? The solution is to use Spring Security j_spring_security_check.jsp as a filter process URL and correctly map the username and password fields to be forwarded as request parameters. It looks something like this:
Code...
<beans:bean id="authenticationProcessingFilter" class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter">
<beans:property name="filterProcessesUrl">
<beans:value>/j_spring_security_check.jsp</beans:value>
<beans:property>
<!-- very important: use here the ids of your login form as values to get the credentials passed to spring securty authentication processing servlet -->
<beans:property name="usernameParameter">
<beans:value>login_form:username</beans:value>
</beans:property>
<beans:property name="passwordParameter">
<beans:value>login_form:password</beans:value>
</beans:property>
<beans:property name="authenticationFailureUrl">
<beans:value>/login.jsf</beans:value>
</beans:property>
<beans:property name="defaultTargetUrl">
<beans:value>/home.jsf</beans:value>
</beans:property>
<beans:property name="authenticationManager">
<beans:ref bean="authenticationManager"/>
</beans:property>
</beans:bean>
It will only work in connection with the login JSF designed as follows:
Code...
<h:form id="login_form">
...
<h:inputText value="#{loginView.userName}" id="username" required="true"/>
<h:inputSecret value="#{loginView.password}" id="password" required="true"/>
...
and not to forget the navigation rule in the faces config:
Code...
<navigation-rule>
<from-view-id>/login.jsp</from-view-id>
<navigation-case>
<from-outcome>login</from-outcome>
<to-view-id>/j_spring_security_check.jsp</to-view-id>
</navigation-case>
</navigation-rule>
and the last point - the JSF backing bean to map username and password input from the login page, which is defined as a managed bean in faces config:
Code...
<managed-bean>
<managed-bean-name>loginView</managed-bean-name>
<managed-bean-class>
myapp.beans.view.LoginView
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
The java class LoginView contains only getter and setter for username and password and an exception handler to forward a failed login message from JSF to Spring:
Code...
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import org.springframework.security.ui.AbstractProcessingFilter;
public class LoginView {
private String userName;
private String password;
public LoginView() {
try {
Exception ex = (Exception) FacesContext
.getCurrentInstance()
.getExternalContext()
.getSessionMap()
.get(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY);
if (ex != null) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR,
ex.getMessage(),
ex.getMessage()));
}
} catch (NullPointerException ex) {
// if no faces context present, e.g. when called from a unit test
}
}
public String getPassword() {
return password;
}
public void setPassword(String aPassword) {
password = aPassword;
}
public String getUserName() {
return userName;
}
public void setUserName(String aUserName) {
userName = aUserName;
}
}
Another interesting topic is localization. How to get localized login error messages displayed if locale is managed in JSF. I'll write on it some other time...
2 comments:
thx! that really helped me a lot!
my pleasure
Post a Comment