I have started using spring security, and after a lot of research I am not able to find an answer for:
If I explicitly want to check if user A have access to stuff B. I can check this with JSP tag support Spring Security - check if web url is secure / protected like
<sec:authorize url="stuff/B">
But what if I want to check the same thing in the controller(java class). I am not finding any spring function here to check if a login user has access to mentioned url(https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html)
Hint from the javadoc:
to use this tag there must also be an instance of WebInvocationPrivilegeEvaluator in your application context. If you are using the namespace, one will automatically be registered. This is an instance of DefaultWebInvocationPrivilegeEvaluator,"
And in the javadoc of DefaultWebInvocationPrivilegeEvaluator, we can see a isAllowed method that should do the job:
// privilegeEvaluator is a WebInvocationPrivilegeEvaluator "autowired"
boolean allowed = privilegeEvaluator.isAllowed("/stuff/B", yourAuthentication);
Why not to use annotations like this:
#PreAuthorize("hasRole('ROLE_USER')")
public void create(Contact contact);
Annotations are standard way for Spring 3+
You are looking at the right place, the link you attached mentions what you need. Since you want access-control on your controller and check per user (not role) you can use the '#PreAuthorize' annotation with "hasPermission" expression or similar.
You could check here for expression-based access control and here for examples of custom security expression example in case you want to customize the solution.
1) First we need to know whether the user may enter the URL at all. This can be very easily achieved using WebInvocationPrivilegeEvaluator.
privilegeEvaluator.isAllowed(contextPath, url, "GET", currentUser);
2) Now we need to identifying whether the user may access the handler method
private boolean isAllowedByAnnotation(Authentication currentUser, HandlerMethod method) {
PreInvocationAuthorizationAdvice advice = new ExpressionBasedPreInvocationAdvice();
PreInvocationAuthorizationAdviceVoter voter = new PreInvocationAuthorizationAdviceVoter(advice);
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
PrePostInvocationAttributeFactory factory = new ExpressionBasedAnnotationAttributeFactory(expressionHandler);
PrePostAnnotationSecurityMetadataSource metadataSource = new PrePostAnnotationSecurityMetadataSource(factory);
Class<?> controller = method.getBeanType();
MethodInvocation mi = MethodInvocationUtils.createFromClass(controller, method.getMethod().getName());
Collection<ConfigAttribute> attributes = metadataSource.getAttributes(method.getMethod(), controller);
return PreInvocationAuthorizationAdviceVoter.ACCESS_GRANTED == voter.vote(currentUser, mi, attributes);
}
We can create a custom PermissionEvaluator and use
hasPermission(Authentication authentication, Object domainObject,
Object permission).
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
final DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
return expressionHandler;
}
#Bean
public aclServiceImpl aclService() {
final AclServiceImpl mutableAclService = new AclServiceImpl
(authorizationStrategy(), grantingStrategy());
return mutableAclService;
}
AclServiceImpl is the implementation of MutableAclService
The most obviously useful annotation is #PreAuthorize which decides whether a method can actually be invoked or not. For example (from the"Contacts" sample application)
#PreAuthorize("hasRole('USER')")
public void create(Contact contact);
which means that access will only be allowed for users with the role "ROLE_USER". Obviously the same thing could easily be achieved using a traditional configuration and a simple configuration attribute for the required role. But what about:
#PreAuthorize("hasPermission(#contact, 'admin')")
public void deletePermission(Contact contact, Sid recipient, Permission permission);
Here we’re actually using a method argument as part of the expression to decide whether the current user has the "admin"permission for the given contact. The built-in hasPermission() expression is linked into the Spring Security ACL module through the application context.
For More Detailed Explanation please refer this Link
Related
I have spring boot application which exposes REST endpoints protected by spring security.
I need to restrict access to some paths depending on service call. Let's say I have a service like this:
#Service
public class AccessService {
boolean hasAccess(String requestedPath) {
// some business logic here
}
}
The service will check user roles, some business conditions and return true or false.
Now I need to integrate this service call into my security configuration.
So far I have configuration like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.and().authorizeRequests()
.anyRequest().hasRole("USER");
}
I see no way of adding the service call here (as it is completely static).
What I'm trying:
Currently I'm thinking of overriding my AuthenticationProvider and extending it with the additional functionality.
The other option would be to extend my REST controllers from a class which would do some sort of authorization, but I'm not sure if it is possible.
Question: How can I protect REST endpoints based on service method call? What is the proper way of doing that?
This is explained in the reference guide. Basically you need to use the access expression instead of the hasRole. You can then write powerful security expressions.
Something like the following should do the trick:
anyRequest()
.access("#accessService.hasAccess(request.requestURI) && hasRole('USER')");
This restricts access to user with the role ROLE_USER and which have access according to your own custom logic.
I think a good way to to this is to use #PreAuthorize
Some documentation can be found here: Expression-Based Access Control.
You are also able to add your own evaluator class/methods to customize to your specific needs:
#PreAuthorize("#customPermissionEvaluator.accessMethod(variable)")
Example class:
#Service(value = "customPermissionEvaluator")
public class CustomPermissionEvaluatorImpl implements CustomPermissionEvaluator {
#Override
public boolean accessMethod(int variable) {
if (variable == 1) {
return true;
}
return false;
}
}
The setup of the RESPApi project is:
SpringBoot
Spring's OAuth2
In the project we have many clients, so SQL queries almost always have "... and clientId = ?" in the where clause.
We store clientId in the SecurityContext with other user details (we extend Spring's User class).
The question is: how to get the User object in the #Repository?
Possible solutions we can think of:
In every repository implementation add
SecurityContextHolder.getContext().getAuthentication()
cast the result to our custom UserDetails implementation and use it.
Cons: somehow I feel there's a better solution.
Add #AuthenticationPrincipal annotated parameters to the controllers and then pass the parameters to the service layer and then to the repository layer.
Cons: passing the paremeter though 2 layers only to obtain clientId doesn't seem reasonable.
I thought about #Autowired paramter MyUser user in the #Repository class. The first try was to create #Configuration annotated class in which there will be a method
#Bean
public MyUser getUser() {
SecurityContext context = SecurityContextHolder.getContext();
if (context != null) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
return (MyUser) authentication.getPrincipal();
}
}
return null;
}
But the bean is null and I cannot use it.
For now we've ended up with solution nr 1 but I feel there must be a better way.
Any ideas how to solve this problem?
If you're using Spring Data (or have the time to switch to using it), you can use the SecurityEvaluationContextExtension and use principal directly in your queries:
https://stackoverflow.com/a/29692158/1777072
If not, you could hide the static access if it offends (or if you want more control over it changing in future):
#Component
public class AuthenticationHelper {
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Then inject that class into your Repository.
Or your Service. That's probably a better fit than the Repository.
I like to keep Repositories dumb (ultimately using Spring Data to avoid writing them entirely).
And I like to think of Services as being separated out of the web layer, running on separate boxes (even if they aren't). In that situation, you would never pass the Authentication details over HTTP from Controller to Service. The service would obtain authentication details for itself, rather than just trusting what the web layer sent it.
So I think the Service should get the details itself, rather than the Controller passing them through.
Your bean is null because by default beans are singleton and they are created when the application starts, and as you can imagine, you are not going to have a SecurityContext at that point.
Try declaring your bean with request scope, in this way:
#Bean
#Scope(value=WebApplicationContext.SCOPE_REQUEST, proxyMode=ScopedProxyMode.TARGET_CLASS)
public MyUser getUser() {
.....
}
I want to validate my MyAccount form using a custom Spring Validator.
Basic validation rules are no problem.
I now have this requirement, which seems pretty obvious and common:
If the user (currently logged in) changes his username, I'll have to check if it's already in use. If it's not been changed, I'm fine (it would definitely be in use when checked, that's why I don't want to check it in that case). The problem is, that the validator is a Spring managed Singleton and I don't seem to have access to the current session (where I store my login context - i.e. not Spring Security). But I need the currently logged in user, in order to compare its e-mail with the one entered in the form.
This is my scenario, but the question is actually about how to validate using an object from the user's session, in general.
The ways I was thinking about solving this:
Do only basic validation in the Validator and do the rest, that I need the session for, in the Controller. Doesn't seem to be a nice solution.
#RequestMapping(value="/myaccount", method=RequestMethod.GET)
public String myAccount(#Valid MyAccountForm form, BindingResult result, HttpSession session)
{
boolean hasUsernameChanged = // check using session
if (hasUsernameChanged && CustomerService.customerAlreadyExists(form.getUsername()))
result.rejectValue("username", "my.error.code");
if (result.hasErrors()) {
// handle errors
} else {
// proceed
}
}
Adding a second validate method to the Validator like so
public void validateWithCurrentCustomer(Customer current) {
...
}
and call it explicitly from the controller, with the appropriate object. Not much better, but at least the validation logic is in one class, although separated in two methods, one of which is not standard.
Not having the Validator a Spring managed (singleton) bean, but create it everytime in the initBinder method. Instead of:
#Autowired
public MyAccountController(MyAccountFormValidator validator)
{
this.validator = validator;
}
#InitBinder
protected void initBinder (WebDataBinder binder)
{
binder.setValidator(validator);
}
do
#InitBinder
protected void initBinder (WebDataBinder binder, HttpSession session)
{
Customer current = ...// get from session
binder.setValidator(new MyAccountFormValidator(current));
}
But here, the problem is, that since the validator is not Spring managed, it's hard to get a service (like CustomerService) injected into the validator, for checking if an e-mail is available or not.
Am I missing something, any other ways to achieve what I want? This seems to be a common problem, but I couldn't find any pattern on Google or SO.
Thanks for your hints.
You may try to access any Spring bean from your validator. Maybe this answer can help.
Where should I place validation code that requires access to database?
Basically, you can make a SpringBeanUtil singleton, which gives you access to any bean you want.
I'd like to use some (Java) methods in my controllers that are similiar to the built-in expressions provided by Spring Security e.g. hasRole([role]) or isFullyAuthenticated().
Do you know where I can find these methods and how to call them within a Java method of a controller (I don't want to use Spring EL, I want to use plain Java)? E.g something like
SomeStaticSpringSecutityClass.isFullyAuthenticated();
EDIT:
SecurityContextHolder.getContext().getAuthentication().isAuthenticated()
Actually doesn't really work. This method also returns true if the user is authenticated as "Anonymous". See the link to the spring security docs from above:
isAuthenticated() Returns true if the user is not anonymous
Instead you have to use something like that:
public boolean isAuthenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return !(authentication == null || authentication instanceof AnonymousAuthenticationToken);
}
But anyway: I don't really want to implement logic again, that is already implemented somewhere in Spring Security. Additionally
SecurityContextHolder.getContext().getAuthentication()
does not provided methods like isFullyAuthenticated() or hasRole().
Take a look at org.springframework.security.core.contex.SecurityContextHolder. For instance to check if the current user is authenticated:
SecurityContextHolder.getContext().getAuthentication().isAuthenticated()
I found the SecurityContextHolderAwareRequestFilter that use the interface:
AuthenticationTrustResolver.isAnonymous(Authentication authentication);
If you want to use the Spring source code, you can check the class SecurityContextHolderAwareRequestWrapper that instantiates this interfaces as below:
private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
public Authentication getAuthentication() {
if (!authenticationTrustResolver.isAnonymous(auth)) {
return auth;
}
return null;
}
I looked into documentation and I didn't find a utility class to do it.
Is there any way under spring 3.0 to access the HttpSession without including it in the method signature? What I really want to do is be able to pass in values from an HttpSession that CAN BE null.
Something like this:
#RequestMapping("/myHomePage")
public ModelAndView show(UserSecurityContext ctx) {}
instead of this:
#RequestMapping("/myHomePage")
public ModelAndView show(HttpSession session) {
UserSecurityContext ctx = (UserSecurityContext) session.getAttribute("userSecurityCtx");
}
The #SessionAttribute annotation mentioned by #uthark is not suitable for this task - I thought it was too, but a bit of reading shows otherwise:
Session attributes as indicated using
this annotation correspond to a
specific handler's model attributes,
getting transparently stored in a
conversational session. Those
attributes will be removed once the
handler indicates completion of its
conversational session. Therefore, use
this facility for such conversational
attributes which are supposed to be
stored in the session temporarily
during the course of a specific
handler's conversation.
For permanent session attributes, e.g.
a user authentication object, use the
traditional session.setAttribute
method instead. Alternatively,
consider using the attribute
management capabilities of the generic
WebRequest interface.
In other words, #SessionAttribute is for storing conversation MVC-model objects in the session (as opposed to storing them as request attributes). It's not intended for using with arbitrary session attributes. As you discovered, it only works if the session attribute is always there.
I'm not aware of any other alternative, I think you're stuck with HttpSession.getAttribute()
You can use a RequestContextHolder:
class SecurityContextHolder {
public static UserSecurityContext currentSecurityContext() {
return (UserSecurityContext)
RequestContextHolder.currentRequestAttributes()
.getAttribute("userSecurityCtx", RequestAttributes.SCOPE_SESSION));
}
}
...
#RequestMapping("/myHomePage")
public ModelAndView show() {
UserSecurityContext ctx = SecurityContextHolder.currentSecurityContext();
}
For cross-cutting concerns such as security this approach is better because you doesn't need to modify your controller signatures.
Yes, you can.
#SessionAttributes("userSecurityContext")
public class UserSecurityContext {
}
#RequestMapping("/myHomePage")
public String show(#ModelAttribute("userSecurityContext") UserSecurityContext u) {
// code goes here.
}
See for details:
http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/bind/annotation/SessionAttributes.html
http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/bind/annotation/ModelAttribute.html