Page 1 of 2 12 LastLast
Results 1 to 10 of 14

Thread: How to implement Entity/Finder authorization?

  1. #1
    Join Date
    Apr 2009
    Posts
    21

    Default How to implement Entity/Finder authorization?

    Hello everyone.

    Consider a newly created Roo-App (including "security setup") in which each user may only access his own data (order, shopping cart... the usual). How do I integrate authorization "the right way" in this situation?

    I know about the Spring Security annotations (PreAuthorize, PostFilter) and they do work for my service methods, but when copying the static findOrder(Long id) method and adding a @PostAuthorize to it, I felt like doing something wrong. I did take a brief look at Spring Security's contact sample with its ACLs, it seemed to complex for my case, but you may refer me back to that if it is the way to go.

    So I am still wondering, if there is a more general solution to this, especially to "harden" the generated finder method or an entity globally (like "accessing an order in any way is only allowed if order.customer.username == principal.username or principal.hasRole(ROLE_ADMIN)").

    Thanks for any answer/reference link/sample code

    Wolfram

  2. #2
    Join Date
    Jan 2009
    Location
    Huntington Beach, CA
    Posts
    718

    Default

    Two recommendations.

    1) Use Hibernate Filters.

    2) My new favorite, use Spring Expression Language in your @RolesAllowed to access user information. I have seen this in the new Spring Security Documentation, but haven't actually done it yet myself.

    Mark

  3. #3
    Join Date
    Nov 2009
    Location
    Mexico City, Mexico
    Posts
    13

    Default

    Hi, I'm doing something like this right now, and thought I'd share what i've got working right now.
    Short answer is by doing
    Code:
    SecurityContextHolder.getContext().getAuthentication().getPrincipal()
    you get the currently authenticated user. However the users are pre-defined on an XML file. So what to do?

    First thing is getting the user from the database. To do that I create an entity that will hold users. I also have now Roles mapped to an enum, as I won't be changing them for now and add a finder to locate an user by its username
    Code:
    entity --class ~.domain.Person
    field string --fieldName username --notNull
    field string --fieldName password
    field boolean --fieldname enabled
    
    enum type --class ~.domain.PersonRole
    enum constant ROLE_USER
    enum constant ROLE_ADMIN
    
    field enum --fieldName role --type ~.domain.PersonRole --enumType STRING  --class ~.domain.Person
    finder add findPeopleByUsername
    the open the Role enum and let it implement the GrantedAuthority Interface
    Code:
    public enum PersonRole implements GrantedAuthority {
    	ROLE_USER, 
    	ROLE_ADMIN;
    
    	@Override
    	public String getAuthority() {
    		return name();
    	}
    }
    and get the Person class to implement UserDetails
    Code:
    @Entity
    @RooJavaBean
    @RooEntity(finders = { "findPeopleByUsername" })
    public class People implements UserDetails {
        @NotNull
        @Size(min = 4, max = 30)
        private String username;
    
        @NotNull
        private String password;
    
        @Enumerated(EnumType.STRING)
        private PersonRole role;
    
        private Boolean enabled;
       @Override
       public Collection<GrantedAuthority> getAuthorities() {
         return Arrays.asList((GrantedAuthority) role);
       }
    
       @Override
       public boolean isAccountNonExpired() {
         return enabled;
       }
    
       @Override
       public boolean isAccountNonLocked() {
         return enabled;
       }
    
       @Override
       public boolean isCredentialsNonExpired() {
         return enabled;
       }
    
       @Override
       public boolean isEnabled() {
         return enabled;
       }
    the last thing here is implement a class that implements UserDetailsService
    Code:
    @Component("userDetailsService")
    public class JpaUserDetailsService implements UserDetailsService {
    
    	@Override
    	public UserDetails loadUserByUsername(String username)
    			throws UsernameNotFoundException, DataAccessException {
    		try {
    			return (UserDetails) Person.findPeopleByUsername(username).getSingleResult();
    		} catch (EntityNotFoundException ex) {
    			throw new UsernameNotFoundException(ex.getMessage());
    		}
    	}
    
    }
    and enable it on applicationContext-security
    replace
    Code:
    <authentication-manager alias="authenticationManager">
        	<!-- SHA-256 values can be produced using 'echo -n your_desired_password | sha256sum' (using normal *nix environments) -->
        	<authentication-provider>
    	    	<password-encoder hash="sha-256"/>
    	        <user-service>
    	            <user name="admin" password="8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918" authorities="ROLE_ADMIN"/>
    		        <user name="user" password="04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb" authorities="ROLE_USER"/>
    		    </user-service>
        	</authentication-provider>
    	</authentication-manager>
    by
    Code:
     <authentication-manager alias="authenticationManager">
        	<authentication-provider user-service-ref="userDetailsService"/>
     </authentication-manager>
    Up to here you will have the authentication working on the database instead of the pre-defined users on the xml file. It would be a great moment to load users and test it to make sure the auth is working.

    So the second part is obtaining the user data. You can load as I told at the beggining by doing that static method call, however static method feel seem un-springful to me, so I created a helper class that I inject through spring
    Code:
    public class ContextUserDetails {
    	private final SecurityContextHolderStrategy holderStrategy;
    
    	public ContextUserDetails(SecurityContextHolderStrategy holderStrategy) {
    		this.holderStrategy = holderStrategy;
    	}
    
    	public Person getPerson() {
    		Object principal = holderStrategy.getContext().getAuthentication().getPrincipal();
    		if (principal instanceof Person) {
    			return (Person) principal;
    		}
    		// principal object is either null or represents anonymous user -
    	    // neither of which our domain User object can represent - so return null
    	    
    		return null;
    	}
    }
    and define it on applicationContext.xml
    Code:
    	<!--Gets the active person object-->
     	<bean id="contextUserDetails" class="com.jeduan.util.ContextUserDetails">
     		<constructor-arg>
     			<bean class="org.springframework.security.core.context.SecurityContextHolder" factory-method="getContextHolderStrategy"/>
     		</constructor-arg>
     	</bean>
    so now when you want to use it just
    Code:
    @Autowired
    private ContextUserDetails contextUser;
    
    public someMethod() {
      Person person = contextUser.getPerson()
    }
    Whoa, so that was long, but hopefully you will get the hold of it rather promptly.
    Kind regards and best of lucks with your application!

    EDIT:
    Forgot the important part, what you asked.

    So, after all this setup, you then could do something like
    Code:
    @RequestMapping
    public String addToCart(Long itemId, ModelMap modelMap) {
        Item item = Item.findItem(itemId);
        item.setShopper(contextUser.getPerson);
        item.persist()
    }
    or setting up finders to find by the person you just got from the context, you get the idea.
    Best Regards
    Last edited by jeduan; Feb 18th, 2010 at 07:19 PM.

  4. #4
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    667

    Post

    That's good as far as it goes, but I think what Wolfram is asking is how do you allow the user to go to this URL:
    http://hostname/appname/shoppingcart/29
    ... where that's the user's own shopping cart, but not to this URL:
    http://hostname/appname/shoppingcart/30
    ... where that's someone else's shopping cart.
    Andrew Swan
    "Now is the EJB of our discontent made glorious Spring"

  5. #5
    Join Date
    Jan 2009
    Location
    Huntington Beach, CA
    Posts
    718

    Default

    OK, I found this

    http://blog.springsource.com/2009/06...00m1-released/

    But I thought it was adding something to @RolesAllowed, but it is @PreAuthorize, or @PostAuthorize.

    I really like this approach the best now, it is far less to write than what jeduan posted, in which he is customizing the UserDetailsService and UserDetails objects.

    Now I am not against customizing UserDetails at all, as a matter of fact, I have done that in a real app. (I do some non-real apps to practice) And it makes for some cool ways to keep data that you would have normally put into the Session, but able to hold in the UserDetails object for quick access.

    But even with a custom UserDetails object, you can use it in the Spring expression language in your @Post or @PreAuthorize.

    Oooh, just saw this I like it

    http://stsmedia.net/spring-finance-p...3-integration/

    the third code box, first @PostFilter.

    But something looked familiar there. jeduan, that isn't your stuff is it? I saw a Person object and thinking it was your custom UserDetails object.

    Anyway, really looking at that last link and all the parts of that series, it looks like a really sweet good article to read.

    I digress

    Mark

  6. #6
    Join Date
    Jan 2009
    Location
    Huntington Beach, CA
    Posts
    718

    Default

    DOH!.

    Now, I realize why it is a cool blog, cause it is Stefan Schmidt's site.

    That is really funny. I think I am turning red.

    Mark

  7. #7
    Join Date
    Nov 2009
    Location
    Mexico City, Mexico
    Posts
    13

    Default

    There techniques I described are parts from here and there, but definitely Stefan's blog was one of the references I got

    Currently SpringSecurity's @PostFilter and @PreAuthorize are not working on Roo AFAIK, due to some stuff going on with aspects. The blog you linked creates an intermediate layer annotated with @Service which communicates with Entities and turns the results back to the Controllers.

    Agreed on the part that my method is really long and maybe cumbersome. OTOH you can get friendlier URLs that do not include the user's id, due to it being already on the SecurityContext, ie
    Code:
    http://server:8080/appname/shoppingcart
    instead of
    Code:
    http://server:8080/appname/shoppingcart/81
    which may not sound as that much of a hassle, but I find it more polished.

  8. #8
    Join Date
    Apr 2009
    Posts
    21

    Default

    @jeduan:

    Thanks for your elaborate description, but as andrews said, I do have all of that working already, but was looking for the "cleanest" solution for a business logic authorization structure (let's call it that).

    You are right that in case of a OneToOne relationship you do not really have to include any ID as you can get the shopping cart by using the information about the principal. However, in case of a OneToMany (like shipping addresses or orders) you need to specifiy an ID ("/app/order/14" and 14 is not one of the principal's orders). That is where I was stuck, stuck as in "could do it but it feels wrong somehow".

    Regarding methods on the entity you are right, as soon as I annotate a finder-method with @PostFilter, I do get an java.lang.IllegalStateException from the checkExposedObject() as the exposedObject and originalBeanInstance differ:

    Code:
    Post-processor tried to replace bean instance of type [com.company.project.MyType] with (proxy) object of type [$Proxy53] - not supported for aspect-configured classes!
    @bytor99999:

    I'll definitely have a closer look on those Hibernate filters, didn't know about them as I was looking for a Spring Security based solution.

    ---

    Considering the following two statements + the aforementioned exception, I come to think that you have to use a service layer in order to apply Spring Security's annotations in a Roo application.

    From the Roo reference:
    A web application will rarely require a services layer, as most logic can be placed in the web controller handle methods and the remainder in entity methods.
    From the Spring Security FAQ:
    Generally we would recommend applying method security at the service layer rather than on individual web controllers.
    I think I will go with @Services and securing their methods using Spring Security annotations (at least for write operations) + using Hibernate filters or additional constraints when reading data from the databse.

    Thanks everyone!

  9. #9
    Join Date
    Jan 2009
    Location
    Huntington Beach, CA
    Posts
    718

    Default

    This is definitely an area where it would be nice to know the "standard" best practice approach to "ACL" security. There seems to be many different possible solutions, and which one is the "best" might end up just being subjective.

    jeduan - I was definitely not disagreeing with you. I have followed and used similar approaches, and will have to on the project I am working for my iPhone app I have in the Apple App Store. Long Story

    But lets just say that there is "Entitys" that I have that might equal to User. Or in Spring terms UserDetails object. Meaning I need the UserDetails object for security and there are entities in my model that you can map one to one to my User, and storing those entity objects in the UserDetails object seemed to me a clean nice solution, and I have used it.

    Now, an issue might come up where there can be a One To Many relationship between your UserDetails and another Entity, but then comes into question of are we trying to store too much into the UserDetails and the benefit/cleaness is gone? Or what happens when the related data ends up mapping Many To Many between UserDetails and the other Entity.

    I don't know if maybe starting a new thread would be better to see if anyone want to further discuss this and see what we might collectively come up with. I am definitely interested.

    Mark

  10. #10
    Join Date
    Jan 2009
    Location
    Huntington Beach, CA
    Posts
    718

    Default

    In looking at the Spring 3.x documentation, I saw this

    "<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <aop:scoped-proxy/>
    </bean>

    <bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
    </bean>"

    So, what if the Entity that we stored in the UserDetails object instead was declared as a bean set to session scope?

    Then, do we have access to it using SPel, in the @Pre or @Post authorize annotation, and can get a value from that bean, and compare it to a PathVariable in the URI??

    OK, this does seem out of scope to Roo at this point.

    But, if Roo could have an option in an entity to set it to Session scope and well, I am just thinking here.

    Mark

    Oh, one last point, we could also make the Entity in question to be a FactoryBean, or use factory-method, if there needs to have some properties dynamically set at runtime to help create the Entity instance. OK, my brain hurts now, I will stop
    Last edited by bytor99999; Feb 19th, 2010 at 02:38 PM. Reason: Add more stuff

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •