Authorizing on services and controllers

In this recipe, we restrict the access to services and controllers depending upon the authorities that are granted to users.

Getting ready

We are going to install interceptors on specific URL paths and method-invocations, which will trigger a predefined authorization workflow: the AbstractSecurityInterceptor workflow.

In order for us to test these services' restrictions, we also slightly customized the Swagger UI to use it over a BASIC authentication.

How to do it...

  1. We updated our CustomBasicAuthenticationEntryPoint class for this new version that allows the browser native BASIC-form to be prompted when the call is made from Swagger UI:
    public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
      @Override
      public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        String referer = (String) request.getHeader("referer");
        if(referer != null && referer.contains(SWAGGER_UI_PATH)){
          super.commence(request, response, authException);
          return;
        }
        response.setHeader("WWW-Authenticate", "CSM_Basic realm=" + getRealmName() + ");
        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
      }
    }
  2. We created a MonitoringController (a RestController) that offers the possibility to manage users for an administration purpose.
  3. The GET method returns User objects directly (and not the UserDTO), which provides all the data about the users. Also, a delete method shows up at this location. The MonitoringController code is as follows:
    @RestController
    @RequestMapping(value="/monitoring", 	produces={"application/xml", "application/json"})
    @PreAuthorize("hasRole('ADMIN')")
    public class MonitoringController extends CloudstreetApiWCI{
      @Autowired
      private CommunityService communityService;
      @Autowired
      private SocialUserService socialUserService;
      @RequestMapping(value="/users/{username}", method=GET)
      @ResponseStatus(HttpStatus.OK)
      @ApiOperation(value = "Details one account", notes = )
      public User getUserDetails(@PathVariable String username){
        return communityService.findOne(username);
      }
      @RequestMapping(value="/users/{username}", method=DELETE)
      @ResponseStatus(HttpStatus.OK)
      @ApiOperation(value = "Delete user account", notes =)
      public void deleteUser(@PathVariable String username){
        communityService.delete(username);
      }
      @RequestMapping(value="/users", method=GET)
      @ResponseStatus(HttpStatus.OK)
      @ApiOperation(value = "List user accounts", notes =)
      public Page<User> getUsers(@ApiIgnore @PageableDefault(size=10, page=0) Pageable pageable){
        return communityService.findAll(pageable);
      }
    }
  4. In the communityService implementation, the two used methods (findAll, delete ) have been secured:
      @Override
      @Secured({"ROLE_ADMIN", "ROLE_SYSTEM"})
      public void delete(String userName) {
        userRepository.delete(userName);
      }
      @Override
      @Secured("ROLE_ADMIN")
      public Page<User> findAll(Pageable pageable) {
        return userRepository.findAll(pageable);
      }
  5. As a reminder, we have set a global method-security in security-config.xml:
    <security:global-method-security secured-annotations"="enabled"" pre-post-annotations"="enabled"" authentication-manager-ref"="authenticationManager""/>
  6. Let's try it now. Restart your Tomcat and open a new window in your favorite browser. Open Swagger UI (http://cloudstreetmarket.com/api/index.html) as shown here:
    How to do it...
  7. Open the monitoring tab. Try to call the GET /monitoring/users method in order to list user accounts.
  8. Your web browser should prompt a BASIC authentication form as follows:
    How to do it...
  9. If you cancel this form, you should receive a 401 (Unauthorized) response code.
  10. For test purpose, there is a delete method in communityController that is not secured by any annotation. Also, remember that there is no specific URL interceptor defined for the communityController path:
    @RequestMapping(value"="/{username"}", method=DELETE)
    @ResponseStatus(HttpStatus.OK
    @ApiOperation(value = "Delete a user account", notes =)
    public void deleteUser(@PathVariable String username){
      communityService.delete(username);
    }
  11. Without sign in, try to call this handler from Swagger UI. As shown in the following screenshot, try to delete the user named other10.
    How to do it...
  12. You should receive a 403 (Forbidden) response status because the underlying service-method is secured!
  13. You will see that you haven't been prompted a BASIC login form. Also, take a look at the response headers. You shouldn't see any WWW-Authenticate header, which could have triggered this popup.

    Tip

    An AuthenticationEntryPoint is called if the user is not authenticated and if the requested HTTP resource appears to be secured. Securing the service alone is not sufficient for Spring Security to consider the Controller method-handler as secured.

  14. Try to GET the users again in the monitoring tab. You should see again the BASIC authentication form. Fill it with the following details:
    <User Name> admin
    <Password> admin 

    You should now receive the following response with a 200 status code:

    How to do it...
  15. Swagger UI cannot beautify the body when we request a JSON response but everything is there.

Note

Notice the response code: WWW-Authenticate: CSM_Basic realm"="cloudstreetmarket.com".

How it works...

We will see how the Spring Security authorization process works and how to configure it.

Spring Security authorities

An AuthenticationManager implementation stores GrantedAuthorities into an Authentication object in the SecurityContext. These GrantedAuthorities are read by the AccessDecisionManager in an attempt to match them against accesses' requirements.

The AccessDecisionManager implementations can be native or external and this explains why the infrastructure forces the authorities to be rendered as Strings.

If a getAuthority() method is not able to represent the GrantedAuthority as a String, then it should return null, indicating to the AuthenticationManager that it has to support this type of Authority.

This mechanism constraints the different getAuthority() implementations into limited responsibilities.

Configuration attributes

We have mentioned the Configuration attributes when we were introducing the GrantedAuthority objects (Authenticating over a BASIC scheme recipe).

Configuration attributes play a key role in SecurityInterceptor and indirectly in AccessDecisionManager implementations, since SecurityInterceptor delegates to AccessDecisionManager. Configuration attributes implement the one-method ConfigAttribute interface:

public interface ConfigAttribute extends Serializable {
  String getAttribute();
}

Note

Configuration attributes are specified as annotations on secured methods or as access attributes on secured URLs (intercept-urls).

We have defined the following instruction in our security-config.xml file as a way to tell Spring Security to expect the configuration attributes ROLE_BASIC on web requests matching the /basic.html pattern:

<security:intercept-url pattern="/basic.html" access="hasRole('BASIC')"/>

With the default AccessDecisionManager implementation, any user having a matching GrantedAuthority will be granted the access.

For a voter-based AccessDecisionManager implementation, a configuration attribute beginning with ROLE_ the prefix will be considered as a role and should be examined by a RoleVoter. We will see more about AccessDecisionManager in the next sections.

SecurityInterceptor protecting Secure objects are objects or actions that require a security examination. There are two types of secure objects that are handled by the Framework:

  • Web resources such as ServletRequest or ServletResponse. Those are checked by FilterSecurityInterceptor: a core Filter positioned almost at the end of the filter chain.
  • Method invocations, which are implementations of org.aopalliance.intercept.MethodInvocation. Those are checked by MethodSecurityInterceptor.

A security interceptor (method or HTTP request) intercepts asynchronously (event-based) every single secure object invocations before they actually reach the resource. Spring Security always applies a simple pattern when handling those invocations. This pattern comes from the use of AbstractSecurityInterceptor subclasses.

The AbstractSecurityInterceptor examinations impose a consistent workflow to Secure Objects:

  • Looking up the configuration attributes associated with the .secure object.
  • Submitting the secure object, current authentication object, and configuration attributes to the AccessDecisionManager interface for an authorization decision.
  • Optionally changing the Authentication object under which the invocation takes place.
  • Allowing the secure object invocation to proceed (assuming access was granted).
  • Calling the AfterInvocationManager interface if configured, once the invocation has returned. If the invocation raised an exception, the AfterInvocationManager will not be invoked.

This workflow can be summarized with the following diagram:

Configuration attributes

The original graph for this picture comes from the Spring Security reference. It is interesting because it highlights the different elements that SecurityInterceptor can use when examining a secure object.

Note

The main idea is the delegation to an AccessDecisionManager interface and then optionally to an AfterInvocationManager interface, using the pulled attributes from SecurityMetadaSource and eventually the AuthenticationManager capability to authenticate.

A RunAsManager dependency can optionally be added to SecurityInterceptor on rare occasions where the SecurityContext Authentication object may need to be altered (step 3 of the workflow). The interface is defined as follows:

public interface RunAsManager {
  Authentication buildRunAs(Authentication authentication, Object object, Collection<ConfigAttribute> attributes);
  boolean supports(ConfigAttribute attribute);
  boolean supports(Class<?> clazz);
}

If no dependency is set for RunAsManager, the SecurityInterceptor will run a NullRunAsManager implementation. An AfterInvocationManager interface may optionally be configured and used to alter the statusToken object returned by the invocation (step 5 of the workflow).

Pre-invocation handling

An AccessDecisionManager decides whether an access must be allowed or not.

AccessDecisionManager

The AccessDecisionManager interface is called by the SecurityInterceptor (in step 2 of its workflow) and is responsible for making the final access control decision.

The interface is made of the following three methods:

public interface AccessDecisionManager {
  void decide(Authentication authentication, Object object, 				Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException;
  boolean supports(ConfigAttribute attribute);
  boolean supports(Class<?> clazz);
}

As you can see, the method names are pretty explicit:

  • The decide method resolves an access control decision for the provided arguments. The Authentication object represents the caller invoking the method, the object is the Secured Object to be examined, the configAttributes are the configuration attributes associated with the secured object. Also, it throws an AccessDeniedException when access is denied.
  • The supports(ConfigAttribute attribute) method is called at an early stage of the examination to determine whether the AccessDecisionManager can process a specific ConfigAttribute.
  • The supports(Class<?> clazz) method is called prior the invocation to ensure the configured AccessDecisionManager supports the type of Secure Object that will be presented.

Tip

When using a namespace configuration, Spring Security automatically registers a default instance of AccessDecisionManager for assessing method invocations and web accesses, based on the access attributes which are specified in the intercept-url and protect-pointcut declarations (and in annotations if using annotations to secure methods).

A specific or custom AccessDecisionManager can be specified in the following cases:

  • On the http Namespace when handling web resources:
      <security:http ... access-decision-manager-ref"="xxx"">
      </security:http>
  • On the global-method-security Namespace when handling method invocations:
      <security:global-method-security access-decision-manager-ref""=""... />

Spring Security includes three AccessDecisionManager implementations (AffirmativeBased, ConsensusBased, and UnanimousBased) that are based on voting. Voters are eligible AccessDecisionVoter implementations. The interface is defined as follows:

public interface AccessDecisionVoter<S> {
  boolean supports(ConfigAttribute attribute);
  boolean supports(Class<?> clazz);
  int vote(Authentication authentication, S object,
  Collection<ConfigAttribute> attributes);
}

A few AccessDecisionVoter implementations come along with the Framework (AuthenticatedVoter, Jsr250Voter, PreInvocationAuthorizationAdviceVoter, WebExpressionVoter, RoleVoter, and so on). During the examination, eligible AccessDecisionVoters are polled on the authorization decision. Voters' eligibility depends on the voters' registration in the AccessDecisionManager.decisionVoters property. It also depends on the voters' supports methods.

The AccessDecisionManager decides whether or not it should be thrown an AccessDeniedException based on its assessment of the votes. Each AccessDecisionVoter assesses the Secure Object against different criteria.

 

"The most commonly used AccessDecisionVoter provided with Spring Security is the simple RoleVoter, which treats configuration attributes as simple role names and votes to grant access if the user has been assigned that role"."

 
 --Spring Security reference

After invocation handling

There is only one AfterInvocationManager implementation in Spring Security: AfterInvocationProviderManager. This class aligns all the eligible AfterInvocationProvider implementations to give them the opportunity to alter the SecurityInterceptor result.

Similar to the AccessDecisionManager interface, the AfterInvocationProvider interface looks like this:

public interface AfterInvocationProvider {
  Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes, Object returnedObject) throws AccessDeniedException;
  boolean supports(ConfigAttribute attribute);
  boolean supports(Class<?> clazz);
}

Expression-based access control

Since Spring Security 3, it is now possible to use Spring Expression Language (EL) in order to define the Web security and methods security.

 

"Expressions are evaluated with a root object as part of the evaluation context. Spring Security uses specific classes for web and method security as the root object, in order to provide built-in expressions and access to values such as the current principal."

 
 --Spring Security reference

The base class for expression root objects is SecurityExpressionRoot. This abstract class gives access to the following methods and properties which represent the common built-in expressions:

Expression

Description

hasRole([role])

Returns true if the current principal has the specified role. By default if the supplied role does not start with ROLE_ it will be added. This can be customized by modifying the defaultRolePrefix onDefaultWebSecurityExpressionHandler.

hasAnyRole([role1,role2])

Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings). By default, if the supplied role does not start with ROLE_ it will be added. This can be customized by modifying the defaultRolePrefix on DefaultWebSecurityExpressionHandler.

hasAuthority([authority])

Returns true if the current principal has the specified authority.

hasAnyAuthority([authority1,authority2])

Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings).

