How to dynamically decide <intercept-url> access attribute value in Spring Security? - 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";
}

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.

How to solve IllegalArgumentException in Spring security

I'm using spring security. When I browse to http://localhost:8080/rest/user/json/quypham, I get the following error:
[WARNING] /rest/user/json/quypham java.lang.IllegalArgumentException:
Failed to evaluate expression 'ROLE_ADMIN'
at org.springframework.security.access.expression.ExpressionUtils.evalua
teAsBoolean(ExpressionUtils.java:15)
at org.springframework.security.web.access.expression.WebExpressionVoter
.vote(WebExpressionVoter.java:36)
at org.springframework.security.web.access.expression.WebExpressionVoter
.vote(WebExpressionVoter.java:18)
at org.springframework.security.access.vote.AffirmativeBased.decide(Affi
rmativeBased.java:62)
at org.springframework.security.access.intercept.AbstractSecurityInterce
ptor.beforeInvocation(AbstractSecurityInterceptor.java:232)
at org.springframework.security.web.access.intercept.FilterSecurityInter
Caused by:
org.springframework.expression.spel.SpelEvaluationException: EL1008E:
(pos 0): Property or field 'ROLE_ADMIN' cannot be found on object of
type 'org.s
pringframework.security.web.access.expression.WebSecurityExpressionRoot'
- maybe not public?
at org.springframework.expression.spel.ast.PropertyOrFieldReference.read
Property(PropertyOrFieldReference.java:215)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getV
alueInternal(PropertyOrFieldReference.java:85)
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getV
alueInternal(PropertyOrFieldReference.java:78)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(Sp
elNodeImpl.java:114)
at org.springframework.expression.spel.standard.SpelExpression.getValue(
SpelExpression.java:105)
at org.springframework.security.access.expression.ExpressionUtils.evalua
I think I have something wrong in my userservice-servlet.xml file:
<security:http>
<security:http-basic/>
<security:intercept-url pattern="/rest/**" access="ROLE_ADMIN"/>
</security:http>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider>
<security:user-service>
<security:user name="datpham" password="Dat12345" authorities="ROLE_ADMIN"/>
<security:user name="hoaipham" password="Hoai12345" authorities="ROLE_USER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
Here is UserRestServiceController.java:
#Controller
public class UserRestServiceController {
#Autowired
public UserDao userDao;
#Autowired
public View jsonTemplate;
#RequestMapping(value="/rest/user/json/{username}",
produces = MediaType.APPLICATION_JSON_VALUE,method = RequestMethod.GET)
public #ResponseBody User loadUser(#PathVariable("username") String name){
return userDao.loadUser(name);
}
By default, expression-based access-control is enabled. That means you should change access value to an expression:
<security:intercept-url pattern="/rest/**" access="hasRole('ADMIN')"/>
As an option, you can disable expressions:
<security:http use-expressions="false">
For all roles you have to use 'hasRole'.
In your case access="hasRole('ADMIN')" to be used when <http use-expressions="false">

Spring Security Struts 1.3 integration without extending GrandAuthority and UserDetails

I am new to spring security and wanted to integrate it into existing Struts 1.3 webapp.
I successfully followed this tutorial and it works great when it is that simple.
However in the tutorial user is hardcoded into security.xml:
<authentication-manager>
<authentication-provider>
<password-encoder hash="md5"/>
<user-service>
<user name="admin" password="21232f297a57a5a743894a0e4a801fc3" authorities="ROLE_ADMIN"/>
</user-service>
</authentication-provider>
</authentication-manager>
If I understand correctly to enable custom authentication I need to implement my CustomAuthenticationProvider that extends AuthenticationProvider interface, which has an authenticate method.
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}
However UsernamePasswordAuthenticationToken accepts Collection in its constructor and in our model entities do not extend spring security interaces, such as UserDetails and GrantedAuthority.
So is there a way to authenticate user without extending spring security interfaces in my entities?
EDIT:
I have added customUserDetailService:
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/login.do" access="isAnonymous()"/>
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
<intercept-url pattern="/j_spring_security_check" access="permitAll"/>
<form-login login-page="/login.do"
authentication-failure-url="/login.do?login_error=1"
default-target-url="/index.do"/>
<logout invalidate-session="true"
logout-url="/logout.do"
logout-success-url="/"/>
<csrf />
<remember-me />
<access-denied-handler error-page="/denied"/>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="customUserDetailService" />
</authentication-manager>
<beans:bean id="customUserDetailService" class="com.demo.service.CustomUserDetailsService"/>
However loadUserByUsername() method is not invoked...

