In this recipe, we restrict the access to services and controllers depending upon the authorities that are granted to users.
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.
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()); } }
MonitoringController
(a RestController
) that offers the possibility to manage users for an administration purpose.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); } }
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); }
security-config.xml
:<security:global-method-security secured-annotations"="enabled"" pre-post-annotations"="enabled"" authentication-manager-ref"="authenticationManager""/>
http://cloudstreetmarket.com/api/index.html
) as shown here: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); }
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:
We will see how the Spring Security authorization process works and how to configure it.
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.
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(); }
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:
ServletRequest
or ServletResponse.
Those are checked by FilterSecurityInterceptor: a core Filter positioned almost at the end of the filter chain.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:
AccessDecisionManager
interface for an authorization decision.Authentication
object under which the invocation takes place.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:
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.
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).
An
AccessDecisionManager
decides whether an access must be allowed or not.
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:
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.supports(ConfigAttribute attribute)
method is called at an early stage of the examination to determine whether the AccessDecisionManager
can process a specific ConfigAttribute
.supports(Class<?> clazz)
method is called prior the invocation to ensure the configured AccessDecisionManager
supports the type of Secure Object that will be presented.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:
<security:http ... access-decision-manager-ref"="xxx""> </security:http>
<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 |
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); }
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:
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
.
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
.
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
.
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
.
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 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 |
---|---|
|
Specifies the security roles permitted to access method(s) in an application |
|
Specifies that all security roles are permitted to access the annotated method, or all methods in the annotated class |
|
Specifies that no security roles are allowed to invoke the specified method(s) |
|
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""…/>
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
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 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