principal

Allows direct access to the principal object representing the current user.

authentication

Allows direct access to the current Authentication object obtained from the SecurityContext.

permitAll

Always evaluates to true.

denyAll

Always evaluates to false.

isAnonymous()

Returns true if the current principal is an anonymous user.

isRememberMe()

Returns true if the current principal is a remember-me user.

isAuthenticated()

Returns true if the user is not anonymous.

isFullyAuthenticated()

Returns true if the user is not an anonymous or a remember-me user.

hasPermission(Object target, Object permission)

Returns true if the user has access to the provided target for the given permission. For example, hasPermission(domainObject, 'read').

hasPermission(Object targetId, String targetType, Object permission)

Returns true if the user has access to the provided target for the given permission. For example, hasPermission(1,'com.example.domain.Message', 'read').

Web Security expressions

Using the Spring Security namespace, the <http> block has a use-expression attribute that defaults to true. This property makes the access attributes in the intercept-url elements expecting expressions as values.

For Web security, the base class for expression root objects is WebSecurityExpressionRoot, which inherits the methods of SecurityExpressionRoot and provides one extra method: hasIpAddress(…).

Also, WebSecurityExpressionRoot exposes in the evaluation context the HttpServletRequest object accessible under the name request.

If expressions are being used, a WebExpressionVoter will be added to the AccessDecisionManager.

