Spring Security with JDBC Authentication - No AuthenticationProvider found - spring-security

I'm new to Spring Security, and I'm trying to set up authentication/authorization against a PostgreSQL database. I followed the first 3 chapters here and got in-memory username/password working without problems. After creating the tables required for the schema (here), then setting up a JNDI DataSource in tomcat (here) along with all the beans needed for Spring, login is now failing with this message:
Your login attempt was not successful, try again.
Reason: No AuthenticationProvider found for
org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Here are the beans defined in my servlet-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/TyedArtDB"/>
<!-- Enables the Spring MVC #Controller programming model -->
<annotation-driven />
<context:component-scan base-package="com.tyedart.web" />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by #Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<beans:bean id="userDetailsService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
<beans:property name="dataSource" ref="dataSource"/>
</beans:bean>
<beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<beans:bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="userDetailsService"/>
<beans:property name="passwordEncoder" ref="passwordEncoder"/>
</beans:bean>
<beans:bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<beans:property name="providers">
<beans:list>
<beans:ref bean="daoAuthenticationProvider"/>
</beans:list>
</beans:property>
</beans:bean>
</beans:beans>
And here's my SecurityConfig class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// auth
// .inMemoryAuthentication()
// .withUser("rob").password("22").roles("ADMIN");
InitialContext ctx = new InitialContext();
DataSource dataSource = (DataSource) ctx.lookup("java:/comp/env/jdbc/TyedArtDB");
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.passwordEncoder(new BCryptPasswordEncoder());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/manage/**").hasRole("ADMIN")
.and()
.formLogin();
}
}
Any ideas what I'm doing wrong here?

I just configured Security with JDBC. I used the java annotation to configure my app. A couple things that I noted:
.withDefaultSchema()
For me, unexpected. I had a data source configured, however I did not create the default tables. Spring connected to my data source and automatically create the schema. I noticed you created the schema manually. That's what I expected also. The docs seemed to be ambiguous, so I just ran my app without creating the tables. Voila Spring created the database tables for me. Perhaps the schema does not match (see my next section, I founds the docs slightly out of date).
Next, I was adding Spring Security to a small Spring MVC app. The Spring blog contained the correct configuration annotation:
#EnableWebMvcSecurity
Let me know if you want me to post my security config. It's java not xml. Thus, I don't know if it would help.

Related

Unable to hit custom AuthenticationProvider using Spring security 5.1.1

I'm using Spring security 5.1.1. I'm trying to create two security entryPoints for my application: one for REST and another for the secured urls of the application. I've created CustomAuthenticationProvider by implementing AuthenticationProvider for the authenticationManager.
I'm following the examples in :
Spring Security for a REST API and
Spring Security – Two Security Realms in one Application
But on the login page, when I enter username and password it doesn't hit the CustomAuthenticationProvider.authenticate() method at all, rather it goes to logout.html.
Below is my xml snippet of http:
<!-- Configuration for API -->
<security:http entry-point-ref="restAuthEntryPoint" pattern="/api/**" use-expressions="true">
<intercept-url pattern="/api/**" access="hasAnyRole('ROLE_DRIVER','ROLE_PARENT') and isAuthenticated()"/>
<intercept-url pattern="/api/driver/**" access="hasRole('ROLE_DRIVER') and isAuthenticated()"/>
<intercept-url pattern="/api/parent/**" access="hasRole('ROLE_PARENT') and isAuthenticated()"/>
<form-login
authentication-success-handler-ref="apiSuccessHandler"
authentication-failure-handler-ref="apiFailureHandler" />
<custom-filter ref="apiAuthenticationFilter" after="BASIC_AUTH_FILTER" />
<logout />
</security:http>
<beans:bean id="apiAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
<beans:constructor-arg name="authenticationEntryPoint" ref="restAuthEntryPoint"/>
<beans:constructor-arg name="authenticationManager" ref="authenticationManager"/>
</beans:bean>
<beans:bean id="restAuthEntryPoint"
class="com.main.sts.api.security.RestAuthenticationEntryPoint"/>
<beans:bean id="apiSuccessHandler"
class="com.main.sts.api.security.MySavedRequestAwareAuthenticationSuccessHandler"/>
<beans:bean id="apiFailureHandler" class=
"org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"/>
<!-- Configuration for Rest-API finished-->
<security:http auto-config="true" use-expressions="true" authentication-manager-ref="authenticationManager">
<intercept-url pattern="/school_admin/*"
access="hasAnyRole('ROLE_SCHOOLADMIN','ROLE_GUEST','ROLE_SCHOOLTEACHER','ROLE_PARENT')" />
<form-login login-page="/login" authentication-failure-url="/loginfailed"/>
<!-- <custom-filter before="FORM_LOGIN_FILTER" ref="userAuthenticationProcessingFilter" /> -->
<logout invalidate-session="true" logout-success-url="/logout" />
<access-denied-handler error-page="/404" />
<session-management invalid-session-url="/logout.html">
</session-management>
<sec:headers >
<sec:cache-control />
<sec:hsts/>
</sec:headers>
</security:http>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="customAuthenticationProvider" />
</authentication-manager>
<beans:bean id="customAuthenticationProvider" class="com.main.sts.util.CustomAuthenticationProvider">
<beans:property name="loginService" ref="loginService" />
</beans:bean>
Even if I commented out the configuration for the REST-api, still I don't get hit to that class.
Here's my CustomAuthenticationProvider:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) {
// **I never hit this class**
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
filter is defined correctly in web.xml:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
springSecurityFilterChain /*
In the login jsp, I've setup form as below:
<form class="form-vertical login-form" name='f' action="<c:url value='j_spring_security_check' />" method="post">
I can't access secured urls, it takes me to the login page; this means - this filter works. But why can't I hit CustomAuthenticationProvider? Why does it go to logout.html???
I've also tried by implementing custom filter (which eventually sets authenticationManager as the property); but still no luck.
I've also checked the log files but nothing in there.
BTW, if I try to access through curl, I get Http status 403 (forbidden). The server understood the request but refuses to authorize it.
curl -i -X POST -d username=admin -d password=Admin123 http://localhost:8080/sts/api/login
Please help me to find out the issue.
Alhamdulillah, finally I've found the issue.The code base which I originally started with was implemented on Spring 2.5. I've upgraded Spring version to 5.1. Basically /j_spring_security_check , j_username and j_password have been deprecated.
Now I've changed my jsp accordingly and it works now.
It's weird that there was no error or warning message.

Spring Security UserDetailsService not called

I'm upgrading from Spring Security 3.2.5 to 4.0.4, working with the migration guide.
My UserDetailsService looks like this:
package com.me.security;
import org.springframework.security.core.userdetails.UserDetailsService;
public class Users implements UserDetailsService {
public Users() {
System.err.println("USERS CONSTRUCTOR");
}
#Override
public UserDetail loadUserByUsername(String name) {
System.err.println("LOAD BY USER NAME " + name);
throw new UsernameNotFoundException("User not found.");
}
}
My WEB-INF/applicationContext.xml has this:
<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-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.0.xsd">
<security:http disable-url-rewriting="true" use-expressions="false">
<security:intercept-url pattern="/auth/**" access="ROLE_ANONYMOUS"/>
<security:intercept-url pattern="/dashboard/**" access="ROLE_ADMIN,ROLE_USER"/>
<!-- ...more intercept-urls... -->
<security:access-denied-handler error-page="/pages/general/403.xhtml"/>
<security:form-login login-page="/auth/login.html"
username-parameter="j_username"
password-parameter="j_password"
login-processing-url="/j_spring_security_check"
default-target-url="/dashboard/"
authentication-failure-url="/auth/error.html"/>
<security:logout logout-success-url="/auth/login.html"
logout-url="/auth/login.html"
delete-cookies="JSESSIONID"
invalidate-session="true" />
<security:session-management invalid-session-url="/auth/login.html"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider user-service-ref='userDetailsService'/>
</security:authentication-manager>
<bean id="userDetailsService" class="com.me.security.Users"/>
</beans>
When I try to log in, my code does not get called. I do see the message from the Users constructor in the server logs, but not the one from its loadUserByUsername method.
Instead, no matter what I enter for user name and password, I get to my 403 error page.
(Maybe I've been looking at this for too long already...)
Why doesn't Spring call my UserDetailsService and what do I need to get it to work?
It sounds to be the csrf filter. In Spring Security 4.x it is activated by default: Migrating from Spring Security 3.x to 4.x. This may be problem if you are allways getting an HTTP 403.
Try disabling setting this inside the security:http element:
<csrf disabled="true"/>

How to keep SecurityContext set through WithSecurityContextFactory in Spring Security tests?

I'm using Spring 4.1.5 and Spring Security 4.0.0.RELEASE.
I read http://spring.io/blog/2014/05/07/preview-spring-security-test-method-security (nice article by Rob Winch) and developed my own implementation of WithSecurityContextFactory to be able to test my Spring MVC controllers:
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
#Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
final User fakeUser = new User();
final SecurityUser principal = new SecurityUser(fakeUser);
final Authentication auth = new UsernamePasswordAuthenticationToken(principal, "password", HelpersTest.getAuthorities(customUser.faps()));
final SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(auth);
return context;
}
}
My abstract resource test class is as follow:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations =
{
"classpath:spring/mock-daos-and-scan-for-services.xml",
"classpath:security.xml",
"classpath:singletons.xml",
"classpath:controller-scan.xml",
"classpath:servlet.xml" })
#TestExecutionListeners(listeners=
{
ServletTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
WithSecurityContextTestExcecutionListener.class })
public abstract class AbstractResourceMockMvcTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private Filter springSecurityFilterChain;
private MockMvc mockMvc;
[...]
#Before
public void setup() {
this.mockMvc =
MockMvcBuilders.webAppContextSetup(this.getWac())
.addFilters(springSecurityFilterChain)
.build();
}
[...]
}
Then, my concrete test class inherits from AbstractResourceTest (from above) and it uses the following annotation on a #Test-enabled method:
#WithMockCustomUser(faps={"promotion_read"})
Tracing the code, I can confirm WithMockCustomUserSecurityContextFactory.createSecurityContext() is called and its return value is set in SecurityContextHolder.setContext() (through TestSecurityContextHolder.setContext()).
So far, so good !
However, later in the process, SecurityContextPersistenceFilter.doFilter() calls SecurityContextHolder.setContext() and this overwrites the context set by the test and I lose track of the mocked security context I prepared.
security.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd
"
>
<!-- HTTP security handling -->
<security:http use-expressions="true">
<security:logout logout-url="/j_spring_security_logout" invalidate-session="true" logout-success-url="/login.jsp?loggedout=true" />
<security:custom-filter before="FIRST" ref="multiTenantRequestFilter" />
<!-- make sure following page are not secured -->
<security:intercept-url pattern="/*/*/internal/**" access="hasIpAddress('127.0.0.1')" />
<!-- make sure everything else going through the security filter is secured -->
<security:intercept-url pattern="/resources/**" access="hasRole('ROLE_USER')" requires-channel="any" />
<!-- supporting basic authentication for unattended connections (web services) -->
<security:http-basic />
</security:http>
<!-- authentication strategy -->
<security:authentication-manager alias="authManager">
<security:authentication-provider user-service-ref="userSecurityService">
<security:password-encoder ref="passwordEncoder" />
</security:authentication-provider>
</security:authentication-manager>
<!-- custom filter to intercept the tenant name from the login form -->
<bean id="multiTenantRequestFilter" class="com.meicpg.ti.web.MultiTenantRequestFilter" />
</beans>
servlet.xml:
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
"
>
<mvc:annotation-driven>
<!-- Content skipped for StackOverflow question -->
</mvc:annotation-driven>
<context:annotation-config />
<bean id="annotationExceptionResolver" class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"></bean>
<security:global-method-security pre-post-annotations="enabled"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
How can I prevent this security context overwrite ? Does my security.xml contain an obvious flaw I missed ?
PS: I skipped the other context configuration files as they seem irrelevant to the problem.
Thanks in advance !
Unfortunately that blog post is just for method level security and does not have complete instructions for MockMvc setup (the following blog in the series does). Additionally, the blogs are actually dated (I have updated them to reflect that readers should refer to the reference documentation). You can find updated instructions in the Testing Section of the reference.
In short, update your code to the following:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations =
{
"classpath:spring/mock-daos-and-scan-for-services.xml",
"classpath:security.xml",
"classpath:singletons.xml",
"classpath:controller-scan.xml",
"classpath:servlet.xml" })
public abstract class AbstractResourceMockMvcTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
[...]
#Before
public void setup() {
this.mockMvc =
MockMvcBuilders.webAppContextSetup(this.getWac())
.apply(springSecurity())
.build();
}
#Test
#WithMockCustomUser(faps={"promotion_read"})
public void myTest() {
...
}
[...]
}
A few highlights:
You no longer need to provide the TestExecutionListeners
Use .apply(springSecurity()) instead of adding the spring security filter chain manually
This works because Spring Security's test support i.e. apply(springSecurity()) will override the SecurityContextRepository used by the springSecurityFilterChain to first try the TestSecurityContextHolder.

Spring security authenticaton

I'm developing an application based on Spring and I'm using Spring Security 3.1.3.
Every time i try to authenticate, authentication doesn't succeed. Here is my security-config.xml:
<?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/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<security:http auto-config="true">
<security:form-login login-page="/app/main"
default-target-url="/app/account" />
<security:logout logout-url="/app/logout"
logout-success-url="/app/main" />
</security:http>
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt. BCryptPasswordEncoder" />
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider
user-service-ref="userService" >
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication .dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userService" />
<property name="hideUserNotFoundExceptions" value="false" />
</bean>
<bean id="authenticationManager"
class="org.springframework.security.authentication .ProviderManager">
<constructor-arg>
<ref local="daoAuthenticationProvider" />
</constructor-arg>
</bean>
</beans>
In my bean's password set method, i use BCryptPasswordEncoder like following:
public void setPassword(String password) {
PasswordEncoder passwordEncoder= new BCryptPasswordEncoder();
this.password=passwordEncoder.encode(password);
}
I store username and encoded password in db. I can authenticate just the first time after i sign up. Reason for that is that hibernate entity manager does automatic update of password value in database after selection. I don't know why!!! When i use plain text password, none of this is happening, everything works fine.
It's difficult to know for sure, since you haven't elaborated on what "my bean" is or where this setPassword function is used, but if it's part of a Hibernate entity, then it may be called with an already encoded password (e.g. when loading an entity from the database, not just when creating a new). You certainly wouldn't want to use bcrypt at that point. This might also explain why you can login the first time and sounds likely from your description of hibernate automatically updating the field.
For testing purposes, try adding an assertion that checks that the string doesn't begin with "$2a" (as a bcrypt string will), and throw an exception if it does.
If that's the case, make sure you call the encoder outside the entity when you create it, and pass in the encoded password to the setter.

How to dynamically decide <intercept-url> access attribute value in Spring Security?

In Spring Security we use the intercept-url tag to define the access for URLs as below:
<intercept-url pattern="/**" access="ROLE_ADMIN" />
<intercept-url pattern="/student" access="ROLE_STUDENT" />
This is hard coded in applicationContext-security.xml. I want to read the access values from a database table instead. I have defined my own UserDetailsService and I read the roles for the logged in user from the database. How do I assign these roles to the URL patterns during runtime?
The FilterInvocationSecurityMetadataSourceParser class in Spring-security (try Ctrl/Cmd+Shift+T in STS with the source code) parses the intercept-url tags and creates instances of ExpressionBasedFilterInvocationSecurityMetadataSource, that extends DefaultFilterInvocationSecurityMetadataSource that implements FilterInvocationSecurityMetadataSource that extends SecurityMetadataSource.
What I did is to create a custom class that implements FilterInvocationSecurityMetadataSource, OptionsFromDataBaseFilterInvocationSecurityMetadataSource. I used DefaultFilterInvocationSecurityMetadataSource as base to use urlMatcher, to implement the support() method and something like that.
Then you must to implement these methods:
Collection getAttributes(Object object), where you can access to database, searching for the 'object' being secured (normally the URL to access) to obtain the allowed ConfigAttribute's (normally the ROLE's)
boolean supports(Class clazz)
Collection getAllConfigAttributes()
Be careful with the later, because it's called at startup and maybe is not well configured at this time (I mean, with the datasources or persistence context autowired, depending on what are you using). The solution in a web environment is to configure the contextConfigLocation in the web.xml to load the applicationContext.xml before the applicationContext-security.xml
The final step is to customize the applicationContext-security.xml to load this bean.
For doing that, I used regular beans in this file instead of the security namespace:
<beans:bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<filter-chain-map path-type="ant">
<filter-chain pattern="/images/*" filters="none" />
<filter-chain pattern="/resources/**" filters="none" />
<filter-chain pattern="/**" filters="
securityContextPersistenceFilter,
logoutFilter,
basicAuthenticationFilter,
exceptionTranslationFilter,
filterSecurityInterceptor"
/>
</filter-chain-map>
</beans:bean>
You have to define all the related beans. For instance:
<beans:bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
<beans:property name="accessDecisionManager" ref="affirmativeBased"></beans:property>
<beans:property name="securityMetadataSource" ref="optionsFromDataBaseFilterInvocationSecurityMetadataSource"></beans:property>
<beans:property name="validateConfigAttributes" value="true"/></beans:bean>
I know that is not a well explained answer, but it's not as difficult as it seems.
Just use the spring source as base and you will obtain what you want.
Debugging with the data in your database, will help you a lot.
Actually, spring security 3.2 do not encourage to do this according to http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/faq.html#faq-dynamic-url-metadata
but, it is possible (but not elegant) using http element in namespace with a custom accessDecisionManager..
The config should be:
<http pattern="/login.action" security="none"/>
<http pattern="/media/**" security="none"/>
<http access-decision-manager-ref="accessDecisionManager" >
<intercept-url pattern="/**" access="ROLE_USER"/>
<form-login login-page="/login.action"
authentication-failure-url="/login?error=1"
default-target-url="/console.action"/>
<logout invalidate-session="true" delete-cookies="JSESIONID"/>
<session-management session-fixation-protection="migrateSession">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/login.action"/>
</session-management>
<!-- NO ESTA FUNCIONANDO, los tokens no se ponen en el request!
<csrf />
-->
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="test" password="test" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
<beans:bean id="accessDecisionManager" class="openjsoft.core.services.security.auth.CustomAccessDecisionManager">
<beans:property name="allowIfAllAbstainDecisions" value="false"/>
<beans:property name="decisionVoters">
<beans:list>
<beans:bean class="org.springframework.security.access.vote.RoleVoter"/>
</beans:list>
</beans:property>
</beans:bean>
The CustomAccessDecisionManager should be...
public class CustomAccessDecisionManager extends AbstractAccessDecisionManager {
...
public void decide(Authentication authentication, Object filter,
Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
if ((filter == null) || !this.supports(filter.getClass())) {
throw new IllegalArgumentException("Object must be a FilterInvocation");
}
String url = ((FilterInvocation) filter).getRequestUrl();
String contexto = ((FilterInvocation) filter).getRequest().getContextPath();
Collection<ConfigAttribute> roles = service.getConfigAttributesFromSecuredUris(contexto, url);
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, filter, roles);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied",
"Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
...
}
Where getConfigAttributesFromSecuredUris retrieve form DB de roles for the specific URL
I have kind of the same problem, basically I'd like to keep separate the list of intercept-url from the other springsecurity configuration section, the first to belong to the application configuration the latter to the product (core, plugin) configuration.
There is a proposal in the JIRA of spring, concerning this problem.
I don't want to give up to use the springsecurity namespace, so I was thinking to some possible solutions in order to deal with this.
In order to have the list of intercept-url dynamically created you have to inject the securitymetadatasource object in the FilterSecurityInterceptor.
Using springsecurity schema the instance of FilterSecurityInterceptor is created by the HttpBuilder class and there is no way to pass the securitymetadatasource as property defined in the schema configuration file, as less as using kind of workaround, which could be:
Define a custom filter, to be executed before FilterSecurityInterceptor, in this filter retrieving the instance FilterSecurityInterceptor (assuming a unique http section is defined) by the spring context and inject there the securitymetadatasource instance;
The same as above but in a HandlerInterceptor.
What do you think?
This the solution I've applied in order to split the list of intercept-url entries from the other spring security configuration.
<security:custom-filter ref="parancoeFilterSecurityInterceptor"
before="FILTER_SECURITY_INTERCEPTOR" />
........
<bean id="parancoeFilterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor" >
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource" ref="securityMetadataSource"/>
</bean>
The bean securityMetadataSource can be put either in the same configuration file or in another configuration file.
<security:filter-security-metadata-source
id="securityMetadataSource" use-expressions="true">
<security:intercept-url pattern="/admin/**"
access="hasRole('ROLE_ADMIN')" />
</security:filter-security-metadata-source>
Of course you can decide to implement your own securityMetadataSource bean by implementing the interface FilterInvocationSecurityMetadataSource.
Something like this:
<bean id="securityMetadataSource" class="mypackage.MyImplementationOfFilterInvocationSecurityMetadataSource" />
Hope this helps.
This is how it can be done in Spring Security 3.2:
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public SecurityConfigDao securityConfigDao() {
SecurityConfigDaoImpl impl = new SecurityConfigDaoImpl() ;
return impl ;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
/* get a map of patterns and authorities */
Map<String,String> viewPermissions = securityConfigDao().viewPermissions() ;
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry interceptUrlRegistry = http
.authorizeRequests().antMatchers("/publicAccess/**")
.permitAll();
for (Map.Entry<String, String> entry: viewPermissions.entrySet()) {
interceptUrlRegistry.antMatchers(entry.getKey()).hasAuthority(entry.getValue());
}
interceptUrlRegistry.anyRequest().authenticated()
.and()
...
/* rest of the configuration */
}
}
A simple solution that works for me.
<intercept-url pattern="/**/**" access="#{#customAuthenticationProvider.returnStringMethod}" />
<intercept-url pattern="/**" access="#{#customAuthenticationProvider.returnStringMethod}" />
customAuthenticationProvider is a bean
<beans:bean id="customAuthenticationProvider"
class="package.security.CustomAuthenticationProvider" />
in CustomAuthenticationProvider class create method:
public synchronized String getReturnStringMethod()
{
//get data from database (call your method)
if(condition){
return "IS_AUTHENTICATED_ANONYMOUSLY";
}
return "ROLE_ADMIN,ROLE_USER";
}

Resources