Spring-security not working, need help understanding why

I'm trying to understand Spring-security but my pages can be accessed without loggin in and I don't understand why.
The "secure" page is located in WEB-INF/pages/secure and is accessed using http://localhost:8080/secret. This should not allow access, but currently does.
/secure maps here
#Controller
public class HelloWorld {
...
#RequestMapping("/secret")
public String showSecret(ModelMap model) {
return "secure/secretPage";
}
}
web.xml
...
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-security.xml
/WEB-INF/springmvc-config.xml
</param-value>
</context-param>
...
applicationContext-security.xml
<http auto-config="true">
<form-login login-processing-url="/j_spring_security_check"
login-page="/login"
authentication-failure-url="/login?login_error=t"/>
<logout logout-url="/j_spring_security_logout"/>
<intercept-url pattern="/pages/secure/**" access="IS_AUTHENTICATED_FULLY" requires-channel="https"/>
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userAccountDetailsService"/>
</authentication-manager>
userAccountDetailsService
#Service("userAccountDetailsService") // enables component to be found to <component-scan/>
public class UserAccountDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
throw new UsernameNotFoundException("Could not find user");
}
}
The /login page does not currently exist. There are no users anyway. I just want this to disallow access for now.
The URL you are hitting is /secure, not /pages/secure and yet in your Spring Security configuration you are protecting /pages/secure/** instead of /secure/**. Change the intercept URL from /pages/secure/** to /secure/** and try again.

Spring security 3 http-basic authentication-success-handler

H i'm using spring security
for form-login i have
<http auto-config="true">
<intercept-url pattern="/pages/**" access="ROLE_USER" />
<form-login authentication-success-handler-ref="authenticationSuccessHandler" login-page="/login.html" default-target-url="/pages/index.html"
always-use-default-target="true" authentication-failure-url="/login.html" />
<logout logout-success-url="/login.html" invalidate-session="true" />
<anonymous enabled='false'/>
</http>
here i can set an authentication-success-handler-ref, how can i add one to my basic authentication:
<http pattern="/REST/**" realm="REALM" entry-point-ref="authenticationEntryPoint">
<intercept-url pattern="/**" access="ROLE_USER" />
<http-basic />
<logout logout-url="/REST/logout" success-handler-ref="restLogoutSuccessHandler" />
</http>
i thought abour overriding BasicAuthenticationFilter, but how can i inject my cutom class for <http-basic />
You cannot set an authentication success handler for BASIC authentication. You can, however, extend BasicAuthenticationFilter and override onSuccessfulAuthentication method:
#Component("customBasicAuthFilter")
public class CustomBasicAuthFilter extends BasicAuthenticationFilter {
#Autowired
public CustomBasicAuthFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
protected void onSuccessfulAuthentication(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Authentication authResult) {
// Do what you want here
}
}
Inject it in your security configuration with something like:
<http entry-point-ref="basicEntryPoint">
<custom-filter ref="customBasicAuthFilter" position="BASIC_AUTH_FILTER"/>
</http>
<authentication-manager alias="authenticationManager">
...
</authentication-manager>
Update: Or with Java config instead of XML:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAt(customBasicAuthFilter, BasicAuthenticationFilter.class)
.exceptionHandling().authenticationEntryPoint(basicEntryPoint);
}
As a workaround you can use http-basic in conjuction with form-login:
<http auto-config="true">
...
<http-basic />
<form-login authentication-success-handler-ref="authenticationSuccessHandler" ... />
...
</http>
BasicAuthenticationFilter will work.
EDIT.
If you want set up your overriden version of BasicAuthenticationFilter I think you need to:
Add it to filter chain at BASIC_AUTH_FILTER position as explained here
Set up corresponding BasicAuthenticationEntryPoint entry point via entry-point-ref attribute of http tag.
Instead of using an AuthenticationSuccessHandler you can rely on Spring Security's event mechanism and listen to AuthenticationSuccessEvent by using the ApplicationListener interface:
#Component
public class AuthenticationEventListener implements
ApplicationListener<AuthenticationSuccessEvent>
{
#Override
public void onApplicationEvent (AuthenticationSuccessEvent event) {
// do what you want here
// example: persist event to the database
}
}
See also this answer here: https://stackoverflow.com/a/11384001/474034

Resources