Method security expressions

Expressions for methods security have been introduced with Spring Security 3.0. Four security annotations support the use of expressions: @PreAuthorize, @PostAuthorize, @PreFilter, and @PostFilter.

Access control using @PreAuthorize and @PostAuthorize

The use of these annotations has to be activated in the global security bean:

<security:global-method-security pre-post-annotations"="enabled"">

@PreAuthorize is commonly used to allow or disallow methods' invocations.

We have implemented this annotation on the MonitoringController class:

@PreAuthorize("hasRole('ADMIN')")
public class MonitoringController extends CloudstreetApiWCI{
  ...
}

The specified Expression hasRole('ADMIN') meant that the accesses to the controller will only be allowed to users within the role ROLE_ADMIN.

Tip

You can note the automatic prefixing of ROLE_ that avoids a word repetition. This nice feature can be used in Expressions, both in web security (intercept-url: access attribute) and methods security.

Let's also consider this example from the Spring security reference documents:

@PreAuthorize("hasPermission(#contact, 'admin')")
public void deletePermission(Contact contact, Sid recipient, Permission permission);

Here, a method argument is passed into the expression to decide whether the current user has the admin permission for the given contact.

The @PostAuthorize is less commonly used but can perform an access-control check after the method has been invoked. To access the AccessDecisionManager return value in the Expression, use the built-in name returnObject.

