Securing url pattern based on user properties Spring security - spring

I have secured certain url patterns for my project based on the users role as seen in my spring_security xml below.
<security:http auto-config="true" use-expressions="true" access-denied-page="/auth/denied.do" >
<security:intercept-url pattern="/auth/login" access="permitAll"/>
<security:intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
<security:intercept-url pattern="/security/**" access="hasRole('ROLE_SECURITY')"/>
<security:intercept-url pattern="/common/**" access="hasRole('ROLE_USER')"/>
<security:intercept-url pattern="/notsecure/**" access="permitAll"/>
<security:form-login
login-page="/auth/login.do"
authentication-failure-url="/auth/login.do?error=true"
default-target-url="/common/tasks/tasks.do"
authentication-success-handler-ref="mySuccessHandler"/>
<security:logout
invalidate-session="true"
logout-success-url="/auth/login.do"
logout-url="/auth/logout.do"/>
</security:http>
<sec:global-method-security pre-post-annotations="enabled" />
<!-- Declare an authentication-manager to use a custom userDetailsService -->
<security:authentication-manager>
<security:authentication-provider user-service-ref="authenticationService">
<!-- <security:password-encoder ref="passwordEncoder"/> -->
</security:authentication-provider>
</security:authentication-manager>
<!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
<!--
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>
-->
<!-- A custom service where Spring will retrieve users and their corresponding access levels -->
<bean id="authenticationService" class="ie.premiumpower.services.AuthenticationService"/>
<bean id="mySuccessHandler" class="ie.premiumpower.services.MySuccessHandler">
</bean>
So only admin users can access /admin/** etc.
Now I want to limit users to their own url pattern based on a different attribute (their site_id which is just an int). So only users with a site_id of 1 can go to the url "/1/**" and so on.
How can I go about doing this? Just looking for a point in the right direction. Everything I've seen so far doesn't allow me to have a variable url-pattern. As in "/{variable}/".

https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html
See "15.3 Method Security Expressions"
You can use something like
#PreAuthorize("#value == '123'")
#RequestMapping(value="/secure")
#ResponseBody
public String aloa(#RequestParam("value") String value, Principal principal) {
return "Hello " + principal.getName();
}
This will only let you in if you provide "value=123" as a request Parameter.
You may also use #PathVariable here:
#PreAuthorize("#value == '123'")
#RequestMapping(value="/secure/{value}/data")
#ResponseBody
public String aloa(#PathVariable("value") String value, Principal principal)
If you want fine-grain access control to your domain objects, you may want to use spring-acl for such purpose. There you can define fine grained access control for any object base on user permissions. Heres the simple base on which acl is base uppon, too. you can throw in your own implementation of PermissionEvaluator and then make use of "hasPermission" inside the #PreAuthorize:
Link it in in your security config:
<global-method-security secured-annotations="disabled" pre-post-annotations="enabled">
<expression-handler ref="expressionHandler"/>
</global-method-security>
<beans:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<beans:property name="permissionEvaluator" ref="myPermissionEvaluator"/>
</beans:bean>
create a "hasPermission" PreAuthorize constraint:
#PreAuthorize("hasPermission(#value, 'admin')")
#RequestMapping(value="/secure/{value}/data")
#ResponseBody
public String aloa(#PathVariable("value") String value, Principal principal)
Fill a PermissionEvaluator with life. Here you can bridge your domain-permission over to spring-security: The referenced value from your #RequestMapping will come in through the "targetDomainObject" in "permission" you'll find the required permission as defined in your "hasPermission" definition above.
#Component("myPermissionEvaluator")
public class MyPermissionEvaluator implements PermissionEvaluator {
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
return ...;
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
Object permission) {
return ...;
}
}
you may even directly access the Principal object from within the annotations, too:
#PreAuthorize("#value == authentication.principal.title") //my pricipal is from ldap source and title is mapped in from there.

Related

How to configure one login page with Multiple landing pages which intercept with different url patterns in spring security 4

My requirement is, I have two different landing pages one for user and one for admin. This landing pages has to be appear based on the intercept url pattern which i configure in the spring security xml file. Both the landing page is having a hyperlink to login, when the user click on the login hyperlink of adminLayout.jsp it will load the same login page and when the user click on the login hyperlink of userLayout.jsp it will load the same login page by interacting with the controller for two different url patterns.The url patterns will be /admin and /user.
I stuck here.
How can i configure two different landing pages(adminLayout) and userLayout) in spring security. this tow landing pages is having the same login form, which i want to configure in the spring security form-login for both the url patterns and tow layouts.Before login only the landing page has to appear and then when the user click on the login hyperlink from two different pages it has to make user of spring security provided login-form functionality.Please help me out on this.
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" requires-channel="http" />
<intercept-url pattern="/user**" access="hasRole('ROLE_USER')" requires-channel="http" />
<csrf disabled="true"/>
<access-denied-handler ref="accessDeniedHandler"/>
<!-- Here i want to configure the landing pages based on the intercept url pattern if the pattern is /admin i want to dispaly the adminLayout.
If the intercept url pattern is /user i want to display the userLayout. Both this Layout pages is having common login page which user will click from this layout pages.
if want to make use of spring secuiryt for form login..
-->
<form-login login-processing-url="/j_spring_security_check"
login-page="/sslogin" authentication-success-handler-ref="authenticationHandler"
authentication-failure-url="/fail2login?error=true"/>
<logout logout-success-url="/logout" invalidate-session="true" logout-url="/j_spring_security_logout" delete-cookies="JSESSIONID" />
<session-management>
<concurrency-control error-if-maximum-exceeded="true" max-sessions="1" expired-url="/fail2login" />
</session-management>
</http>
<beans:bean id="accessDeniedHandler" class="com.fss.portal.handlers.PortalAccessDeniedHandler">
<beans:property name="accessDeniedURL" value="403"></beans:property>
</beans:bean>
<beans:bean id="authenticationHandler" class="com.fss.portal.handlers.AuthenticationHandler">
</beans:bean>
<beans:bean id="customAuthenticationProvider" class="com.fss.portal.utility.CustomAuthenticationProvider">
<beans:property name="passwordEncoder" ref="bcryptEncoder"></beans:property>
</beans:bean>
<beans:bean id="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<authentication-manager>
<authentication-provider ref="customAuthenticationProvider">
</authentication-provider>
</authentication-manager>
I think you should try creating a AuthenticationEntryPoint implementation with multiple landing page support.
It could be something like this:
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
public class MultipleLandingPageEntryPoint extends LoginUrlAuthenticationEntryPoint
implements AuthenticationEntryPoint {
private Map<String, String> landingPages;
public MultipleLandingPageEntryPoint(String defaultLoginFormUrl, Map<String, String> landingPages) {
super(defaultLoginFormUrl);
this.landingPages = landingPages;
}
public MultipleLandingPageEntryPoint(String defaultLoginFormUrl) {
super(defaultLoginFormUrl);
}
public Map<String, String> getLandingPages() {
return landingPages;
}
public void setLandingPages(Map<String, String> landingPages) {
this.landingPages = landingPages;
}
#Override
protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) {
for(String key : this.landingPages.keySet()){
RequestMatcher rm = new RegexRequestMatcher(key, null);
if(rm.matches(request)){
return this.landingPages.get(key);
}
}
// If not found in the map, return the default landing page through superclass
return super.determineUrlToUseForThisRequest(request, response, exception);
}
}
Then, in your security config, you must configure it:
<beans:bean id="authenticationMultiEntryPoint" class="com.xxx.yyy.MultipleLandingPageEntryPoint">
<beans:constructor-arg value="/user/landing.htm" />
<beans:property name="landingPages">
<beans:map>
<beans:entry key="/user**" value="/user/landing.htm" />
<beans:entry key="/admin**" value="/admin/landing.htm" />
</beans:map>
</beans:property>
</beans:bean>
And use it in your <security:http> element:
<security:http pattern="/admin/landing.htm" security="none" />
<security:http pattern="/user/landing.htm" security="none" />
<security:http auto-config="true" use-expressions="true"
entry-point-ref="authenticationMultiEntryPoint">
If you implement the AuthenticationEntryPoint extending LoginUrlAuthenticationEntryPoint (which I think it's a good idea) check additional parameters on it.
EDIT: I've just updated the class implementation, did not include the latest version
Create A Custom AuthenticationSuccessHandler like below
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response,
Authentication authentication)
throws IOException,
javax.servlet.ServletException {
if(authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN")) {
request.getRequestDispatcher("/admin").forward(request, response);
} else if (authentication.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_USER")) {
request.getRequestDispatcher("/user").forward(request, response);
}
}
}
And configure it with form-login tag as following
<bean id="customAuthenticationSuccessHandler" class="CustomAuthenticationSuccessHandler" />
<form-login authentication-success-handler-ref="customAuthenticationSuccessHandler" ...>
UPDATE
Create a Controller mappings /landing point to it by <form-login login-page="/landing" .../>. This landing should have links to admin and user landing pages. Which can have links or forms to login.
You can remove protection from these landing pages.
<http pattern="/landing**" security="none"/>
<http pattern="/landing/admin**" security="none"/>
<http pattern="/landing/user**" security="none"/>
And you can write a Custom AuthenticationFailureHandler to redirect to correct login page.

Spring MVC, Method level security

I am trying to restrict access to my controller, based on user role. Now i can do that using the security.xml file in a following way
<http use-expressions="true">
<intercept-url pattern="/**" access="hasRole([ROLE_ADMIN,ROLE_USER])" />
</http>
But I dont want to do it this way. Rather i will write
<http use-expressions="true">
<intercept-url pattern="/**" access="isAuthenticated()"/>
</http>
and in the controller
#RequestMapping("/test")
#PreAuthorize("hasRole('ROLE_USER')")
public String test() {
return "test";
}
#RequestMapping("/testadmin")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public String testAdminPage() {
return "testadmin";
}
now ROLE_USER can access both (ROLE_ADMIN & ROLE_ USER) tagged controller. this is the problem.
and based on this testadmin.jsp can only be viewed by ROLE_ADMIN type user and test.jsp can only be viewed by "ROLE_USER" type user.
To sum up rather than writing the access code in the xml file i want to control it from the controller.
How do i do this??
you have to enable method level security via
<global-method-security pre-post-annotations="enabled"/>
then your spring controllers are going to get proxied and the PreAuthorize annotation is going to be evaluated.
further information can be found here (section 16.3):
http://docs.spring.io/spring-security/site/docs/current/reference/el-access.html
EDIT:
I guess your Controller beans are being created in the Disptacher Servlet (the web-context) and your security configuration is in the root-context -> Controllers will stay unaffected by the BeanPostProcessor so you have to put the <global-method-security>tag in the web context config (dispatcher-servlet.xml?)

Spring MVC + Spring Security login with a rest web service

I have a SpringMVC web application that needs to authenticate to a RESTful web service using Spring Security by sending the username and password. When an user is logged, a cookie needs to be set to the user's browser and in the subsequent calls the user session is validated with another RESTful web service by using the cookie.
I've been looking everywhere, but I have not been able to find a good example on how to accomplish this, and all my attempts have been in vain.
Here is what I have in mind:
I can have two authentication-providers declared, the first checks the cookie, and if it fails for any reason it goes to the second one which checks with the username and password (will fail too if there is no username and password in that request).
Both services return the authorities of the user each time, and spring security is "stateless".
On the other hand, I have questioned myself if this approach is correct, since it's been so difficult to find an example or somebody else with the same problem. Is this approach wrong?
The reason why I want to do this instead of just JDBC authentication is because my whole web application is stateless and the database is always accessed through RESTful web services that wrap a "petitions queue", I'd like to respect this for user authentication and validation too.
What have I tried so far? I could paste the long long springSecurity-context.xml, but I'll just list them instead for now:
Use a custom authenticationFilter with a authenticationSuccessHandler. Obviously doesn't work because the user is already logged in this point.
Make an implementation of entry-point-ref filter.
Do a custom-filter in the position BASIC_AUTH_FILTER
Make a custom Authentication Provider (Struggled a lot with no luck!). I'm retrying this while I get some answers.
I was starting to use CAS when I decided to write a question instead. Maybe in the future I can consider having a CAS server in my webapp, however for the moment, this feels like a huge overkill.
Thanks in advance!
BTW, I'm using Spring Security 3.1.4 and Spring MVC 3.2.3
EDIT: I WAS ABLE TO DO IT THANKS TO #coder ANSWER
Here is some light on what I did, I'll try to document all this and post it here or in a blog post sometime soon:
<http use-expressions="true" create-session="stateless" entry-point-ref="loginUrlAuthenticationEntryPoint"
authentication-manager-ref="customAuthenticationManager">
<custom-filter ref="restAuthenticationFilter" position="FORM_LOGIN_FILTER" />
<custom-filter ref="restPreAuthFilter" position="PRE_AUTH_FILTER" />
<intercept-url pattern="/signin/**" access="permitAll" />
<intercept-url pattern="/img/**" access="permitAll" />
<intercept-url pattern="/css/**" access="permitAll" />
<intercept-url pattern="/js/**" access="permitAll" />
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
</http>
<authentication-manager id="authManager" alias="authManager">
<authentication-provider ref="preauthAuthProvider" />
</authentication-manager>
<beans:bean id="restPreAuthFilter" class="com.company.CustomPreAuthenticatedFilter">
<beans:property name="cookieName" value="SessionCookie" />
<beans:property name="checkForPrincipalChanges" value="true" />
<beans:property name="authenticationManager" ref="authManager" />
</beans:bean>
<beans:bean id="preauthAuthProvider"
class="com.company.CustomPreAuthProvider">
<beans:property name="preAuthenticatedUserDetailsService">
<beans:bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<beans:property name="userDetailsService" ref="userDetailsService" />
</beans:bean>
</beans:property>
</beans:bean>
<beans:bean id="userDetailsService" class="com.company.CustomUserDetailsService" />
<beans:bean id="loginUrlAuthenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:constructor-arg value="/signin" />
</beans:bean>
<beans:bean id="customAuthenticationManager"
class="com.company.CustomAuthenticationManager" />
<beans:bean id="restAuthenticationFilter"
class="com.company.CustomFormLoginFilter">
<beans:property name="filterProcessesUrl" value="/signin/authenticate" />
<beans:property name="authenticationManager" ref="customAuthenticationManager" />
<beans:property name="authenticationFailureHandler">
<beans:bean
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/login?login_error=t" />
</beans:bean>
</beans:property>
</beans:bean>
And the Custom Implementations are something like this:
// Here, the idea is to write authenticate method and return a new UsernamePasswordAuthenticationToken
public class CustomAuthenticationManager implements AuthenticationManager { ... }
// Write attemptAuthentication method and return UsernamePasswordAuthenticationToken
public class CustomFormLoginFilter extends UsernamePasswordAuthenticationFilter { ... }
// Write getPreAuthenticatedPrincipal and getPreAuthenticatedCredentials methods and return cookieName and cookieValue respectively
public class CustomPreAuthenticatedFilter extends AbstractPreAuthenticatedProcessingFilter { ... }
// Write authenticate method and return Authentication auth = new UsernamePasswordAuthenticationToken(name, token, grantedAuths); (or null if can't be pre-authenticated)
public class CustomPreAuthProvider extends PreAuthenticatedAuthenticationProvider{ ... }
// Write loadUserByUsername method and return a new UserDetails user = new User("hectorg87", "123456", Collections.singletonList(new GrantedAuthorityImpl("ROLE_USER")));
public class CustomUserDetailsService implements UserDetailsService { ... }
you can define a custom pre-auth filter by extending
AbstractPreAuthenticatedProcessingFilter.
In your implementation of
getPreAuthenticatedPrincipal() method you can check if cookie exists
and if it exists return cookie name is principal and cookie value in
credentials.
Use PreAuthenticatedAuthenticationProvider and provide your custom preAuthenticatedUserDetailsService to check if cookie is vali, if its valid also fetch granted authorities else throw AuthenticationException like BadCredentialsException
For authenticating user using username/password, add a form-login filter, basic-filter or a custom filter with custom authentication provider (or custom userdetailsService) to validate user/password
In case cookie exists, pre auth filter will set authenticated user in springContext and your username./password filter will not be called, if cookie is misisng/invalid, authentication entry point will trigger the authentication using username/password
Hope it helps

Creating New Roles and Permissions Dynamically in Spring Security 3

I am using Spring Security 3 in Struts 2 + Spring IOC project.
I have used Custom Filter, Authentication Provider etc. in my Project.
You can see my security.xml here
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
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.1.xsd">
<global-method-security pre-post-annotations="enabled">
<expression-handler ref="expressionHandler" />
</global-method-security>
<beans:bean id="expressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler" >
<beans:property name="permissionEvaluator" ref="customPermissionEvaluator" />
</beans:bean>
<beans:bean class="code.permission.MyCustomPermissionEvaluator" id="customPermissionEvaluator" />
<!-- User Login -->
<http auto-config="true" use-expressions="true" pattern="/user/*" >
<intercept-url pattern="/index.jsp" access="permitAll"/>
<intercept-url pattern="/user/showLoginPage.action" access="permitAll"/>
<intercept-url pattern="/user/showFirstPage" access="hasRole('ROLE_USER') or hasRole('ROLE_VISIT')"/>
<intercept-url pattern="/user/showSecondUserPage" access="hasRole('ROLE_USER')"/>
<intercept-url pattern="/user/showThirdUserPage" access="hasRole('ROLE_VISIT')"/>
<intercept-url pattern="/user/showFirstPage" access="hasRole('ROLE_USER') or hasRole('ROLE_VISIT')"/>
<form-login login-page="/user/showLoginPage.action" />
<logout invalidate-session="true"
logout-success-url="/"
logout-url="/user/j_spring_security_logout"/>
<access-denied-handler ref="myAccessDeniedHandler" />
<custom-filter before="FORM_LOGIN_FILTER" ref="myApplicationFilter"/>
</http>
<beans:bean id="myAccessDeniedHandler" class="code.security.MyAccessDeniedHandler" />
<beans:bean id="myApplicationFilter" class="code.security.MyApplicationFilter">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="authenticationFailureHandler" ref="failureHandler"/>
<beans:property name="authenticationSuccessHandler" ref="successHandler"/>
</beans:bean>
<beans:bean id="successHandler"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/user/showFirstPage"> </beans:property>
</beans:bean>
<beans:bean id="failureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/user/showLoginPage.action?login_error=1"/>
</beans:bean>
<beans:bean id= "myUserDetailServiceImpl" class="code.security.MyUserDetailServiceImpl">
</beans:bean>
<beans:bean id="myAuthenticationProvider" class="code.security.MyAuthenticationProvider">
<beans:property name="userDetailsService" ref="myUserDetailServiceImpl"/>
</beans:bean>
<!-- User Login Ends -->
<!-- Admin Login -->
<http auto-config="true" use-expressions="true" pattern="/admin/*" >
<intercept-url pattern="/index.jsp" access="permitAll"/>
<intercept-url pattern="/admin/showSecondLogin" access="permitAll"/>
<intercept-url pattern="/admin/*" access="hasRole('ROLE_ADMIN')"/>
<form-login login-page="/admin/showSecondLogin"/>
<logout invalidate-session="true"
logout-success-url="/"
logout-url="/admin/j_spring_security_logout"/>
<access-denied-handler ref="myAccessDeniedHandlerForAdmin" />
<custom-filter before="FORM_LOGIN_FILTER" ref="myApplicationFilterForAdmin"/>
</http>
<beans:bean id="myAccessDeniedHandlerForAdmin" class="code.security.admin.MyAccessDeniedHandlerForAdmin" />
<beans:bean id="myApplicationFilterForAdmin" class="code.security.admin.MyApplicationFilterForAdmin">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="authenticationFailureHandler" ref="failureHandlerForAdmin"/>
<beans:property name="authenticationSuccessHandler" ref="successHandlerForAdmin"/>
</beans:bean>
<beans:bean id="successHandlerForAdmin"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
</beans:bean>
<beans:bean id="failureHandlerForAdmin"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/admin/showSecondLogin?login_error=1"/>
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="myAuthenticationProviderForAdmin" />
<authentication-provider ref="myAuthenticationProvider" />
</authentication-manager>
<beans:bean id="myAuthenticationProviderForAdmin" class="code.security.admin.MyAuthenticationProviderForAdmin">
<beans:property name="userDetailsService" ref="userDetailsServiceForAdmin"/>
</beans:bean>
<beans:bean id= "userDetailsServiceForAdmin" class="code.security.admin.MyUserDetailsServiceForAdminImpl">
</beans:bean>
<!-- Admin Login Ends -->
<beans:bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<beans:property name="basenames">
<beans:list>
<beans:value>code/security/SecurityMessages</beans:value>
</beans:list>
</beans:property>
</beans:bean>
Uptill now you can see, url-pattern I have mentioned is hard coded. I wanted to know if there is a way to create new ROLES and PERMISSIONS dynamically, not hard coded.
Like creating new roles and permissions and saving them to database and then accessing from database. I have searched on net, but I am not able to find out how to add new entries to code.
So these are at least two questions:
How to make the granted authorities/privileges/Roles dynamic?
How to make the access restriction for the URLs dynamic?
1) How to make the granted authorities/privileges/Roles dynamic?
I will not answer this in great detail, because I believe this theme was discussed often enough.
The easiest way would be to store the complete user information (login, password and roles) in a database (3 Tables: User, Roles, User2Roles) and use the JdbcDetailService. You can configure the two SQL Statements (for authentication and for granting the roles) very nicely in your xml configuration.
But then the user needs to logout and login to get these new Roles. If this is not acceptable, you must also manipulate the Roles of the current logged in user. They are stored in the users session. I guess the easiest way to do that is to add a filter in the spring security filter chain that updates the Roles for every request, if they need to be changed.
2) How to make the access restriction for the URLs dynamic?
Here you have at last two ways:
Hacking into the FilterSecurityInterceptor and updating the securityMetadataSource, the needed Roles should be stored there. At least you must manipulate the output of the method DefaultFilterInvocationSecurityMetadataSource#lookupAttributes(String url, String method)
The other way would be using other expressions for the access attribute instead of access="hasRole('ROLE_USER')". Example: access="isAllowdForUserPages1To3". Of course you must create that method. This is called a "custom SpEL expression handler" (If you have the Spring Security 3 Book it's around page 210. Wish they had chapter numbers!). So what you need to do now is to subclass WebSecurityExpressionRoot and introduce a new method isAllowdForUserPages1To3. Then you need to subclass DefaultWebSecurityExpressionHandler and modify the createEvaluationContext method so that its first request StandartEvaluationContext calls super (you need to cast the result to StandartEvaluationContext). Then, replace the rootObject in the StandartEvaluationContext using your new CustomWebSecurityExpressionRoot implementation. That's the hard part! Then, you need to replace the expressionHandler attribute of the expressionVoter (WebExpressionVoter) in the xml configuration with your new subclassed DefaultWebSecurityExpressionHandler. (This sucks because you first need to write a lot of security configuration explicity as you can't access them directly from the security namespace.)
I would like to supplement Ralph's response about creating custom SpEL expression. His explanations helped very much on my attempt to find the right way to do this, but i think that they need to be extended.
Here is a way on how to create custom SpEL expression:
1) Create custom subclass of WebSecurityExpressionRoot class. In this subclass create a new method which you will use in expression. For example:
public class CustomWebSecurityExpressionRoot extends WebSecurityExpressionRoot {
public CustomWebSecurityExpressionRoot(Authentication a, FilterInvocation fi) {
super(a, fi);
}
public boolean yourCustomMethod() {
boolean calculatedValue = ...;
return calculatedValue;
}
}
2) Create custom subclass of DefaultWebSecurityExpressionHandler class and override method createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) (not createEvaluationContext(...)) in it to return your CustomWebSecurityExpressionRoot instance. For example:
#Component(value="customExpressionHandler")
public class CustomWebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler {
#Override
protected SecurityExpressionRoot createSecurityExpressionRoot(
Authentication authentication, FilterInvocation fi) {
WebSecurityExpressionRoot expressionRoot = new CustomWebSecurityExpressionRoot(authentication, fi);
return expressionRoot;
}}
3) Define in your spring-security.xml the reference to your expression handler bean
<security:http access-denied-page="/error403.jsp" use-expressions="true" auto-config="false">
...
<security:expression-handler ref="customExpressionHandler"/>
</security:http>
After this, you can use your own custom expression instead of the standard one:
<security:authorize access="yourCustomMethod()">
This question has a very straightforward answer. I wonder why you haven't got your answer yet.
There are two things that should be cleared at least:
First, you should know that when you are using namespace, automatically some filters will be added to each URL you have written.
Second, you should also know that what each filter does.
Back to your question:
As you want to have intercept-url to be dynamically configured, you have to remove those namespaces, and replace them with these filters:
<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="/user/showLoginPage.action" filters="none" />
<sec:filter-chain pattern="/**"
filters="
securityContextPersistenceFilter,
logoutFilter,
authenticationProcessingFilter,
exceptionTranslationFilter,
filterSecurityInterceptor" />
</sec:filter-chain-map>
</bean>
Then you have to inject your own SecurityMetadaSource into FilterSecurityInterceptor. See the following:
<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="myPackage.MyFilterSecurityMetadataSource">
</bean>
But before that, you have to customize 'MyFilterSecurityMetadataSource' first.
This class has to implement the 'DefaultFilterInvocationSecurityMetadataSource'.
As you want to have all roles and URLs in your DB, you have to customize its getAttributes
Now see the following example of its implementation:
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); //Here Goes Code
return attributes;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
do you see the "Here goes your code" comment? You have to implement that method yourself.
I myself, have a table named URL_ACCESS which contains both URLs and their corresponding roles. After receiving URL from user, I look up into that table and return its related role.
As I'm working exactly on this subject, you may ask any questions... I will always answer.
You can use Voter to dynamically restrict access. Also see Get Spring Security intercept urls from database or properties
Create your model (user, role, permissions) and a way to retrieve permissions for a given user;
Define your own org.springframework.security.authentication.ProviderManager and configure is (set its providers) to a custom org.springframework.security.authentication.AuthenticationProvider; this last one should return on its authenticate method a Authentication, which should be setted with the GrantedAuthority, in your case, all the permissions for the given user.
The trick in that article is to have roles assigned to users, but, to set the permissions for those roles in the Authentication.authorities object.
For that I advise you to read the API, and see if you can extend some basic ProviderManager and AuthenticationProvider instead of implementing everything. I've done that with LdapAuthenticationProvider setting a custom LdapAuthoritiesPopulator, that would retrieve the correct roles for the user.

Method #Secured supposed to throw error when no user authenticated

The code for my service follows...
#Controller
#GwtRpcEndPoint
public class ServerServiceImpl implements ServerService {
#org.springframework.security.annotation.Secured("ROLE_ADMIN")
public String runGwtSprMvcHibJpaDemo(String s) {
System.out.println("SecurityContextHolder.getContext()="+SecurityContextHolder.getContext());
System.out.println("SecurityContextHolder.getContext().getAuthentication()="+SecurityContextHolder.getContext().getAuthentication());
}
}
my applicationContext.xml
<security:global-method-security secured-annotations="enabled" jsr250-annotations="disabled" />
but when i call the serviceImpl through gwt-rpc, aren't runGwtSprMvcHibJpaDemo supposed to print out security error since user not yet authenticated? Rather, the method runGwtSprMvcHibJpaDemo is executed with output
SecurityContextHolder.getContext()=org.springframework.security.context.SecurityContextImpl#ffffffff: Null authentication SecurityContextHolder.getContext().getAuthentication()=null
Add
<security:http auto-config="true">
<security:intercept-url pattern="/**" access="ROLE_ADMIN" />
</security:http>
to your xml config and see if that fixes it.
Define bean in your spring context like:
bean id="userDetailsService"
class="packagename.MyUserService">.
Please note that bean name should be extactly same. Spring use this bean internally to start this service.
MyUserService is a implementation of UserDetailsService.

Resources