Announcement Announcement Module
Collapse
No announcement yet.
Spring Security2: how to use HTTP request data to assist authorization evaluation? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring Security2: how to use HTTP request data to assist authorization evaluation?

    Hi all,

    I have created a framework extended from spring security2 used by many applications in the company. Note, the following problem can be easily resolved by using spring security3's EL, but I cannot upgrade to that version.

    Most applications I am working with have the requirement that users can only perform certain actions under their pre-assigned domains (for example, they can only create service request for their pre-assigned departments). The allowed domains are typically implemented as HTML drop down list. On the server side, code has to verify if the domain submitted is really allowed. In order to implement this in the framework, I created:
    1. DomainAwareGrantedAuthority - it contains a collection of domains which is loaded by UserDetailsService
    2. DomainRetriever - individual application must inject an implementation of this interface (the only parameter is HttpServletRequest)
    3. DomainAwareRoleVoter - it will use DomainRetriever to perform additional evaluation for making the authorization decision
    4. similarly, DomainAwareRequestFilter, DomainAwareWrapper, and DomainAwareAuthorizeTag make use of DomainRetriever for making the authorization decision

    This all works well. But the only problem is that this "domain" concept is either "on" or "off" feature. Typically, an application will have both domain enabled URLs and domain disabled URLs (for example, the landing page, where there is no domain in the request). To work around this problem, I could use following options (neither are elegant):
    • ask the application to create 2 set of authorities: one for domain enabled URLs, one for domain disabled URLs; for example, both ROLE_USER and ROLE_USER_DOMAIN_DISABLED will always be created; then on the <intercept-url>, the application will assign the correct role accordingly.

      OR
    • when DomainRetriever returns NULL, the framework will grant the domain. But this violates the convention that security should by default "deny", instead of "grant".

    Is there a better solution to this? Again, this problem can be very easily resolved by using spring security3's EL, but I cannot upgrade.

    Sorry about the long post and thank you.

    -Jav
    Last edited by jav01_2000; Mar 17th, 2011, 02:07 PM.

  • #2
    I'm not sure I understand the use case entirely. Can anyone access the pages that are not domain specific? Is it just authenticated users? You might consider specifying the pages that are not domain specific with IS_AUTHENTICATED_FULLY and use the AuthenticatedVoter.

    Comment


    • #3
      It is easy when domain non-specific pages are either unprotected or doesn't require any roles; I can simply use "IS_AUTHENTICATED_ANONYMOUSLY" or "IS_AUTHENTICATED_FULLY".

      Assuming all protected pages require some roles; but the roles are built with domain specific information, which will be evaluated at runtime and ALL the time. This strategy works if all protected pages are domain specific, but wouldn't work if there are some pages that are not domain specific.

      A typical use case: a ROLE_USER goes to /secure/createServiceRequest (not domain specific); on the page there is a dropdown for all the departments (the domains) that he/she is allowed to access; user selects one of departments and submit a new service request posting to /secure/submitNewServiceRequest (domain specific).

      Right now, we are using the workaround option#1, so ROLE_USER_DOMAIN_DISABLED mapped to /secure/createServiceRequest and ROLE_USER mapped to /secure/submitNewServiceRequest. (basically, using the suffix "_DOMAIN_DISABLED" to skip the domain evaluation at runtime)

      I am at the point thinking this might be the best we can get out of spring security 2. Please prove me wrong.

      Thanks.
      Last edited by jav01_2000; Mar 17th, 2011, 12:51 PM.

      Comment


      • #4
        bump before I give up looking for better solution

        Comment


        • #5
          Sorry it sounded as though you already had given up. I apologize, but judging from your previous response I am still having trouble understanding your requirements. Focusing on the what vs how will help (especially since that is what you are seeking advice about). Do you think you could provide a concrete example of what you are trying to do?

          Cheers,

          Comment


          • #6
            Thank you rwinch for keeping my hope up

            I am posting some much simplified code, hopefully it can explain the requirement better.

            This is the GrantedAuthority I created to contain a collection of domains which will be populated by UserDetailsService:
            Code:
            public class DomainAwareGrantedAuthority extends GrantedAuthorityImpl {
            	private Collection<String> domains;
            
            	public Collection<String> getDomains() {
            		return domains;
            	}
            
            	public DomainAwareGrantedAuthority(String authority, Collection<String> domains) {
            		super(authority);
            		this.domains = domains;
            	}
            }
            This is the interface that application needs to implement and be injected so that it can assist to make authorization decision:
            Code:
            public interface DomainRetriever {
            	public String getDomain(HttpServletRequest request);
            }
            This is the sample implementation for the above interface:
            Code:
            public class DefaultDomainRetriever implements DomainRetriever {
            
            	private boolean useSession = false;
            	private String attributeName = "department";
            
            	public void setAttributeName(String attributeName) {
            		this.attributeName = attributeName;
            	}
            
            	public void setUseSession(boolean useSession) {
            		this.useSession = useSession;
            	}
            
            	public String getDomain(HttpServletRequest request) {
            		String domain = null;
            		try {
            			domain = useSession ? (String)request.getSession(false).getAttribute(attributeName) : request.getParameter(attributeName);
            		}
            		catch (NullPointerException e) {
            			// ignore
            		}
            		return domain;
            	}
            }
            This is the customized RoleVoter, it will first compare the authority name, if it finds a match, it will also check to see if the domain passed from HttpRequest is contained in this particular authority:
            Code:
            public class DomainAwareRoleVoter extends RoleVoter {
            
            	private DomainRetriever domainRetriever = new NullDomainRetriever();
            
            	public void setDomainRetriever(DomainRetriever domainRetriever) {
            		Validate.isTrue(domainRetriever != null);
            		this.domainRetriever = domainRetriever;
            	}
            
            	@Override
            	public boolean supports(Class clazz) {
            		return FilterInvocation.class.isAssignableFrom(clazz);
            	}
            
            	@Override
            	public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
            		int result = super.vote(authentication, object, config);
            		if ((result == ACCESS_GRANTED) && supports(object.getClass())) {
            			result = ACCESS_DENIED;
            
            			GrantedAuthority[] authorities = authentication.getAuthorities();
            			Iterator<ConfigAttribute> it = config.getConfigAttributes().iterator();
            			while (it.hasNext()) {
            				String targetAuthority = it.next().getAttribute();
            				for (int i = 0; i < authorities.length; i++) {
            					GrantedAuthority auth = authorities[i];
            					if (targetAuthority.equals(auth.getAuthority())) {
            						if (isDomainGranted(auth, ((FilterInvocation)object).getHttpRequest()))
            							return ACCESS_GRANTED;
            					}
            				}
            			}
            		}
            		return result;
            	}
            
            	private isDomainGranted(GrantedAuthority authority, HttpServletRequest request) {
            		return !(authority instanceof DomainAwareGrantedAuthority) ||
            			((DomainAwareGrantedAuthority)authority).getDomains().contains(
            				domainRetriever.getDomain(request)
            			);
            	}
            }
            Here is the much simplified application context, the UserDetailsService must create "ROLE_USER_DOMAIN_DISABLED" using GrantedAuthorityImpl, because it will be mapped to domain non-specific URL "/secure/createServiceRequest"; whereas "ROLE_USER" must be created using DomainAwareGrantedAuthority, because it will be mapped to domain specific URL "/secure/submitNewServiceRequest":
            Code:
            <security:http access-denied-page="/access_denied.jsp" access-decision-manager-ref="domainAwareAccessDecisionManager">
            	<security:intercept-url pattern="/secure/createServiceRequest" access="ROLE_USER_DOMAIN_DISABLED" />
            	<security:intercept-url pattern="/secure/submitNewServiceRequest" access="ROLE_USER" />
            	<security:intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_FULLY" />
            	<security:anonymous />
            </security:http>
            
            <bean id="domainRetriever" class="DefaultDomainRetriever" />
            
            <bean id="domainAwareAccessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
            	<property name="decisionVoters">
            		<list>
            			<bean class="DomainAwareRoleVoter">
            				<property name="domainRetriever" ref="domainRetriever" />
            			</bean>
            			<bean class="org.springframework.security.vote.AuthenticatedVoter" />
            		</list>
            	</property>
            </bean>
            A concrete use case:
            1. user goes to /secure/createServiceRequest; this page is an empty service request submission page, there is a dropdown on the page pre-populated with all the "departments" that this user is allowed to access under his ROLE_USER authority.
            2. user fill up the page, and select one of the "department" from the dropdown and click "submit".
            3. the page will be posted to /secure/submitNewServiceRequest with "department" as one of request parameters.
            4. on the server side, we need to make sure the value of "department" is one of the "departments" this user is allowed to access.

            Hopefully this is clear enough.
            Last edited by jav01_2000; Mar 18th, 2011, 04:13 PM.

            Comment


            • #7
              So if you can figure out what domains the user can set, why do you even need to set the domain? Couldn't you use the same logic that determines what domains are allowed to base the access control? At that point the user wouldn't have to set a domain either.

              Comment


              • #8
                Domains are stored at user store, so it is nature to store them in UserDetails, as well as in user's authorities, and loaded by UserDetailsService.

                I don't understand the statement "at that point the user wouldn't have to set a domain either". They may have multiple domains assigned, so they need to pick one to submit the form, at which point, the server has to verify the domain submitted is allowed using the value in HttpRequest. Instead of letting the corresponding servlets to do the job (they might skip this important step), I am just doing this check in the security framework.

                Comment


                • #9
                  Originally posted by jav01_2000 View Post
                  Domains are stored at user store, so it is nature to store them in UserDetails, as well as in user's authorities, and loaded by UserDetailsService.
                  It seems that you are using the DomainAwareGrantedAuthority's to determine what domains a user can set. Why don't you load all the things the user is allowed to access rather than forcing the user to select a domain? This is similar to how a user might have ROLE_USER and ROLE_ADMIN. They might have DOMAIN_A, DOMAIN_B, ROLE_USER.

                  If the number of domains a user is associated to is too large, it probably makes more sense to approach this in the same way permissions are done. More specifically, looking up to see if the user has permission on each request, but using caching to ensure you still get good performance.

                  Originally posted by jav01_2000 View Post
                  I don't understand the statement "at that point the user wouldn't have to set a domain either". They may have multiple domains assigned, so they need to pick one to submit the form, at which point, the server has to verify the domain submitted is allowed using the value in HttpRequest. Instead of letting the corresponding servlets to do the job (they might skip this important step), I am just doing this check in the security framework.
                  If you have the logic to determine which domains they can set, then as I understand your scenario you don't need to actually set the domain you can look up the domain for each request (possibly caching it). From what I understand this might provide a better user experience since the user would not be required to select a domain.

                  Comment

                  Working...
                  X