Filtering collections using @PreFilter and @PostFilter

It is now possible to rely on Spring Security to filter collections (using expressions) that may be returned from a method invocation.

Consider this example from the reference document:

@PreAuthorize("hasRole('USER')")
@PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')")
public List<Contact> getAll();

Spring Security iterates through the returned collection and removes any elements for which the supplied expression is false. The name filter object refers to the current object in the collection. You can also filter before the method call, using @PreFilter, though this is a less common requirement.

Actually, in order to use hasPermission() in expressions, it is necessary to explicitly configure a PermissionEvaluator in the application context. The following code is a example:

<security:global-method-security...>
  <security:expression-handler ref="expressionHandler"/>
</security:global-method-security>
<bean id="expressionHandler" class="org.sfw.security.access.expression.method.DefaultMethod SecurityExpressionHandler">
  <property name="permissionEvaluator" ref="myPermissionEvaluator"/>
</bean>

With myPermissionEvaluator being a PermissionEvaluator implementation:

public interface PermissionEvaluator extends AopInfrastructureBean {
  boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);
  boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission);
}

JSR-250 and legacy method security

JSR-250 is a Java specification request that has been released in 2006. It specifies a set of annotations to address common semantic patterns. Among these annotations, some relate to security:

Annotation name

Description

RolesAllowed

Specifies the security roles permitted to access method(s) in an application

PermitAll

Specifies that all security roles are permitted to access the annotated method, or all methods in the annotated class

DenyAll

Specifies that no security roles are allowed to invoke the specified method(s)

DeclareRoles

Used to specify the security roles by the application

Spring Security supports these annotations but this support has to be activated:

<security:global-method-security jsr250-annotations"="enabled""…/>

Spring Security also supports its legacy @Secured annotations, if enabled:

<security:global-method-security secured-annotations"="enabled""…/>

There is more…

Domain Object Security (ACLs)

Some more complex applications may require authorization decisions to be taken, depending upon the actual domain object that is subject to method invocation:

http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#domain-acls

Spring EL

You might need to find extra information about the Spring EL:

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html

The Spring Security reference

The Spring and Security reference and the Spring JavaDoc have been the main source of information for this recipe. We hope you have enjoyed our information selection, analysis, and point of view.

http://docs.spring.io/spring-security/site/docs/current/apidocs/

http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset