Spring MVC, Method level security - spring

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?)

Related

Save web form values prior to redirect to login page with Spring Security

I'm using Spring security 4.0. I have a basic web form which submits by POST to /SaveForm. In order to do this you have to be an authenticated user.
<security:intercept-url pattern="/SaveForm" access="isAuthenticated()" />
<security:intercept-url pattern="/login" access="permitAll" />
My form-login is as follows where LoginAuthenticationSuccessHandler which implements AuthenticationSuccessHandler.
<security:form-login login-page="/login"
authentication-failure-url="/login" username-parameter="email"
password-parameter="password"
authentication-success-handler ref="LoginAuthenticationSuccessHandler" />
Not sure if it's relevant but I'm also using CustomAuthenticationProvider bean which implements AuthenticationProvider (which just returns true or false).
<security:authentication-manager>
<security:authentication-provider
ref="CustomAuthenticationProvider">
</security:authentication-provider>
</security:authentication-manager>
Once on the login page I'm saving the header "Referer" so I can redirect back to that page after. See Controller below
#RequestMapping("/login")
public String login(HttpServletRequest request, HttpSession session) {
String referringURL = request.getHeader("Referer");
session.setAttribute("referring_url", referringURL);
Then in my LoginAuthenticationSuccessHandler class ( which implements AuthenticationSuccessHandler ) I redirect back to the original web form
redirectURL = (String) session.getAttribute("referring_url");
response.sendRedirect(redirectURL);
This "hack" works but it seems that in Spring you can automatically redirect back to the original page that was requested without this. I can't seem to configure it however. My problem is the original form values are lost and I can't figure out where to save them. Once I submit to /SaveForm Spring security intercepts as expected but then how can I save the form values? I want to be able to re-populate the form with the values they typed before being made to authenticate.

Securing url pattern based on user properties Spring security

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.

Secure rest api using spring security - custom status codes

I am using spring security (4.0.0.M2) to secure my web application and also my rest api. everything works great. but I have one problem. I want to return http result 403 (forbidden) instean of 401, when user could not be authenticated
I defined different http definitions, for each authentication scenario, one for web and one for api.
<http pattern="/rest/v?/**" auto-config="false" use-expressions="true"
disable-url-rewriting="true" authentication-manager-ref="tokenAuthenticationManager"
create-session="stateless" realm="API security check"
entry-point-ref="http403EntryPoint">
<intercept-url pattern="/rest/v?/*" access="isAuthenticated()" />
<anonymous enabled="false" />
<http-basic />
</http>
<authentication-manager id="tokenAuthenticationManager" erase-credentials="true">
<authentication-provider user-service-ref="tokenUserDetailsService" />
</authentication-manager>
public class TokenUserDetailsService implements UserDetailsService {
#Override
public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String checkUser)
throws UsernameNotFoundException {
// lookup user
if (user == null) {
// here I whant to return 403 instead of 401
throw new UsernameNotFoundException("User not found");
}
}
}
Could somebody help to return http status code 403 in this case?
Thank you for help.
Best regards
sonny
The BasicAuthenticationFilter which is created by the <http-basic> element also has a reference to an entry point. It invokes this when authentication fails and this is where the 401 code comes from (which is normal with Basic authentication).
If you want to change it you can use the entry-point-ref namespace attribute. So
<http-basic entry-point-ref="http403EntryPoint" />
should give the result you want.
Currently I found out a working solution. When I use a custom filter (position=pre_auth), then I could change the response code using doFilter method.
But I am not sure, if this is really the best solution.

Spring Security hasRole('ROLE_ADMIN') in config and #PreAuthorize("permitAll") not working?

I'm trying to lock down my entire app except a particular URL/method.
Here's my applicationContext-security.xml:
<global-method-security pre-post-annotations="enabled"/>
<http use-expressions="true">
<http-basic/>
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />
<logout logout-success-url="/products" />
</http>
Here's the class with its annotation:
#RooWebScaffold(path = "products", formBackingObject = Product.class)
#RequestMapping("/products")
#Controller
public class ProductController {
#RequestMapping(value="/json", headers = "Accept=application/json")
#ResponseBody
#PreAuthorize("permitAll")
public String listJson() {
return Product.toJsonArray(Product.findAllProducts());
}
}
However, it's not working as expected.
If I swap the conditions around and have permitAll in the config and the hasRole() in the annotation it works as expected - but I'm trying to achieve the reverse.
Any advice would be greatly appreciated!
If you are using XML Configuration don't forget to add the following attribute:
<s:global-method-security pre-post-annotations="enabled"/>
If you are using Java Configuration don't forget to add the following annotation:
#EnableGlobalMethodSecurity(prePostEnabled = true)
It is almost similar to question spring security 3 - Setting up a customized login, if you restrict all access (pattern /**) to role_admin then how the permitAll on /product would work? Solution would be to provide IS_AUTHENTICATED_ANONYMOUSLY access on /product.
From #PreAuthorize and intercept-url priority
:
<intercept-url> ... takes precedence over (#PreAuthorize) annotations. [, since] <intercept-url> works at URL level and annotations at method level.
So the solution to your problem (apart from #PreAuthorize annotations) must be addressed in your security config.
You have to declare /prodcuts/json intercept url with permitAll (or anonymous..) before /** intercept url pattern like:
...
<intercept-url pattern="/products/json" access="permitAll" />
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />
...
From https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#nsa-intercept-url:
When matching the specified patterns against an incoming request, the matching is done in the order in which the elements are declared. So the most specific patterns should come first and the most general should come last.

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