Reputation: 5178
I've been working on a spring security recently and I need to know how I can define intercept-url (in Spring Security) dynamically using a Database.
I've already dug deep the whole internet and I could not find any unique (and of course useful) tutorial in this area.
So here is what I did:
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
public List<ConfigAttribute> getAttributes(Object object) {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();
attributes = getAttributesByURL(url);
return attributes;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public List<ConfigAttribute> getAttributesByURL(String inputUrl)
{
List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();
Connection connection = null;
String url = "jdbc:mysql://173.0.0.22:3306/";
String dbName = "kheirkhahandb";
String driverName = "com.mysql.jdbc.Driver";
String userName = "kheirkhahan";
String password = "kheirkhahan";
try{
Class.forName(driverName).newInstance();
connection = DriverManager.getConnection(url+dbName, userName, password);
try{
Statement stmt = connection.createStatement();
String selectquery = "select * from URL_ACCESS where URL = '" + inputUrl +"'";
ResultSet rs = stmt.executeQuery(selectquery);
while(rs.next()){
MyConfigAttribute temp = new MyConfigAttribute();
String attr = rs.getString("ACCESS").toString();
temp.setAttr(attr);
attributes.add(temp);
}
}
catch(SQLException s){
System.out.println(s);
}
connection.close();
}
catch (Exception e){
e.printStackTrace();
}
return attributes;
}
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/css/**" filters="none" />
<sec:filter-chain pattern="/images/**" filters="none" />
<sec:filter-chain pattern="/login.jsp*" filters="none" />
<sec:filter-chain pattern="/**"
filters="
securityContextPersistenceFilter,
logoutFilter,
authenticationProcessingFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
</sec:filter-chain-map>
</bean>
<bean id="securityContextPersistenceFilter"
class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
</bean>
<bean id="exceptionTranslationFilter"
class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="authenticationEntryPoint" />
<property name="accessDeniedHandler" ref="accessDeniedHandler" />
</bean>
<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp?error=entryPoint" />
</bean>
<bean id="accessDeniedHandler"
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/login.jsp?error=access_denied" />
</bean>
<bean id="authenticationProcessingFilter"
class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="securityMetadataSource" ref="myFilterInvocationSecurityMetadataSource" />
</bean>
<bean id="myFilterInvocationSecurityMetadataSource" class="com.datx.dao.MyFilterSecurityMetadataSource">
</bean>
<bean id="logoutFilter"
class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg value="/login.jsp?error=logout" />
<constructor-arg ref="logoutHandler">
</constructor-arg>
</bean>
<bean id="logoutHandler"
class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"></bean>
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider>
<sec:jdbc-user-service data-source-ref="dataSource"
group-authorities-by-username-query="
SELECT acg.ID, acg.GROUP_NAME, a.AUTHORITY_NAME AS AUTHORITY
FROM ACCESS_GROUPS acg, ACCESS_GROUP_MEMBERSHIP agm, GROUP_AUTHORITIES ga, AUTHORITIES a
WHERE agm.USERNAME = ? and acg.ID = ga.GROUP_ID and acg.ID = agm.GROUP_ID and ga.AUTHORITY_ID = a.ID
"
users-by-username-query="SELECT USERNAME,PASSWORD,IS_ACTIVE FROM USER where USERNAME = ?"
authorities-by-username-query="
SELECT ua.USERNAME, a.AUTHORITY_NAME AS AUTHORITY
FROM USER_AUTHORITIES ua, AUTHORITIES a
WHERE ua.USERNAME = ? and ua.AUTHORITY_ID = a.ID
" />
</sec:authentication-provider>
</sec:authentication-manager>
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<ref bean="roleVoter" />
</list>
</property>
</bean>
<bean id="roleVoter"
class="org.springframework.security.access.vote.RoleHierarchyVoter">
<property name="rolePrefix" value="" />
<constructor-arg ref="roleHierarchy" />
</bean>
<bean id="roleHierarchy" class="com.datx.dao.MyRoleHierarchyImpl">
<property name="roleHierarchyEntryDaoJdbc" ref="RoleHierarchyEntryDaoJdbc" />
</bean>
</beans>
3. I'm receiving exception when a user uses a wrong username/password or tries to access not-permitted-pages, instead of being redirected to login.jsp. Why is that?
Thanks in advance
Upvotes: 4
Views: 9561
Reputation: 5178
Thank you both Tom and Rob for your quick replies.
First of all, I'm totally aware of "storing url-patterns in database is not a good idea". However we are trying to manage everything dynamically. So there are no other choices.
As it turned out, there were some minor problems with my code. Here I answer each of my questions one by one.
My getAttributes method is working just fine. But there is an alternative to load url-patterns. I could load all url-patterns and their corresponding roles first into a HashedMap seperately. And in getAttributes method I could just look up the HasehdMap instead. In brief, it'd be like this:
The question is somehow trimmed!
I was trying to ask about those filters used in springSecurityFilterChain bean.
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<security:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/css/**" filters="none" />
<sec:filter-chain pattern="/images/**" filters="none" />
<sec:filter-chain pattern="/login.jsp*" filters="none" />
<sec:filter-chain pattern="/**"
filters="
securityContextPersistenceFilter,
logoutFilter,
authenticationProcessingFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
</security:filter-chain-map>
</bean>
I was receiving exceptions because in authenticationProcessingFilter bean there were no such properties. So I re-write it like this:
<bean id="authenticationProcessingFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationFailureHandler" ref="authenticationFailureHandler" />
<property name="filterProcessesUrl" value="/j_spring_security_check" />
<property name="usernameParameter" value="j_username" />
<property name="passwordParameter" value="j_password" />
<property name="authenticationManager" ref="authenticationManager" />
</bean>
and of course I had to introduce authenticationFailureHandler bean this as well:
<bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/index.jsp" />
</bean>
Now I'm not receiving any exceptions.
But here arises another question:
I cannot understand whether username/password is incorrect or the username does not have access to the requested page.
In both cases, the user is redirected according to this bean:
<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp?error=EntryPoint" />
</bean>
Why is it controlling both cases?
Upvotes: 2
Reputation: 21720
First I would ensure you have consulted the FAQ about this to ensure you really want to do this. As tom alluded to, it is generally not advisable to place such information in a database.
In terms of if/why your current code is working it is difficult to say without more details. For example, what are the errors you are seeing in the logs? The question in #2 does not appear to be complete. What do the Spring Security logs say?
If are going to stick with this plan, I would continue to fully use the namespace configuration and leverage a BeanPostProcessor (as discussed on the FAQ) to swap out the FilterInvocationServiceSecurityMetadataSource
. An implementation might look something like this:
public class FilterInvocationServiceSecurityMetadataSourceBeanPostProcessor
implements BeanPostProcessor {
private FilterInvocationServiceSecurityMetadataSource metadataSource;
public void setMetadataSource(FilterInvocationServiceSecurityMetadataSource metadataSource) {
this.metadataSource = metadataSource;
}
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if(bean instanceof FilterInvocationSecurityMetadataSource) {
return metadataSource;
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
Then your custom FilterInvocationServiceSecurityMetadataSource
could be specified in your Spring configuration along with the FilterInvocationServiceSecurityMetadataSourceBeanPostProcessor
.
<bean id="fiMds" class="FilterInvocationServiceSecurityMetadataSourceBeanPostProcessor">
<property name="metadataSource">
<bean id="myFilterInvocationSecurityMetadataSource" class="com.datx.dao.MyFilterSecurityMetadataSource"/>
</property>
</bean>
Upvotes: 4