parent
35345fac70
commit
780e6aefd2
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,193 @@
|
|||
[[domain-acls]]
|
||||
== Domain Object Security (ACLs)
|
||||
|
||||
[[domain-acls-overview]]
|
||||
=== Overview
|
||||
Complex applications often will find the need to define access permissions not simply at a web request or method invocation level.
|
||||
Instead, security decisions need to comprise both who (`Authentication`), where (`MethodInvocation`) and what (`SomeDomainObject`).
|
||||
In other words, authorization decisions also need to consider the actual domain object instance subject of a method invocation.
|
||||
|
||||
Imagine you're designing an application for a pet clinic.
|
||||
There will be two main groups of users of your Spring-based application: staff of the pet clinic, as well as the pet clinic's customers.
|
||||
The staff will have access to all of the data, whilst your customers will only be able to see their own customer records.
|
||||
To make it a little more interesting, your customers can allow other users to see their customer records, such as their "puppy preschool" mentor or president of their local "Pony Club".
|
||||
Using Spring Security as the foundation, you have several approaches that can be used:
|
||||
|
||||
* Write your business methods to enforce the security.
|
||||
You could consult a collection within the `Customer` domain object instance to determine which users have access.
|
||||
By using the `SecurityContextHolder.getContext().getAuthentication()`, you'll be able to access the `Authentication` object.
|
||||
* Write an `AccessDecisionVoter` to enforce the security from the `GrantedAuthority[]` s stored in the `Authentication` object.
|
||||
This would mean your `AuthenticationManager` would need to populate the `Authentication` with custom ``GrantedAuthority[]``s representing each of the `Customer` domain object instances the principal has access to.
|
||||
* Write an `AccessDecisionVoter` to enforce the security and open the target `Customer` domain object directly.
|
||||
This would mean your voter needs access to a DAO that allows it to retrieve the `Customer` object.
|
||||
It would then access the `Customer` object's collection of approved users and make the appropriate decision.
|
||||
|
||||
|
||||
Each one of these approaches is perfectly legitimate.
|
||||
However, the first couples your authorization checking to your business code.
|
||||
The main problems with this include the enhanced difficulty of unit testing and the fact it would be more difficult to reuse the `Customer` authorization logic elsewhere.
|
||||
Obtaining the `GrantedAuthority[]` s from the `Authentication` object is also fine, but will not scale to large numbers of `Customer` s.
|
||||
If a user might be able to access 5,000 `Customer` s (unlikely in this case, but imagine if it were a popular vet for a large Pony Club!) the amount of memory consumed and time required to construct the `Authentication` object would be undesirable.
|
||||
The final method, opening the `Customer` directly from external code, is probably the best of the three.
|
||||
It achieves separation of concerns, and doesn't misuse memory or CPU cycles, but it is still inefficient in that both the `AccessDecisionVoter` and the eventual business method itself will perform a call to the DAO responsible for retrieving the `Customer` object.
|
||||
Two accesses per method invocation is clearly undesirable.
|
||||
In addition, with every approach listed you'll need to write your own access control list (ACL) persistence and business logic from scratch.
|
||||
|
||||
Fortunately, there is another alternative, which we'll talk about below.
|
||||
|
||||
|
||||
[[domain-acls-key-concepts]]
|
||||
=== Key Concepts
|
||||
Spring Security's ACL services are shipped in the `spring-security-acl-xxx.jar`.
|
||||
You will need to add this JAR to your classpath to use Spring Security's domain object instance security capabilities.
|
||||
|
||||
Spring Security's domain object instance security capabilities centre on the concept of an access control list (ACL).
|
||||
Every domain object instance in your system has its own ACL, and the ACL records details of who can and can't work with that domain object.
|
||||
With this in mind, Spring Security delivers three main ACL-related capabilities to your application:
|
||||
|
||||
* A way of efficiently retrieving ACL entries for all of your domain objects (and modifying those ACLs)
|
||||
* A way of ensuring a given principal is permitted to work with your objects, before methods are called
|
||||
* A way of ensuring a given principal is permitted to work with your objects (or something they return), after methods are called
|
||||
|
||||
As indicated by the first bullet point, one of the main capabilities of the Spring Security ACL module is providing a high-performance way of retrieving ACLs.
|
||||
This ACL repository capability is extremely important, because every domain object instance in your system might have several access control entries, and each ACL might inherit from other ACLs in a tree-like structure (this is supported out-of-the-box by Spring Security, and is very commonly used).
|
||||
Spring Security's ACL capability has been carefully designed to provide high performance retrieval of ACLs, together with pluggable caching, deadlock-minimizing database updates, independence from ORM frameworks (we use JDBC directly), proper encapsulation, and transparent database updating.
|
||||
|
||||
Given databases are central to the operation of the ACL module, let's explore the four main tables used by default in the implementation.
|
||||
The tables are presented below in order of size in a typical Spring Security ACL deployment, with the table with the most rows listed last:
|
||||
|
||||
|
||||
|
||||
* ACL_SID allows us to uniquely identify any principal or authority in the system ("SID" stands for "security identity").
|
||||
The only columns are the ID, a textual representation of the SID, and a flag to indicate whether the textual representation refers to a principal name or a `GrantedAuthority`.
|
||||
Thus, there is a single row for each unique principal or `GrantedAuthority`.
|
||||
When used in the context of receiving a permission, a SID is generally called a "recipient".
|
||||
|
||||
* ACL_CLASS allows us to uniquely identify any domain object class in the system.
|
||||
The only columns are the ID and the Java class name.
|
||||
Thus, there is a single row for each unique Class we wish to store ACL permissions for.
|
||||
|
||||
* ACL_OBJECT_IDENTITY stores information for each unique domain object instance in the system.
|
||||
Columns include the ID, a foreign key to the ACL_CLASS table, a unique identifier so we know which ACL_CLASS instance we're providing information for, the parent, a foreign key to the ACL_SID table to represent the owner of the domain object instance, and whether we allow ACL entries to inherit from any parent ACL.
|
||||
We have a single row for every domain object instance we're storing ACL permissions for.
|
||||
|
||||
* Finally, ACL_ENTRY stores the individual permissions assigned to each recipient.
|
||||
Columns include a foreign key to the ACL_OBJECT_IDENTITY, the recipient (ie a foreign key to ACL_SID), whether we'll be auditing or not, and the integer bit mask that represents the actual permission being granted or denied.
|
||||
We have a single row for every recipient that receives a permission to work with a domain object.
|
||||
|
||||
|
||||
|
||||
|
||||
As mentioned in the last paragraph, the ACL system uses integer bit masking.
|
||||
Don't worry, you need not be aware of the finer points of bit shifting to use the ACL system, but suffice to say that we have 32 bits we can switch on or off.
|
||||
Each of these bits represents a permission, and by default the permissions are read (bit 0), write (bit 1), create (bit 2), delete (bit 3) and administer (bit 4).
|
||||
It's easy to implement your own `Permission` instance if you wish to use other permissions, and the remainder of the ACL framework will operate without knowledge of your extensions.
|
||||
|
||||
It is important to understand that the number of domain objects in your system has absolutely no bearing on the fact we've chosen to use integer bit masking.
|
||||
Whilst you have 32 bits available for permissions, you could have billions of domain object instances (which will mean billions of rows in ACL_OBJECT_IDENTITY and quite probably ACL_ENTRY).
|
||||
We make this point because we've found sometimes people mistakenly believe they need a bit for each potential domain object, which is not the case.
|
||||
|
||||
Now that we've provided a basic overview of what the ACL system does, and what it looks like at a table structure, let's explore the key interfaces.
|
||||
The key interfaces are:
|
||||
|
||||
|
||||
* `Acl`: Every domain object has one and only one `Acl` object, which internally holds the `AccessControlEntry` s as well as knows the owner of the `Acl`.
|
||||
An Acl does not refer directly to the domain object, but instead to an `ObjectIdentity`.
|
||||
The `Acl` is stored in the ACL_OBJECT_IDENTITY table.
|
||||
|
||||
* `AccessControlEntry`: An `Acl` holds multiple `AccessControlEntry` s, which are often abbreviated as ACEs in the framework.
|
||||
Each ACE refers to a specific tuple of `Permission`, `Sid` and `Acl`.
|
||||
An ACE can also be granting or non-granting and contain audit settings.
|
||||
The ACE is stored in the ACL_ENTRY table.
|
||||
|
||||
* `Permission`: A permission represents a particular immutable bit mask, and offers convenience functions for bit masking and outputting information.
|
||||
The basic permissions presented above (bits 0 through 4) are contained in the `BasePermission` class.
|
||||
|
||||
* `Sid`: The ACL module needs to refer to principals and `GrantedAuthority[]` s.
|
||||
A level of indirection is provided by the `Sid` interface, which is an abbreviation of "security identity".
|
||||
Common classes include `PrincipalSid` (to represent the principal inside an `Authentication` object) and `GrantedAuthoritySid`.
|
||||
The security identity information is stored in the ACL_SID table.
|
||||
|
||||
* `ObjectIdentity`: Each domain object is represented internally within the ACL module by an `ObjectIdentity`.
|
||||
The default implementation is called `ObjectIdentityImpl`.
|
||||
|
||||
* `AclService`: Retrieves the `Acl` applicable for a given `ObjectIdentity`.
|
||||
In the included implementation (`JdbcAclService`), retrieval operations are delegated to a `LookupStrategy`.
|
||||
The `LookupStrategy` provides a highly optimized strategy for retrieving ACL information, using batched retrievals `(BasicLookupStrategy`) and supporting custom implementations that leverage materialized views, hierarchical queries and similar performance-centric, non-ANSI SQL capabilities.
|
||||
|
||||
* `MutableAclService`: Allows a modified `Acl` to be presented for persistence.
|
||||
It is not essential to use this interface if you do not wish.
|
||||
|
||||
|
||||
|
||||
Please note that our out-of-the-box AclService and related database classes all use ANSI SQL.
|
||||
This should therefore work with all major databases.
|
||||
At the time of writing, the system had been successfully tested using Hypersonic SQL, PostgreSQL, Microsoft SQL Server and Oracle.
|
||||
|
||||
Two samples ship with Spring Security that demonstrate the ACL module.
|
||||
The first is the Contacts Sample, and the other is the Document Management System (DMS) Sample.
|
||||
We suggest taking a look over these for examples.
|
||||
|
||||
|
||||
[[domain-acls-getting-started]]
|
||||
=== Getting Started
|
||||
To get starting using Spring Security's ACL capability, you will need to store your ACL information somewhere.
|
||||
This necessitates the instantiation of a `DataSource` using Spring.
|
||||
The `DataSource` is then injected into a `JdbcMutableAclService` and `BasicLookupStrategy` instance.
|
||||
The latter provides high-performance ACL retrieval capabilities, and the former provides mutator capabilities.
|
||||
Refer to one of the samples that ship with Spring Security for an example configuration.
|
||||
You'll also need to populate the database with the four ACL-specific tables listed in the last section (refer to the ACL samples for the appropriate SQL statements).
|
||||
|
||||
Once you've created the required schema and instantiated `JdbcMutableAclService`, you'll next need to ensure your domain model supports interoperability with the Spring Security ACL package.
|
||||
Hopefully `ObjectIdentityImpl` will prove sufficient, as it provides a large number of ways in which it can be used.
|
||||
Most people will have domain objects that contain a `public Serializable getId()` method.
|
||||
If the return type is long, or compatible with long (eg an int), you will find you need not give further consideration to `ObjectIdentity` issues.
|
||||
Many parts of the ACL module rely on long identifiers.
|
||||
If you're not using long (or an int, byte etc), there is a very good chance you'll need to reimplement a number of classes.
|
||||
We do not intend to support non-long identifiers in Spring Security's ACL module, as longs are already compatible with all database sequences, the most common identifier data type, and are of sufficient length to accommodate all common usage scenarios.
|
||||
|
||||
The following fragment of code shows how to create an `Acl`, or modify an existing `Acl`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
// Prepare the information we'd like in our access control entry (ACE)
|
||||
ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
|
||||
Sid sid = new PrincipalSid("Samantha");
|
||||
Permission p = BasePermission.ADMINISTRATION;
|
||||
|
||||
// Create or update the relevant ACL
|
||||
MutableAcl acl = null;
|
||||
try {
|
||||
acl = (MutableAcl) aclService.readAclById(oi);
|
||||
} catch (NotFoundException nfe) {
|
||||
acl = aclService.createAcl(oi);
|
||||
}
|
||||
|
||||
// Now grant some permissions via an access control entry (ACE)
|
||||
acl.insertAce(acl.getEntries().length, p, sid, true);
|
||||
aclService.updateAcl(acl);
|
||||
----
|
||||
|
||||
|
||||
|
||||
In the example above, we're retrieving the ACL associated with the "Foo" domain object with identifier number 44.
|
||||
We're then adding an ACE so that a principal named "Samantha" can "administer" the object.
|
||||
The code fragment is relatively self-explanatory, except the insertAce method.
|
||||
The first argument to the insertAce method is determining at what position in the Acl the new entry will be inserted.
|
||||
In the example above, we're just putting the new ACE at the end of the existing ACEs.
|
||||
The final argument is a Boolean indicating whether the ACE is granting or denying.
|
||||
Most of the time it will be granting (true), but if it is denying (false), the permissions are effectively being blocked.
|
||||
|
||||
Spring Security does not provide any special integration to automatically create, update or delete ACLs as part of your DAO or repository operations.
|
||||
Instead, you will need to write code like shown above for your individual domain objects.
|
||||
It's worth considering using AOP on your services layer to automatically integrate the ACL information with your services layer operations.
|
||||
We've found this quite an effective approach in the past.
|
||||
|
||||
Once you've used the above techniques to store some ACL information in the database, the next step is to actually use the ACL information as part of authorization decision logic.
|
||||
You have a number of choices here.
|
||||
You could write your own `AccessDecisionVoter` or `AfterInvocationProvider` that respectively fires before or after a method invocation.
|
||||
Such classes would use `AclService` to retrieve the relevant ACL and then call `Acl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode)` to decide whether permission is granted or denied.
|
||||
Alternately, you could use our `AclEntryVoter`, `AclEntryAfterInvocationProvider` or `AclEntryAfterInvocationCollectionFilteringProvider` classes.
|
||||
All of these classes provide a declarative-based approach to evaluating ACL information at runtime, freeing you from needing to write any code.
|
||||
Please refer to the sample applications to learn how to use these classes.
|
||||
|
|
@ -0,0 +1,445 @@
|
|||
|
||||
|
||||
[[cas]]
|
||||
== CAS Authentication
|
||||
|
||||
[[cas-overview]]
|
||||
=== Overview
|
||||
JA-SIG produces an enterprise-wide single sign on system known as CAS.
|
||||
Unlike other initiatives, JA-SIG's Central Authentication Service is open source, widely used, simple to understand, platform independent, and supports proxy capabilities.
|
||||
Spring Security fully supports CAS, and provides an easy migration path from single-application deployments of Spring Security through to multiple-application deployments secured by an enterprise-wide CAS server.
|
||||
|
||||
You can learn more about CAS at http://www.ja-sig.org/cas.
|
||||
You will also need to visit this site to download the CAS Server files.
|
||||
|
||||
[[cas-how-it-works]]
|
||||
=== How CAS Works
|
||||
Whilst the CAS web site contains documents that detail the architecture of CAS, we present the general overview again here within the context of Spring Security.
|
||||
Spring Security 3.x supports CAS 3.
|
||||
At the time of writing, the CAS server was at version 3.4.
|
||||
|
||||
Somewhere in your enterprise you will need to setup a CAS server.
|
||||
The CAS server is simply a standard WAR file, so there isn't anything difficult about setting up your server.
|
||||
Inside the WAR file you will customise the login and other single sign on pages displayed to users.
|
||||
|
||||
When deploying a CAS 3.4 server, you will also need to specify an `AuthenticationHandler` in the `deployerConfigContext.xml` included with CAS.
|
||||
The `AuthenticationHandler` has a simple method that returns a boolean as to whether a given set of Credentials is valid.
|
||||
Your `AuthenticationHandler` implementation will need to link into some type of backend authentication repository, such as an LDAP server or database.
|
||||
CAS itself includes numerous `AuthenticationHandler` s out of the box to assist with this.
|
||||
When you download and deploy the server war file, it is set up to successfully authenticate users who enter a password matching their username, which is useful for testing.
|
||||
|
||||
Apart from the CAS server itself, the other key players are of course the secure web applications deployed throughout your enterprise.
|
||||
These web applications are known as "services".
|
||||
There are three types of services.
|
||||
Those that authenticate service tickets, those that can obtain proxy tickets, and those that authenticate proxy tickets.
|
||||
Authenticating a proxy ticket differs because the list of proxies must be validated and often times a proxy ticket can be reused.
|
||||
|
||||
|
||||
[[cas-sequence]]
|
||||
==== Spring Security and CAS Interaction Sequence
|
||||
The basic interaction between a web browser, CAS server and a Spring Security-secured service is as follows:
|
||||
|
||||
* The web user is browsing the service's public pages.
|
||||
CAS or Spring Security is not involved.
|
||||
* The user eventually requests a page that is either secure or one of the beans it uses is secure.
|
||||
Spring Security's `ExceptionTranslationFilter` will detect the `AccessDeniedException` or `AuthenticationException`.
|
||||
* Because the user's `Authentication` object (or lack thereof) caused an `AuthenticationException`, the `ExceptionTranslationFilter` will call the configured `AuthenticationEntryPoint`.
|
||||
If using CAS, this will be the `CasAuthenticationEntryPoint` class.
|
||||
* The `CasAuthenticationEntryPoint` will redirect the user's browser to the CAS server.
|
||||
It will also indicate a `service` parameter, which is the callback URL for the Spring Security service (your application).
|
||||
For example, the URL to which the browser is redirected might be https://my.company.com/cas/login?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas.
|
||||
* After the user's browser redirects to CAS, they will be prompted for their username and password.
|
||||
If the user presents a session cookie which indicates they've previously logged on, they will not be prompted to login again (there is an exception to this procedure, which we'll cover later).
|
||||
CAS will use the `PasswordHandler` (or `AuthenticationHandler` if using CAS 3.0) discussed above to decide whether the username and password is valid.
|
||||
* Upon successful login, CAS will redirect the user's browser back to the original service.
|
||||
It will also include a `ticket` parameter, which is an opaque string representing the "service ticket".
|
||||
Continuing our earlier example, the URL the browser is redirected to might be https://server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZ.
|
||||
* Back in the service web application, the `CasAuthenticationFilter` is always listening for requests to `/login/cas` (this is configurable, but we'll use the defaults in this introduction).
|
||||
The processing filter will construct a `UsernamePasswordAuthenticationToken` representing the service ticket.
|
||||
The principal will be equal to `CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER`, whilst the credentials will be the service ticket opaque value.
|
||||
This authentication request will then be handed to the configured `AuthenticationManager`.
|
||||
* The `AuthenticationManager` implementation will be the `ProviderManager`, which is in turn configured with the `CasAuthenticationProvider`.
|
||||
The `CasAuthenticationProvider` only responds to `UsernamePasswordAuthenticationToken` s containing the CAS-specific principal (such as `CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER`) and `CasAuthenticationToken` s (discussed later).
|
||||
* `CasAuthenticationProvider` will validate the service ticket using a `TicketValidator` implementation.
|
||||
This will typically be a `Cas20ServiceTicketValidator` which is one of the classes included in the CAS client library.
|
||||
In the event the application needs to validate proxy tickets, the `Cas20ProxyTicketValidator` is used.
|
||||
The `TicketValidator` makes an HTTPS request to the CAS server in order to validate the service ticket.
|
||||
It may also include a proxy callback URL, which is included in this example: https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas&ticket=ST-0-ER94xMJmn6pha35CQRoZ&pgtUrl=https://server3.company.com/webapp/login/cas/proxyreceptor.
|
||||
* Back on the CAS server, the validation request will be received.
|
||||
If the presented service ticket matches the service URL the ticket was issued to, CAS will provide an affirmative response in XML indicating the username.
|
||||
If any proxy was involved in the authentication (discussed below), the list of proxies is also included in the XML response.
|
||||
* [OPTIONAL] If the request to the CAS validation service included the proxy callback URL (in the `pgtUrl` parameter), CAS will include a `pgtIou` string in the XML response.
|
||||
This `pgtIou` represents a proxy-granting ticket IOU.
|
||||
The CAS server will then create its own HTTPS connection back to the `pgtUrl`.
|
||||
This is to mutually authenticate the CAS server and the claimed service URL.
|
||||
The HTTPS connection will be used to send a proxy granting ticket to the original web application.
|
||||
For example, https://server3.company.com/webapp/login/cas/proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH.
|
||||
* The `Cas20TicketValidator` will parse the XML received from the CAS server.
|
||||
It will return to the `CasAuthenticationProvider` a `TicketResponse`, which includes the username (mandatory), proxy list (if any were involved), and proxy-granting ticket IOU (if the proxy callback was requested).
|
||||
* Next `CasAuthenticationProvider` will call a configured `CasProxyDecider`.
|
||||
The `CasProxyDecider` indicates whether the proxy list in the `TicketResponse` is acceptable to the service.
|
||||
Several implementations are provided with Spring Security: `RejectProxyTickets`, `AcceptAnyCasProxy` and `NamedCasProxyDecider`.
|
||||
These names are largely self-explanatory, except `NamedCasProxyDecider` which allows a `List` of trusted proxies to be provided.
|
||||
* `CasAuthenticationProvider` will next request a `AuthenticationUserDetailsService` to load the `GrantedAuthority` objects that apply to the user contained in the `Assertion`.
|
||||
* If there were no problems, `CasAuthenticationProvider` constructs a `CasAuthenticationToken` including the details contained in the `TicketResponse` and the ``GrantedAuthority``s.
|
||||
* Control then returns to `CasAuthenticationFilter`, which places the created `CasAuthenticationToken` in the security context.
|
||||
* The user's browser is redirected to the original page that caused the `AuthenticationException` (or a <<form-login-flow-handling,custom destination>> depending on the configuration).
|
||||
|
||||
It's good that you're still here!
|
||||
Let's now look at how this is configured
|
||||
|
||||
[[cas-client]]
|
||||
=== Configuration of CAS Client
|
||||
The web application side of CAS is made easy due to Spring Security.
|
||||
It is assumed you already know the basics of using Spring Security, so these are not covered again below.
|
||||
We'll assume a namespace based configuration is being used and add in the CAS beans as required.
|
||||
Each section builds upon the previous section.
|
||||
A full<<cas-sample,CAS sample application>> can be found in the Spring Security Samples.
|
||||
|
||||
|
||||
[[cas-st]]
|
||||
==== Service Ticket Authentication
|
||||
This section describes how to setup Spring Security to authenticate Service Tickets.
|
||||
Often times this is all a web application requires.
|
||||
You will need to add a `ServiceProperties` bean to your application context.
|
||||
This represents your CAS service:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="serviceProperties"
|
||||
class="org.springframework.security.cas.ServiceProperties">
|
||||
<property name="service"
|
||||
value="https://localhost:8443/cas-sample/login/cas"/>
|
||||
<property name="sendRenew" value="false"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The `service` must equal a URL that will be monitored by the `CasAuthenticationFilter`.
|
||||
The `sendRenew` defaults to false, but should be set to true if your application is particularly sensitive.
|
||||
What this parameter does is tell the CAS login service that a single sign on login is unacceptable.
|
||||
Instead, the user will need to re-enter their username and password in order to gain access to the service.
|
||||
|
||||
The following beans should be configured to commence the CAS authentication process (assuming you're using a namespace configuration):
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<security:http entry-point-ref="casEntryPoint">
|
||||
...
|
||||
<security:custom-filter position="CAS_FILTER" ref="casFilter" />
|
||||
</security:http>
|
||||
|
||||
<bean id="casFilter"
|
||||
class="org.springframework.security.cas.web.CasAuthenticationFilter">
|
||||
<property name="authenticationManager" ref="authenticationManager"/>
|
||||
</bean>
|
||||
|
||||
<bean id="casEntryPoint"
|
||||
class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
|
||||
<property name="loginUrl" value="https://localhost:9443/cas/login"/>
|
||||
<property name="serviceProperties" ref="serviceProperties"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
For CAS to operate, the `ExceptionTranslationFilter` must have its `authenticationEntryPoint` property set to the `CasAuthenticationEntryPoint` bean.
|
||||
This can easily be done using <<ns-entry-point-ref,entry-point-ref>> as is done in the example above.
|
||||
The `CasAuthenticationEntryPoint` must refer to the `ServiceProperties` bean (discussed above), which provides the URL to the enterprise's CAS login server.
|
||||
This is where the user's browser will be redirected.
|
||||
|
||||
The `CasAuthenticationFilter` has very similar properties to the `UsernamePasswordAuthenticationFilter` (used for form-based logins).
|
||||
You can use these properties to customize things like behavior for authentication success and failure.
|
||||
|
||||
Next you need to add a `CasAuthenticationProvider` and its collaborators:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<security:authentication-manager alias="authenticationManager">
|
||||
<security:authentication-provider ref="casAuthenticationProvider" />
|
||||
</security:authentication-manager>
|
||||
|
||||
<bean id="casAuthenticationProvider"
|
||||
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
|
||||
<property name="authenticationUserDetailsService">
|
||||
<bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
|
||||
<constructor-arg ref="userService" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="serviceProperties" ref="serviceProperties" />
|
||||
<property name="ticketValidator">
|
||||
<bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
|
||||
<constructor-arg index="0" value="https://localhost:9443/cas" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="key" value="an_id_for_this_auth_provider_only"/>
|
||||
</bean>
|
||||
|
||||
<security:user-service id="userService">
|
||||
<!-- Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that
|
||||
NoOpPasswordEncoder should be used.
|
||||
This is not safe for production, but makes reading
|
||||
in samples easier.
|
||||
Normally passwords should be hashed using BCrypt -->
|
||||
<security:user name="joe" password="{noop}joe" authorities="ROLE_USER" />
|
||||
...
|
||||
</security:user-service>
|
||||
----
|
||||
|
||||
The `CasAuthenticationProvider` uses a `UserDetailsService` instance to load the authorities for a user, once they have been authenticated by CAS.
|
||||
We've shown a simple in-memory setup here.
|
||||
Note that the `CasAuthenticationProvider` does not actually use the password for authentication, but it does use the authorities.
|
||||
|
||||
The beans are all reasonably self-explanatory if you refer back to the <<cas-how-it-works,How CAS Works>> section.
|
||||
|
||||
This completes the most basic configuration for CAS.
|
||||
If you haven't made any mistakes, your web application should happily work within the framework of CAS single sign on.
|
||||
No other parts of Spring Security need to be concerned about the fact CAS handled authentication.
|
||||
In the following sections we will discuss some (optional) more advanced configurations.
|
||||
|
||||
|
||||
[[cas-singlelogout]]
|
||||
==== Single Logout
|
||||
The CAS protocol supports Single Logout and can be easily added to your Spring Security configuration.
|
||||
Below are updates to the Spring Security configuration that handle Single Logout
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<security:http entry-point-ref="casEntryPoint">
|
||||
...
|
||||
<security:logout logout-success-url="/cas-logout.jsp"/>
|
||||
<security:custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>
|
||||
<security:custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>
|
||||
</security:http>
|
||||
|
||||
<!-- This filter handles a Single Logout Request from the CAS Server -->
|
||||
<bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/>
|
||||
|
||||
<!-- This filter redirects to the CAS Server to signal Single Logout should be performed -->
|
||||
<bean id="requestSingleLogoutFilter"
|
||||
class="org.springframework.security.web.authentication.logout.LogoutFilter">
|
||||
<constructor-arg value="https://localhost:9443/cas/logout"/>
|
||||
<constructor-arg>
|
||||
<bean class=
|
||||
"org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
|
||||
</constructor-arg>
|
||||
<property name="filterProcessesUrl" value="/logout/cas"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The `logout` element logs the user out of the local application, but does not terminate the session with the CAS server or any other applications that have been logged into.
|
||||
The `requestSingleLogoutFilter` filter will allow the URL of `/spring_security_cas_logout` to be requested to redirect the application to the configured CAS Server logout URL.
|
||||
Then the CAS Server will send a Single Logout request to all the services that were signed into.
|
||||
The `singleLogoutFilter` handles the Single Logout request by looking up the `HttpSession` in a static `Map` and then invalidating it.
|
||||
|
||||
It might be confusing why both the `logout` element and the `singleLogoutFilter` are needed.
|
||||
It is considered best practice to logout locally first since the `SingleSignOutFilter` just stores the `HttpSession` in a static `Map` in order to call invalidate on it.
|
||||
With the configuration above, the flow of logout would be:
|
||||
|
||||
* The user requests `/logout` which would log the user out of the local application and send the user to the logout success page.
|
||||
* The logout success page, `/cas-logout.jsp`, should instruct the user to click a link pointing to `/logout/cas` in order to logout out of all applications.
|
||||
* When the user clicks the link, the user is redirected to the CAS single logout URL (https://localhost:9443/cas/logout).
|
||||
* On the CAS Server side, the CAS single logout URL then submits single logout requests to all the CAS Services.
|
||||
On the CAS Service side, JASIG's `SingleSignOutFilter` processes the logout request by invaliditing the original session.
|
||||
|
||||
|
||||
|
||||
The next step is to add the following to your web.xml
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<filter>
|
||||
<filter-name>characterEncodingFilter</filter-name>
|
||||
<filter-class>
|
||||
org.springframework.web.filter.CharacterEncodingFilter
|
||||
</filter-class>
|
||||
<init-param>
|
||||
<param-name>encoding</param-name>
|
||||
<param-value>UTF-8</param-value>
|
||||
</init-param>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>characterEncodingFilter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
<listener>
|
||||
<listener-class>
|
||||
org.jasig.cas.client.session.SingleSignOutHttpSessionListener
|
||||
</listener-class>
|
||||
</listener>
|
||||
----
|
||||
|
||||
When using the SingleSignOutFilter you might encounter some encoding issues.
|
||||
Therefore it is recommended to add the `CharacterEncodingFilter` to ensure that the character encoding is correct when using the `SingleSignOutFilter`.
|
||||
Again, refer to JASIG's documentation for details.
|
||||
The `SingleSignOutHttpSessionListener` ensures that when an `HttpSession` expires, the mapping used for single logout is removed.
|
||||
|
||||
|
||||
[[cas-pt-client]]
|
||||
==== Authenticating to a Stateless Service with CAS
|
||||
This section describes how to authenticate to a service using CAS.
|
||||
In other words, this section discusses how to setup a client that uses a service that authenticates with CAS.
|
||||
The next section describes how to setup a stateless service to Authenticate using CAS.
|
||||
|
||||
|
||||
[[cas-pt-client-config]]
|
||||
===== Configuring CAS to Obtain Proxy Granting Tickets
|
||||
In order to authenticate to a stateless service, the application needs to obtain a proxy granting ticket (PGT).
|
||||
This section describes how to configure Spring Security to obtain a PGT building upon thencas-st[Service Ticket Authentication] configuration.
|
||||
|
||||
The first step is to include a `ProxyGrantingTicketStorage` in your Spring Security configuration.
|
||||
This is used to store PGT's that are obtained by the `CasAuthenticationFilter` so that they can be used to obtain proxy tickets.
|
||||
An example configuration is shown below
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<!--
|
||||
NOTE: In a real application you should not use an in memory implementation.
|
||||
You will also want to ensure to clean up expired tickets by calling
|
||||
ProxyGrantingTicketStorage.cleanup()
|
||||
-->
|
||||
<bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>
|
||||
----
|
||||
|
||||
The next step is to update the `CasAuthenticationProvider` to be able to obtain proxy tickets.
|
||||
To do this replace the `Cas20ServiceTicketValidator` with a `Cas20ProxyTicketValidator`.
|
||||
The `proxyCallbackUrl` should be set to a URL that the application will receive PGT's at.
|
||||
Last, the configuration should also reference the `ProxyGrantingTicketStorage` so it can use a PGT to obtain proxy tickets.
|
||||
You can find an example of the configuration changes that should be made below.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="casAuthenticationProvider"
|
||||
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
|
||||
...
|
||||
<property name="ticketValidator">
|
||||
<bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator">
|
||||
<constructor-arg value="https://localhost:9443/cas"/>
|
||||
<property name="proxyCallbackUrl"
|
||||
value="https://localhost:8443/cas-sample/login/cas/proxyreceptor"/>
|
||||
<property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The last step is to update the `CasAuthenticationFilter` to accept PGT and to store them in the `ProxyGrantingTicketStorage`.
|
||||
It is important the `proxyReceptorUrl` matches the `proxyCallbackUrl` of the `Cas20ProxyTicketValidator`.
|
||||
An example configuration is shown below.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
<bean id="casFilter"
|
||||
class="org.springframework.security.cas.web.CasAuthenticationFilter">
|
||||
...
|
||||
<property name="proxyGrantingTicketStorage" ref="pgtStorage"/>
|
||||
<property name="proxyReceptorUrl" value="/login/cas/proxyreceptor"/>
|
||||
</bean>
|
||||
|
||||
----
|
||||
|
||||
[[cas-pt-client-sample]]
|
||||
===== Calling a Stateless Service Using a Proxy Ticket
|
||||
Now that Spring Security obtains PGTs, you can use them to create proxy tickets which can be used to authenticate to a stateless service.
|
||||
The <<cas-sample,CAS sample application>> contains a working example in the `ProxyTicketSampleServlet`.
|
||||
Example code can be found below:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
// NOTE: The CasAuthenticationToken can also be obtained using
|
||||
// SecurityContextHolder.getContext().getAuthentication()
|
||||
final CasAuthenticationToken token = (CasAuthenticationToken) request.getUserPrincipal();
|
||||
// proxyTicket could be reused to make calls to the CAS service even if the
|
||||
// target url differs
|
||||
final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl);
|
||||
|
||||
// Make a remote call using the proxy ticket
|
||||
final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8");
|
||||
String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
[[cas-pt]]
|
||||
==== Proxy Ticket Authentication
|
||||
The `CasAuthenticationProvider` distinguishes between stateful and stateless clients.
|
||||
A stateful client is considered any that submits to the `filterProcessUrl` of the `CasAuthenticationFilter`.
|
||||
A stateless client is any that presents an authentication request to `CasAuthenticationFilter` on a URL other than the `filterProcessUrl`.
|
||||
|
||||
Because remoting protocols have no way of presenting themselves within the context of an `HttpSession`, it isn't possible to rely on the default practice of storing the security context in the session between requests.
|
||||
Furthermore, because the CAS server invalidates a ticket after it has been validated by the `TicketValidator`, presenting the same proxy ticket on subsequent requests will not work.
|
||||
|
||||
One obvious option is to not use CAS at all for remoting protocol clients.
|
||||
However, this would eliminate many of the desirable features of CAS.
|
||||
As a middle-ground, the `CasAuthenticationProvider` uses a `StatelessTicketCache`.
|
||||
This is used solely for stateless clients which use a principal equal to `CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER`.
|
||||
What happens is the `CasAuthenticationProvider` will store the resulting `CasAuthenticationToken` in the `StatelessTicketCache`, keyed on the proxy ticket.
|
||||
Accordingly, remoting protocol clients can present the same proxy ticket and the `CasAuthenticationProvider` will not need to contact the CAS server for validation (aside from the first request).
|
||||
Once authenticated, the proxy ticket could be used for URLs other than the original target service.
|
||||
|
||||
This section builds upon the previous sections to accommodate proxy ticket authentication.
|
||||
The first step is to specify to authenticate all artifacts as shown below.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="serviceProperties"
|
||||
class="org.springframework.security.cas.ServiceProperties">
|
||||
...
|
||||
<property name="authenticateAllArtifacts" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The next step is to specify `serviceProperties` and the `authenticationDetailsSource` for the `CasAuthenticationFilter`.
|
||||
The `serviceProperties` property instructs the `CasAuthenticationFilter` to attempt to authenticate all artifacts instead of only ones present on the `filterProcessUrl`.
|
||||
The `ServiceAuthenticationDetailsSource` creates a `ServiceAuthenticationDetails` that ensures the current URL, based upon the `HttpServletRequest`, is used as the service URL when validating the ticket.
|
||||
The method for generating the service URL can be customized by injecting a custom `AuthenticationDetailsSource` that returns a custom `ServiceAuthenticationDetails`.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="casFilter"
|
||||
class="org.springframework.security.cas.web.CasAuthenticationFilter">
|
||||
...
|
||||
<property name="serviceProperties" ref="serviceProperties"/>
|
||||
<property name="authenticationDetailsSource">
|
||||
<bean class=
|
||||
"org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource">
|
||||
<constructor-arg ref="serviceProperties"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
You will also need to update the `CasAuthenticationProvider` to handle proxy tickets.
|
||||
To do this replace the `Cas20ServiceTicketValidator` with a `Cas20ProxyTicketValidator`.
|
||||
You will need to configure the `statelessTicketCache` and which proxies you want to accept.
|
||||
You can find an example of the updates required to accept all proxies below.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
<bean id="casAuthenticationProvider"
|
||||
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
|
||||
...
|
||||
<property name="ticketValidator">
|
||||
<bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator">
|
||||
<constructor-arg value="https://localhost:9443/cas"/>
|
||||
<property name="acceptAnyProxy" value="true"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="statelessTicketCache">
|
||||
<bean class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache">
|
||||
<property name="cache">
|
||||
<bean class="net.sf.ehcache.Cache"
|
||||
init-method="initialise" destroy-method="dispose">
|
||||
<constructor-arg value="casTickets"/>
|
||||
<constructor-arg value="50"/>
|
||||
<constructor-arg value="true"/>
|
||||
<constructor-arg value="false"/>
|
||||
<constructor-arg value="3600"/>
|
||||
<constructor-arg value="900"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
|
@ -0,0 +1,167 @@
|
|||
|
||||
|
||||
[[concurrency]]
|
||||
== Concurrency Support
|
||||
|
||||
In most environments, Security is stored on a per `Thread` basis.
|
||||
This means that when work is done on a new `Thread`, the `SecurityContext` is lost.
|
||||
Spring Security provides some infrastructure to help make this much easier for users.
|
||||
Spring Security provides low level abstractions for working with Spring Security in multi-threaded environments.
|
||||
In fact, this is what Spring Security builds on to integration with <<servletapi-start-runnable>> and <<mvc-async>>.
|
||||
|
||||
=== DelegatingSecurityContextRunnable
|
||||
|
||||
One of the most fundamental building blocks within Spring Security's concurrency support is the `DelegatingSecurityContextRunnable`.
|
||||
It wraps a delegate `Runnable` in order to initialize the `SecurityContextHolder` with a specified `SecurityContext` for the delegate.
|
||||
It then invokes the delegate Runnable ensuring to clear the `SecurityContextHolder` afterwards.
|
||||
The `DelegatingSecurityContextRunnable` looks something like this:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public void run() {
|
||||
try {
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
delegate.run();
|
||||
} finally {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
While very simple, it makes it seamless to transfer the SecurityContext from one Thread to another.
|
||||
This is important since, in most cases, the SecurityContextHolder acts on a per Thread basis.
|
||||
For example, you might have used Spring Security's <<nsa-global-method-security>> support to secure one of your services.
|
||||
You can now easily transfer the `SecurityContext` of the current `Thread` to the `Thread` that invokes the secured service.
|
||||
An example of how you might do this can be found below:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
Runnable originalRunnable = new Runnable() {
|
||||
public void run() {
|
||||
// invoke secured service
|
||||
}
|
||||
};
|
||||
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
DelegatingSecurityContextRunnable wrappedRunnable =
|
||||
new DelegatingSecurityContextRunnable(originalRunnable, context);
|
||||
|
||||
new Thread(wrappedRunnable).start();
|
||||
----
|
||||
|
||||
The code above performs the following steps:
|
||||
|
||||
* Creates a `Runnable` that will be invoking our secured service.
|
||||
Notice that it is not aware of Spring Security
|
||||
* Obtains the `SecurityContext` that we wish to use from the `SecurityContextHolder` and initializes the `DelegatingSecurityContextRunnable`
|
||||
* Use the `DelegatingSecurityContextRunnable` to create a Thread
|
||||
* Start the Thread we created
|
||||
|
||||
Since it is quite common to create a `DelegatingSecurityContextRunnable` with the `SecurityContext` from the `SecurityContextHolder` there is a shortcut constructor for it.
|
||||
The following code is the same as the code above:
|
||||
|
||||
|
||||
[source,java]
|
||||
----
|
||||
Runnable originalRunnable = new Runnable() {
|
||||
public void run() {
|
||||
// invoke secured service
|
||||
}
|
||||
};
|
||||
|
||||
DelegatingSecurityContextRunnable wrappedRunnable =
|
||||
new DelegatingSecurityContextRunnable(originalRunnable);
|
||||
|
||||
new Thread(wrappedRunnable).start();
|
||||
----
|
||||
|
||||
The code we have is simple to use, but it still requires knowledge that we are using Spring Security.
|
||||
In the next section we will take a look at how we can utilize `DelegatingSecurityContextExecutor` to hide the fact that we are using Spring Security.
|
||||
|
||||
=== DelegatingSecurityContextExecutor
|
||||
|
||||
In the previous section we found that it was easy to use the `DelegatingSecurityContextRunnable`, but it was not ideal since we had to be aware of Spring Security in order to use it.
|
||||
Let's take a look at how `DelegatingSecurityContextExecutor` can shield our code from any knowledge that we are using Spring Security.
|
||||
|
||||
The design of `DelegatingSecurityContextExecutor` is very similar to that of `DelegatingSecurityContextRunnable` except it accepts a delegate `Executor` instead of a delegate `Runnable`.
|
||||
You can see an example of how it might be used below:
|
||||
|
||||
|
||||
[source,java]
|
||||
----
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
Authentication authentication =
|
||||
new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
context.setAuthentication(authentication);
|
||||
|
||||
SimpleAsyncTaskExecutor delegateExecutor =
|
||||
new SimpleAsyncTaskExecutor();
|
||||
DelegatingSecurityContextExecutor executor =
|
||||
new DelegatingSecurityContextExecutor(delegateExecutor, context);
|
||||
|
||||
Runnable originalRunnable = new Runnable() {
|
||||
public void run() {
|
||||
// invoke secured service
|
||||
}
|
||||
};
|
||||
|
||||
executor.execute(originalRunnable);
|
||||
----
|
||||
|
||||
The code performs the following steps:
|
||||
|
||||
* Creates the `SecurityContext` to be used for our `DelegatingSecurityContextExecutor`.
|
||||
Note that in this example we simply create the `SecurityContext` by hand.
|
||||
However, it does not matter where or how we get the `SecurityContext` (i.e. we could obtain it from the `SecurityContextHolder` if we wanted).
|
||||
* Creates a delegateExecutor that is in charge of executing submitted ``Runnable``s
|
||||
* Finally we create a `DelegatingSecurityContextExecutor` which is in charge of wrapping any Runnable that is passed into the execute method with a `DelegatingSecurityContextRunnable`.
|
||||
It then passes the wrapped Runnable to the delegateExecutor.
|
||||
In this instance, the same `SecurityContext` will be used for every Runnable submitted to our `DelegatingSecurityContextExecutor`.
|
||||
This is nice if we are running background tasks that need to be run by a user with elevated privileges.
|
||||
* At this point you may be asking yourself "How does this shield my code of any knowledge of Spring Security?" Instead of creating the `SecurityContext` and the `DelegatingSecurityContextExecutor` in our own code, we can inject an already initialized instance of `DelegatingSecurityContextExecutor`.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Autowired
|
||||
private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor
|
||||
|
||||
public void submitRunnable() {
|
||||
Runnable originalRunnable = new Runnable() {
|
||||
public void run() {
|
||||
// invoke secured service
|
||||
}
|
||||
};
|
||||
executor.execute(originalRunnable);
|
||||
}
|
||||
----
|
||||
|
||||
Now our code is unaware that the `SecurityContext` is being propagated to the `Thread`, then the `originalRunnable` is executed, and then the `SecurityContextHolder` is cleared out.
|
||||
In this example, the same user is being used to execute each Thread.
|
||||
What if we wanted to use the user from `SecurityContextHolder` at the time we invoked `executor.execute(Runnable)` (i.e. the currently logged in user) to process ``originalRunnable``?
|
||||
This can be done by removing the `SecurityContext` argument from our `DelegatingSecurityContextExecutor` constructor.
|
||||
For example:
|
||||
|
||||
|
||||
[source,java]
|
||||
----
|
||||
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
|
||||
DelegatingSecurityContextExecutor executor =
|
||||
new DelegatingSecurityContextExecutor(delegateExecutor);
|
||||
----
|
||||
|
||||
Now anytime `executor.execute(Runnable)` is executed the `SecurityContext` is first obtained by the `SecurityContextHolder` and then that `SecurityContext` is used to create our `DelegatingSecurityContextRunnable`.
|
||||
This means that we are executing our `Runnable` with the same user that was used to invoke the `executor.execute(Runnable)` code.
|
||||
|
||||
=== Spring Security Concurrency Classes
|
||||
|
||||
Refer to the Javadoc for additional integrations with both the Java concurrent APIs and the Spring Task abstractions.
|
||||
They are quite self-explanatory once you understand the previous code.
|
||||
|
||||
* DelegatingSecurityContextCallable
|
||||
* DelegatingSecurityContextExecutor
|
||||
* DelegatingSecurityContextExecutorService
|
||||
* DelegatingSecurityContextRunnable
|
||||
* DelegatingSecurityContextScheduledExecutorService
|
||||
* DelegatingSecurityContextSchedulingTaskExecutor
|
||||
* DelegatingSecurityContextAsyncTaskExecutor
|
||||
* DelegatingSecurityContextTaskExecutor
|
|
@ -0,0 +1,151 @@
|
|||
[[crypto]]
|
||||
== Spring Security Crypto Module
|
||||
|
||||
|
||||
[[spring-security-crypto-introduction]]
|
||||
=== Introduction
|
||||
The Spring Security Crypto module provides support for symmetric encryption, key generation, and password encoding.
|
||||
The code is distributed as part of the core module but has no dependencies on any other Spring Security (or Spring) code.
|
||||
|
||||
|
||||
[[spring-security-crypto-encryption]]
|
||||
=== Encryptors
|
||||
The Encryptors class provides factory methods for constructing symmetric encryptors.
|
||||
Using this class, you can create ByteEncryptors to encrypt data in raw byte[] form.
|
||||
You can also construct TextEncryptors to encrypt text strings.
|
||||
Encryptors are thread-safe.
|
||||
|
||||
[[spring-security-crypto-encryption-bytes]]
|
||||
==== BytesEncryptor
|
||||
Use the Encryptors.standard factory method to construct a "standard" BytesEncryptor:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
Encryptors.standard("password", "salt");
|
||||
----
|
||||
|
||||
The "standard" encryption method is 256-bit AES using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2).
|
||||
This method requires Java 6.
|
||||
The password used to generate the SecretKey should be kept in a secure place and not be shared.
|
||||
The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised.
|
||||
A 16-byte random initialization vector is also applied so each encrypted message is unique.
|
||||
|
||||
The provided salt should be in hex-encoded String form, be random, and be at least 8 bytes in length.
|
||||
Such a salt may be generated using a KeyGenerator:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
|
||||
----
|
||||
|
||||
[[spring-security-crypto-encryption-text]]
|
||||
==== TextEncryptor
|
||||
Use the Encryptors.text factory method to construct a standard TextEncryptor:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
Encryptors.text("password", "salt");
|
||||
----
|
||||
|
||||
A TextEncryptor uses a standard BytesEncryptor to encrypt text data.
|
||||
Encrypted results are returned as hex-encoded strings for easy storage on the filesystem or in the database.
|
||||
|
||||
Use the Encryptors.queryableText factory method to construct a "queryable" TextEncryptor:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
Encryptors.queryableText("password", "salt");
|
||||
----
|
||||
|
||||
The difference between a queryable TextEncryptor and a standard TextEncryptor has to do with initialization vector (iv) handling.
|
||||
The iv used in a queryable TextEncryptor#encrypt operation is shared, or constant, and is not randomly generated.
|
||||
This means the same text encrypted multiple times will always produce the same encryption result.
|
||||
This is less secure, but necessary for encrypted data that needs to be queried against.
|
||||
An example of queryable encrypted text would be an OAuth apiKey.
|
||||
|
||||
[[spring-security-crypto-keygenerators]]
|
||||
=== Key Generators
|
||||
The KeyGenerators class provides a number of convenience factory methods for constructing different types of key generators.
|
||||
Using this class, you can create a BytesKeyGenerator to generate byte[] keys.
|
||||
You can also construct a StringKeyGenerator to generate string keys.
|
||||
KeyGenerators are thread-safe.
|
||||
|
||||
==== BytesKeyGenerator
|
||||
Use the KeyGenerators.secureRandom factory methods to generate a BytesKeyGenerator backed by a SecureRandom instance:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
BytesKeyGenerator generator = KeyGenerators.secureRandom();
|
||||
byte[] key = generator.generateKey();
|
||||
----
|
||||
|
||||
The default key length is 8 bytes.
|
||||
There is also a KeyGenerators.secureRandom variant that provides control over the key length:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
KeyGenerators.secureRandom(16);
|
||||
----
|
||||
|
||||
Use the KeyGenerators.shared factory method to construct a BytesKeyGenerator that always returns the same key on every invocation:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
KeyGenerators.shared(16);
|
||||
----
|
||||
|
||||
==== StringKeyGenerator
|
||||
Use the KeyGenerators.string factory method to construct a 8-byte, SecureRandom KeyGenerator that hex-encodes each key as a String:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
KeyGenerators.string();
|
||||
----
|
||||
|
||||
[[spring-security-crypto-passwordencoders]]
|
||||
=== Password Encoding
|
||||
The password package of the spring-security-crypto module provides support for encoding passwords.
|
||||
`PasswordEncoder` is the central service interface and has the following signature:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public interface PasswordEncoder {
|
||||
|
||||
String encode(String rawPassword);
|
||||
|
||||
boolean matches(String rawPassword, String encodedPassword);
|
||||
}
|
||||
----
|
||||
|
||||
The matches method returns true if the rawPassword, once encoded, equals the encodedPassword.
|
||||
This method is designed to support password-based authentication schemes.
|
||||
|
||||
The `BCryptPasswordEncoder` implementation uses the widely supported "bcrypt" algorithm to hash the passwords.
|
||||
Bcrypt uses a random 16 byte salt value and is a deliberately slow algorithm, in order to hinder password crackers.
|
||||
The amount of work it does can be tuned using the "strength" parameter which takes values from 4 to 31.
|
||||
The higher the value, the more work has to be done to calculate the hash.
|
||||
The default value is 10.
|
||||
You can change this value in your deployed system without affecting existing passwords, as the value is also stored in the encoded hash.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
// Create an encoder with strength 16
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
|
||||
String result = encoder.encode("myPassword");
|
||||
assertTrue(encoder.matches("myPassword", result));
|
||||
----
|
||||
|
||||
The `Pbkdf2PasswordEncoder` implementation uses PBKDF2 algorithm to hash the passwords.
|
||||
In order to defeat password cracking PBKDF2 is a deliberately slow algorithm and should be tuned to take about .5 seconds to verify a password on your system.
|
||||
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
// Create an encoder with all the defaults
|
||||
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
|
||||
String result = encoder.encode("myPassword");
|
||||
assertTrue(encoder.matches("myPassword", result));
|
||||
----
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
[[advanced-topics]]
|
||||
= Additional Topics
|
||||
In this part we cover features which require knowledge of previous chapters as well as some of the more advanced and less-commonly used features of the framework.
|
||||
|
||||
include::acls.adoc[]
|
||||
|
||||
include::preauth.adoc[]
|
||||
|
||||
include::ldap.adoc[]
|
||||
|
||||
include::oauth2.adoc[]
|
||||
|
||||
include::jsp-taglibs.adoc[]
|
||||
|
||||
include::jaas.adoc[]
|
||||
|
||||
include::cas.adoc[]
|
||||
|
||||
include::x509.adoc[]
|
||||
|
||||
include::runas.adoc[]
|
||||
|
||||
include::crypto.adoc[]
|
||||
|
||||
include::concurrency.adoc[]
|
||||
|
||||
include::mvc.adoc[]
|
|
@ -0,0 +1,171 @@
|
|||
|
||||
[[jaas]]
|
||||
== Java Authentication and Authorization Service (JAAS) Provider
|
||||
|
||||
|
||||
=== Overview
|
||||
Spring Security provides a package able to delegate authentication requests to the Java Authentication and Authorization Service (JAAS).
|
||||
This package is discussed in detail below.
|
||||
|
||||
|
||||
[[jaas-abstractjaasauthenticationprovider]]
|
||||
=== AbstractJaasAuthenticationProvider
|
||||
The `AbstractJaasAuthenticationProvider` is the basis for the provided JAAS `AuthenticationProvider` implementations.
|
||||
Subclasses must implement a method that creates the `LoginContext`.
|
||||
The `AbstractJaasAuthenticationProvider` has a number of dependencies that can be injected into it that are discussed below.
|
||||
|
||||
|
||||
[[jaas-callbackhandler]]
|
||||
==== JAAS CallbackHandler
|
||||
Most JAAS `LoginModule` s require a callback of some sort.
|
||||
These callbacks are usually used to obtain the username and password from the user.
|
||||
|
||||
In a Spring Security deployment, Spring Security is responsible for this user interaction (via the authentication mechanism).
|
||||
Thus, by the time the authentication request is delegated through to JAAS, Spring Security's authentication mechanism will already have fully-populated an `Authentication` object containing all the information required by the JAAS `LoginModule`.
|
||||
|
||||
Therefore, the JAAS package for Spring Security provides two default callback handlers, `JaasNameCallbackHandler` and `JaasPasswordCallbackHandler`.
|
||||
Each of these callback handlers implement `JaasAuthenticationCallbackHandler`.
|
||||
In most cases these callback handlers can simply be used without understanding the internal mechanics.
|
||||
|
||||
For those needing full control over the callback behavior, internally `AbstractJaasAuthenticationProvider` wraps these `JaasAuthenticationCallbackHandler` s with an `InternalCallbackHandler`.
|
||||
The `InternalCallbackHandler` is the class that actually implements JAAS normal `CallbackHandler` interface.
|
||||
Any time that the JAAS `LoginModule` is used, it is passed a list of application context configured `InternalCallbackHandler` s.
|
||||
If the `LoginModule` requests a callback against the `InternalCallbackHandler` s, the callback is in-turn passed to the `JaasAuthenticationCallbackHandler` s being wrapped.
|
||||
|
||||
|
||||
[[jaas-authoritygranter]]
|
||||
==== JAAS AuthorityGranter
|
||||
JAAS works with principals.
|
||||
Even "roles" are represented as principals in JAAS.
|
||||
Spring Security, on the other hand, works with `Authentication` objects.
|
||||
Each `Authentication` object contains a single principal, and multiple `GrantedAuthority` s.
|
||||
To facilitate mapping between these different concepts, Spring Security's JAAS package includes an `AuthorityGranter` interface.
|
||||
|
||||
An `AuthorityGranter` is responsible for inspecting a JAAS principal and returning a set of `String` s, representing the authorities assigned to the principal.
|
||||
For each returned authority string, the `AbstractJaasAuthenticationProvider` creates a `JaasGrantedAuthority` (which implements Spring Security's `GrantedAuthority` interface) containing the authority string and the JAAS principal that the `AuthorityGranter` was passed.
|
||||
The `AbstractJaasAuthenticationProvider` obtains the JAAS principals by firstly successfully authenticating the user's credentials using the JAAS `LoginModule`, and then accessing the `LoginContext` it returns.
|
||||
A call to `LoginContext.getSubject().getPrincipals()` is made, with each resulting principal passed to each `AuthorityGranter` defined against the `AbstractJaasAuthenticationProvider.setAuthorityGranters(List)` property.
|
||||
|
||||
Spring Security does not include any production `AuthorityGranter` s given that every JAAS principal has an implementation-specific meaning.
|
||||
However, there is a `TestAuthorityGranter` in the unit tests that demonstrates a simple `AuthorityGranter` implementation.
|
||||
|
||||
|
||||
[[jaas-defaultjaasauthenticationprovider]]
|
||||
=== DefaultJaasAuthenticationProvider
|
||||
The `DefaultJaasAuthenticationProvider` allows a JAAS `Configuration` object to be injected into it as a dependency.
|
||||
It then creates a `LoginContext` using the injected JAAS `Configuration`.
|
||||
This means that `DefaultJaasAuthenticationProvider` is not bound any particular implementation of `Configuration` as `JaasAuthenticationProvider` is.
|
||||
|
||||
|
||||
[[jaas-inmemoryconfiguration]]
|
||||
==== InMemoryConfiguration
|
||||
In order to make it easy to inject a `Configuration` into `DefaultJaasAuthenticationProvider`, a default in-memory implementation named `InMemoryConfiguration` is provided.
|
||||
The implementation constructor accepts a `Map` where each key represents a login configuration name and the value represents an `Array` of `AppConfigurationEntry` s.
|
||||
`InMemoryConfiguration` also supports a default `Array` of `AppConfigurationEntry` objects that will be used if no mapping is found within the provided `Map`.
|
||||
For details, refer to the class level javadoc of `InMemoryConfiguration`.
|
||||
|
||||
|
||||
[[jaas-djap-config]]
|
||||
==== DefaultJaasAuthenticationProvider Example Configuration
|
||||
While the Spring configuration for `InMemoryConfiguration` can be more verbose than the standarad JAAS configuration files, using it in conjuction with `DefaultJaasAuthenticationProvider` is more flexible than `JaasAuthenticationProvider` since it not dependant on the default `Configuration` implementation.
|
||||
|
||||
An example configuration of `DefaultJaasAuthenticationProvider` using `InMemoryConfiguration` is provided below.
|
||||
Note that custom implementations of `Configuration` can easily be injected into `DefaultJaasAuthenticationProvider` as well.
|
||||
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
<bean id="jaasAuthProvider"
|
||||
class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider">
|
||||
<property name="configuration">
|
||||
<bean class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration">
|
||||
<constructor-arg>
|
||||
<map>
|
||||
<!--
|
||||
SPRINGSECURITY is the default loginContextName
|
||||
for AbstractJaasAuthenticationProvider
|
||||
-->
|
||||
<entry key="SPRINGSECURITY">
|
||||
<array>
|
||||
<bean class="javax.security.auth.login.AppConfigurationEntry">
|
||||
<constructor-arg value="sample.SampleLoginModule" />
|
||||
<constructor-arg>
|
||||
<util:constant static-field=
|
||||
"javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED"/>
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<map></map>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</array>
|
||||
</entry>
|
||||
</map>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="authorityGranters">
|
||||
<list>
|
||||
<!-- You will need to write your own implementation of AuthorityGranter -->
|
||||
<bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[jaas-jaasauthenticationprovider]]
|
||||
=== JaasAuthenticationProvider
|
||||
The `JaasAuthenticationProvider` assumes the default `Configuration` is an instance of http://download.oracle.com/javase/1.4.2/docs/guide/security/jaas/spec/com/sun/security/auth/login/ConfigFile.html[ ConfigFile].
|
||||
This assumption is made in order to attempt to update the `Configuration`.
|
||||
The `JaasAuthenticationProvider` then uses the default `Configuration` to create the `LoginContext`.
|
||||
|
||||
Let's assume we have a JAAS login configuration file, `/WEB-INF/login.conf`, with the following contents:
|
||||
|
||||
[source,txt]
|
||||
----
|
||||
JAASTest {
|
||||
sample.SampleLoginModule required;
|
||||
};
|
||||
----
|
||||
|
||||
Like all Spring Security beans, the `JaasAuthenticationProvider` is configured via the application context.
|
||||
The following definitions would correspond to the above JAAS login configuration file:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
<bean id="jaasAuthenticationProvider"
|
||||
class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider">
|
||||
<property name="loginConfig" value="/WEB-INF/login.conf"/>
|
||||
<property name="loginContextName" value="JAASTest"/>
|
||||
<property name="callbackHandlers">
|
||||
<list>
|
||||
<bean
|
||||
class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler"/>
|
||||
<bean
|
||||
class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler"/>
|
||||
</list>
|
||||
</property>
|
||||
<property name="authorityGranters">
|
||||
<list>
|
||||
<bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
[[jaas-apiprovision]]
|
||||
=== Running as a Subject
|
||||
If configured, the `JaasApiIntegrationFilter` will attempt to run as the `Subject` on the `JaasAuthenticationToken`.
|
||||
This means that the `Subject` can be accessed using:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
Subject subject = Subject.getSubject(AccessController.getContext());
|
||||
----
|
||||
|
||||
This integration can easily be configured using the <<nsa-http-jaas-api-provision,jaas-api-provision>> attribute.
|
||||
This feature is useful when integrating with legacy or external API's that rely on the JAAS Subject being populated.
|
|
@ -0,0 +1,203 @@
|
|||
|
||||
[[taglibs]]
|
||||
== JSP Tag Libraries
|
||||
Spring Security has its own taglib which provides basic support for accessing security information and applying security constraints in JSPs.
|
||||
|
||||
|
||||
=== Declaring the Taglib
|
||||
To use any of the tags, you must have the security taglib declared in your JSP:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
|
||||
----
|
||||
|
||||
[[taglibs-authorize]]
|
||||
=== The authorize Tag
|
||||
This tag is used to determine whether its contents should be evaluated or not.
|
||||
In Spring Security 3.0, it can be used in two ways footnote:[
|
||||
The legacy options from Spring Security 2.0 are also supported, but discouraged.
|
||||
].
|
||||
The first approach uses a <<el-access-web,web-security expression>>, specified in the `access` attribute of the tag.
|
||||
The expression evaluation will be delegated to the `SecurityExpressionHandler<FilterInvocation>` defined in the application context (you should have web expressions enabled in your `<http>` namespace configuration to make sure this service is available).
|
||||
So, for example, you might have
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<sec:authorize access="hasRole('supervisor')">
|
||||
|
||||
This content will only be visible to users who have the "supervisor" authority in their list of <tt>GrantedAuthority</tt>s.
|
||||
|
||||
</sec:authorize>
|
||||
----
|
||||
|
||||
When used in conjuction with Spring Security's PermissionEvaluator, the tag can also be used to check permissions.
|
||||
For example:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<sec:authorize access="hasPermission(#domain,'read') or hasPermission(#domain,'write')">
|
||||
|
||||
This content will only be visible to users who have read or write permission to the Object found as a request attribute named "domain".
|
||||
|
||||
</sec:authorize>
|
||||
----
|
||||
|
||||
A common requirement is to only show a particular link, if the user is actually allowed to click it.
|
||||
How can we determine in advance whether something will be allowed? This tag can also operate in an alternative mode which allows you to define a particular URL as an attribute.
|
||||
If the user is allowed to invoke that URL, then the tag body will be evaluated, otherwise it will be skipped.
|
||||
So you might have something like
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<sec:authorize url="/admin">
|
||||
|
||||
This content will only be visible to users who are authorized to send requests to the "/admin" URL.
|
||||
|
||||
</sec:authorize>
|
||||
----
|
||||
|
||||
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`, which creates a dummy web request for the supplied URL and invokes the security interceptor to see whether the request would succeed or fail.
|
||||
This allows you to delegate to the access-control setup you defined using `intercept-url` declarations within the `<http>` namespace configuration and saves having to duplicate the information (such as the required roles) within your JSPs.
|
||||
This approach can also be combined with a `method` attribute, supplying the HTTP method, for a more specific match.
|
||||
|
||||
The Boolean result of evaluating the tag (whether it grants or denies access) can be stored in a page context scope variable by setting the `var` attribute to the variable name, avoiding the need for duplicating and re-evaluating the condition at other points in the page.
|
||||
|
||||
|
||||
==== Disabling Tag Authorization for Testing
|
||||
Hiding a link in a page for unauthorized users doesn't prevent them from accessing the URL.
|
||||
They could just type it into their browser directly, for example.
|
||||
As part of your testing process, you may want to reveal the hidden areas in order to check that links really are secured at the back end.
|
||||
If you set the system property `spring.security.disableUISecurity` to `true`, the `authorize` tag will still run but will not hide its contents.
|
||||
By default it will also surround the content with `<span class="securityHiddenUI">...</span>` tags.
|
||||
This allows you to display "hidden" content with a particular CSS style such as a different background colour.
|
||||
Try running the "tutorial" sample application with this property enabled, for example.
|
||||
|
||||
You can also set the properties `spring.security.securedUIPrefix` and `spring.security.securedUISuffix` if you want to change surrounding text from the default `span` tags (or use empty strings to remove it completely).
|
||||
|
||||
|
||||
=== The authentication Tag
|
||||
This tag allows access to the current `Authentication` object stored in the security context.
|
||||
It renders a property of the object directly in the JSP.
|
||||
So, for example, if the `principal` property of the `Authentication` is an instance of Spring Security's `UserDetails` object, then using `<sec:authentication property="principal.username" />` will render the name of the current user.
|
||||
|
||||
Of course, it isn't necessary to use JSP tags for this kind of thing and some people prefer to keep as little logic as possible in the view.
|
||||
You can access the `Authentication` object in your MVC controller (by calling `SecurityContextHolder.getContext().getAuthentication()`) and add the data directly to your model for rendering by the view.
|
||||
|
||||
|
||||
=== The accesscontrollist Tag
|
||||
This tag is only valid when used with Spring Security's ACL module.
|
||||
It checks a comma-separated list of required permissions for a specified domain object.
|
||||
If the current user has all of those permissions, then the tag body will be evaluated.
|
||||
If they don't, it will be skipped.
|
||||
An example might be
|
||||
|
||||
CAUTION: In general this tag should be considered deprecated.
|
||||
Instead use the <<taglibs-authorize>>.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<sec:accesscontrollist hasPermission="1,2" domainObject="${someObject}">
|
||||
|
||||
This will be shown if the user has all of the permissions represented by the values "1" or "2" on the given object.
|
||||
|
||||
</sec:accesscontrollist>
|
||||
----
|
||||
|
||||
The permissions are passed to the `PermissionFactory` defined in the application context, converting them to ACL `Permission` instances, so they may be any format which is supported by the factory - they don't have to be integers, they could be strings like `READ` or `WRITE`.
|
||||
If no `PermissionFactory` is found, an instance of `DefaultPermissionFactory` will be used.
|
||||
The `AclService` from the application context will be used to load the `Acl` instance for the supplied object.
|
||||
The `Acl` will be invoked with the required permissions to check if all of them are granted.
|
||||
|
||||
This tag also supports the `var` attribute, in the same way as the `authorize` tag.
|
||||
|
||||
|
||||
=== The csrfInput Tag
|
||||
If CSRF protection is enabled, this tag inserts a hidden form field with the correct name and value for the CSRF protection token.
|
||||
If CSRF protection is not enabled, this tag outputs nothing.
|
||||
|
||||
Normally Spring Security automatically inserts a CSRF form field for any `<form:form>` tags you use, but if for some reason you cannot use `<form:form>`, `csrfInput` is a handy replacement.
|
||||
|
||||
You should place this tag within an HTML `<form></form>` block, where you would normally place other input fields.
|
||||
Do NOT place this tag within a Spring `<form:form></form:form>` block.
|
||||
Spring Security handles Spring forms automatically.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<form method="post" action="/do/something">
|
||||
<sec:csrfInput />
|
||||
Name:<br />
|
||||
<input type="text" name="name" />
|
||||
...
|
||||
</form>
|
||||
----
|
||||
|
||||
|
||||
=== The csrfMetaTags Tag
|
||||
If CSRF protection is enabled, this tag inserts meta tags containing the CSRF protection token form field and header names and CSRF protection token value.
|
||||
These meta tags are useful for employing CSRF protection within JavaScript in your applications.
|
||||
|
||||
You should place `csrfMetaTags` within an HTML `<head></head>` block, where you would normally place other meta tags.
|
||||
Once you use this tag, you can access the form field name, header name, and token value easily using JavaScript.
|
||||
JQuery is used in this example to make the task easier.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSRF Protected JavaScript Page</title>
|
||||
<meta name="description" content="This is the description for this page" />
|
||||
<sec:csrfMetaTags />
|
||||
<script type="text/javascript" language="javascript">
|
||||
|
||||
var csrfParameter = $("meta[name='_csrf_parameter']").attr("content");
|
||||
var csrfHeader = $("meta[name='_csrf_header']").attr("content");
|
||||
var csrfToken = $("meta[name='_csrf']").attr("content");
|
||||
|
||||
// using XMLHttpRequest directly to send an x-www-form-urlencoded request
|
||||
var ajax = new XMLHttpRequest();
|
||||
ajax.open("POST", "http://www.example.org/do/something", true);
|
||||
ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded data");
|
||||
ajax.send(csrfParameter + "=" + csrfToken + "&name=John&...");
|
||||
|
||||
// using XMLHttpRequest directly to send a non-x-www-form-urlencoded request
|
||||
var ajax = new XMLHttpRequest();
|
||||
ajax.open("POST", "http://www.example.org/do/something", true);
|
||||
ajax.setRequestHeader(csrfHeader, csrfToken);
|
||||
ajax.send("...");
|
||||
|
||||
// using JQuery to send an x-www-form-urlencoded request
|
||||
var data = {};
|
||||
data[csrfParameter] = csrfToken;
|
||||
data["name"] = "John";
|
||||
...
|
||||
$.ajax({
|
||||
url: "http://www.example.org/do/something",
|
||||
type: "POST",
|
||||
data: data,
|
||||
...
|
||||
});
|
||||
|
||||
// using JQuery to send a non-x-www-form-urlencoded request
|
||||
var headers = {};
|
||||
headers[csrfHeader] = csrfToken;
|
||||
$.ajax({
|
||||
url: "http://www.example.org/do/something",
|
||||
type: "POST",
|
||||
headers: headers,
|
||||
...
|
||||
});
|
||||
|
||||
<script>
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
----
|
||||
|
||||
If CSRF protection is not enabled, `csrfMetaTags` outputs nothing.
|
||||
|
|
@ -0,0 +1,321 @@
|
|||
[[ldap]]
|
||||
== LDAP Authentication
|
||||
|
||||
|
||||
[[ldap-overview]]
|
||||
=== Overview
|
||||
LDAP is often used by organizations as a central repository for user information and as an authentication service.
|
||||
It can also be used to store the role information for application users.
|
||||
|
||||
There are many different scenarios for how an LDAP server may be configured so Spring Security's LDAP provider is fully configurable.
|
||||
It uses separate strategy interfaces for authentication and role retrieval and provides default implementations which can be configured to handle a wide range of situations.
|
||||
|
||||
You should be familiar with LDAP before trying to use it with Spring Security.
|
||||
The following link provides a good introduction to the concepts involved and a guide to setting up a directory using the free LDAP server OpenLDAP: http://www.zytrax.com/books/ldap/[http://www.zytrax.com/books/ldap/].
|
||||
Some familiarity with the JNDI APIs used to access LDAP from Java may also be useful.
|
||||
We don't use any third-party LDAP libraries (Mozilla, JLDAP etc.) in the LDAP provider, but extensive use is made of Spring LDAP, so some familiarity with that project may be useful if you plan on adding your own customizations.
|
||||
|
||||
When using LDAP authentication, it is important to ensure that you configure LDAP connection pooling properly.
|
||||
If you are unfamiliar with how to do this, you can refer to the http://docs.oracle.com/javase/jndi/tutorial/ldap/connect/config.html[Java LDAP documentation].
|
||||
|
||||
=== Using LDAP with Spring Security
|
||||
LDAP authentication in Spring Security can be roughly divided into the following stages.
|
||||
|
||||
* Obtaining the unique LDAP "Distinguished Name", or DN, from the login name.
|
||||
This will often mean performing a search in the directory, unless the exact mapping of usernames to DNs is known in advance.
|
||||
So a user might enter the name "joe" when logging in, but the actual name used to authenticate to LDAP will be the full DN, such as `uid=joe,ou=users,dc=spring,dc=io`.
|
||||
|
||||
* Authenticating the user, either by "binding" as that user or by performing a remote "compare" operation of the user's password against the password attribute in the directory entry for the DN.
|
||||
|
||||
* Loading the list of authorities for the user.
|
||||
|
||||
The exception is when the LDAP directory is just being used to retrieve user information and authenticate against it locally.
|
||||
This may not be possible as directories are often set up with limited read access for attributes such as user passwords.
|
||||
|
||||
We will look at some configuration scenarios below.
|
||||
For full information on available configuration options, please consult the security namespace schema (information from which should be available in your XML editor).
|
||||
|
||||
|
||||
[[ldap-server]]
|
||||
=== Configuring an LDAP Server
|
||||
The first thing you need to do is configure the server against which authentication should take place.
|
||||
This is done using the `<ldap-server>` element from the security namespace.
|
||||
This can be configured to point at an external LDAP server, using the `url` attribute:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />
|
||||
----
|
||||
|
||||
==== Using an Embedded Test Server
|
||||
The `<ldap-server>` element can also be used to create an embedded server, which can be very useful for testing and demonstrations.
|
||||
In this case you use it without the `url` attribute:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<ldap-server root="dc=springframework,dc=org"/>
|
||||
----
|
||||
|
||||
Here we've specified that the root DIT of the directory should be "dc=springframework,dc=org", which is the default.
|
||||
Used this way, the namespace parser will create an embedded Apache Directory server and scan the classpath for any LDIF files, which it will attempt to load into the server.
|
||||
You can customize this behaviour using the `ldif` attribute, which defines an LDIF resource to be loaded:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<ldap-server ldif="classpath:users.ldif" />
|
||||
----
|
||||
|
||||
This makes it a lot easier to get up and running with LDAP, since it can be inconvenient to work all the time with an external server.
|
||||
It also insulates the user from the complex bean configuration needed to wire up an Apache Directory server.
|
||||
Using plain Spring Beans the configuration would be much more cluttered.
|
||||
You must have the necessary Apache Directory dependency jars available for your application to use.
|
||||
These can be obtained from the LDAP sample application.
|
||||
|
||||
==== Using Bind Authentication
|
||||
This is the most common LDAP authentication scenario.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"/>
|
||||
----
|
||||
|
||||
This simple example would obtain the DN for the user by substituting the user login name in the supplied pattern and attempting to bind as that user with the login password.
|
||||
This is OK if all your users are stored under a single node in the directory.
|
||||
If instead you wished to configure an LDAP search filter to locate the user, you could use the following:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<ldap-authentication-provider user-search-filter="(uid={0})"
|
||||
user-search-base="ou=people"/>
|
||||
----
|
||||
|
||||
If used with the server definition above, this would perform a search under the DN `ou=people,dc=springframework,dc=org` using the value of the `user-search-filter` attribute as a filter.
|
||||
Again the user login name is substituted for the parameter in the filter name, so it will search for an entry with the `uid` attribute equal to the user name.
|
||||
If `user-search-base` isn't supplied, the search will be performed from the root.
|
||||
|
||||
==== Loading Authorities
|
||||
How authorities are loaded from groups in the LDAP directory is controlled by the following attributes.
|
||||
|
||||
* `group-search-base`.
|
||||
Defines the part of the directory tree under which group searches should be performed.
|
||||
* `group-role-attribute`.
|
||||
The attribute which contains the name of the authority defined by the group entry.
|
||||
Defaults to `cn`
|
||||
* `group-search-filter`.
|
||||
The filter which is used to search for group membership.
|
||||
The default is `uniqueMember={0}`, corresponding to the `groupOfUniqueNames` LDAP class footnote:[Note that this is different from the default configuration of the underlying `DefaultLdapAuthoritiesPopulator` which uses `member={0}`.].
|
||||
In this case, the substituted parameter is the full distinguished name of the user.
|
||||
The parameter `{1}` can be used if you want to filter on the login name.
|
||||
|
||||
So if we used the following configuration
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"
|
||||
group-search-base="ou=groups" />
|
||||
----
|
||||
|
||||
and authenticated successfully as user "ben", the subsequent loading of authorities would perform a search under the directory entry `ou=groups,dc=springframework,dc=org`, looking for entries which contain the attribute `uniqueMember` with value `uid=ben,ou=people,dc=springframework,dc=org`.
|
||||
By default the authority names will have the prefix `ROLE_` prepended.
|
||||
You can change this using the `role-prefix` attribute.
|
||||
If you don't want any prefix, use `role-prefix="none"`.
|
||||
For more information on loading authorities, see the Javadoc for the `DefaultLdapAuthoritiesPopulator` class.
|
||||
|
||||
=== Implementation Classes
|
||||
The namespace configuration options we've used above are simple to use and much more concise than using Spring beans explicitly.
|
||||
There are situations when you may need to know how to configure Spring Security LDAP directly in your application context.
|
||||
You may wish to customize the behaviour of some of the classes, for example.
|
||||
If you're happy using namespace configuration then you can skip this section and the next one.
|
||||
|
||||
The main LDAP provider class, `LdapAuthenticationProvider`, doesn't actually do much itself but delegates the work to two other beans, an `LdapAuthenticator` and an `LdapAuthoritiesPopulator` which are responsible for authenticating the user and retrieving the user's set of `GrantedAuthority` s respectively.
|
||||
|
||||
|
||||
[[ldap-ldap-authenticators]]
|
||||
==== LdapAuthenticator Implementations
|
||||
The authenticator is also responsible for retrieving any required user attributes.
|
||||
This is because the permissions on the attributes may depend on the type of authentication being used.
|
||||
For example, if binding as the user, it may be necessary to read them with the user's own permissions.
|
||||
|
||||
There are currently two authentication strategies supplied with Spring Security:
|
||||
|
||||
* Authentication directly to the LDAP server ("bind" authentication).
|
||||
|
||||
* Password comparison, where the password supplied by the user is compared with the one stored in the repository.
|
||||
This can either be done by retrieving the value of the password attribute and checking it locally or by performing an LDAP "compare" operation, where the supplied password is passed to the server for comparison and the real password value is never retrieved.
|
||||
|
||||
[[ldap-ldap-authenticators-common]]
|
||||
===== Common Functionality
|
||||
Before it is possible to authenticate a user (by either strategy), the distinguished name (DN) has to be obtained from the login name supplied to the application.
|
||||
This can be done either by simple pattern-matching (by setting the `setUserDnPatterns` array property) or by setting the `userSearch` property.
|
||||
For the DN pattern-matching approach, a standard Java pattern format is used, and the login name will be substituted for the parameter `{0}`.
|
||||
The pattern should be relative to the DN that the configured `SpringSecurityContextSource` will bind to (see the section on <<ldap-context-source,connecting to the LDAP server>> for more information on this).
|
||||
For example, if you are using an LDAP server with the URL `ldap://monkeymachine.co.uk/dc=springframework,dc=org`, and have a pattern `uid={0},ou=greatapes`, then a login name of "gorilla" will map to a DN `uid=gorilla,ou=greatapes,dc=springframework,dc=org`.
|
||||
Each configured DN pattern will be tried in turn until a match is found.
|
||||
For information on using a search, see the section on <<ldap-searchobjects,search objects>> below.
|
||||
A combination of the two approaches can also be used - the patterns will be checked first and if no matching DN is found, the search will be used.
|
||||
|
||||
|
||||
[[ldap-ldap-authenticators-bind]]
|
||||
===== BindAuthenticator
|
||||
The class `BindAuthenticator` in the package `org.springframework.security.ldap.authentication` implements the bind authentication strategy.
|
||||
It simply attempts to bind as the user.
|
||||
|
||||
|
||||
[[ldap-ldap-authenticators-password]]
|
||||
===== PasswordComparisonAuthenticator
|
||||
The class `PasswordComparisonAuthenticator` implements the password comparison authentication strategy.
|
||||
|
||||
|
||||
[[ldap-context-source]]
|
||||
==== Connecting to the LDAP Server
|
||||
The beans discussed above have to be able to connect to the server.
|
||||
They both have to be supplied with a `SpringSecurityContextSource` which is an extension of Spring LDAP's `ContextSource`.
|
||||
Unless you have special requirements, you will usually configure a `DefaultSpringSecurityContextSource` bean, which can be configured with the URL of your LDAP server and optionally with the username and password of a "manager" user which will be used by default when binding to the server (instead of binding anonymously).
|
||||
For more information read the Javadoc for this class and for Spring LDAP's `AbstractContextSource`.
|
||||
|
||||
|
||||
[[ldap-searchobjects]]
|
||||
==== LDAP Search Objects
|
||||
Often a more complicated strategy than simple DN-matching is required to locate a user entry in the directory.
|
||||
This can be encapsulated in an `LdapUserSearch` instance which can be supplied to the authenticator implementations, for example, to allow them to locate a user.
|
||||
The supplied implementation is `FilterBasedLdapUserSearch`.
|
||||
|
||||
|
||||
[[ldap-searchobjects-filter]]
|
||||
===== FilterBasedLdapUserSearch
|
||||
This bean uses an LDAP filter to match the user object in the directory.
|
||||
The process is explained in the Javadoc for the corresponding search method on the http://java.sun.com/j2se/1.4.2/docs/api/javax/naming/directory/DirContext.html#search(javax.naming.Name%2C%2520java.lang.String%2C%2520java.lang.Object%5B%5D%2C%2520javax.naming.directory.SearchControls)[JDK DirContext class].
|
||||
As explained there, the search filter can be supplied with parameters.
|
||||
For this class, the only valid parameter is `{0}` which will be replaced with the user's login name.
|
||||
|
||||
|
||||
[[ldap-authorities]]
|
||||
==== LdapAuthoritiesPopulator
|
||||
After authenticating the user successfully, the `LdapAuthenticationProvider` will attempt to load a set of authorities for the user by calling the configured `LdapAuthoritiesPopulator` bean.
|
||||
The `DefaultLdapAuthoritiesPopulator` is an implementation which will load the authorities by searching the directory for groups of which the user is a member (typically these will be `groupOfNames` or `groupOfUniqueNames` entries in the directory).
|
||||
Consult the Javadoc for this class for more details on how it works.
|
||||
|
||||
If you want to use LDAP only for authentication, but load the authorities from a difference source (such as a database) then you can provide your own implementation of this interface and inject that instead.
|
||||
|
||||
[[ldap-bean-config]]
|
||||
==== Spring Bean Configuration
|
||||
A typical configuration, using some of the beans we've discussed here, might look like this:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="contextSource"
|
||||
class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
|
||||
<constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/>
|
||||
<property name="userDn" value="cn=manager,dc=springframework,dc=org"/>
|
||||
<property name="password" value="password"/>
|
||||
</bean>
|
||||
|
||||
<bean id="ldapAuthProvider"
|
||||
class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
|
||||
<constructor-arg>
|
||||
<bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
|
||||
<constructor-arg ref="contextSource"/>
|
||||
<property name="userDnPatterns">
|
||||
<list><value>uid={0},ou=people</value></list>
|
||||
</property>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean
|
||||
class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
|
||||
<constructor-arg ref="contextSource"/>
|
||||
<constructor-arg value="ou=groups"/>
|
||||
<property name="groupRoleAttribute" value="ou"/>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
----
|
||||
|
||||
This would set up the provider to access an LDAP server with URL `ldap://monkeymachine:389/dc=springframework,dc=org`.
|
||||
Authentication will be performed by attempting to bind with the DN `uid=<user-login-name>,ou=people,dc=springframework,dc=org`.
|
||||
After successful authentication, roles will be assigned to the user by searching under the DN `ou=groups,dc=springframework,dc=org` with the default filter `(member=<user's-DN>)`.
|
||||
The role name will be taken from the "ou" attribute of each match.
|
||||
|
||||
To configure a user search object, which uses the filter `(uid=<user-login-name>)` for use instead of the DN-pattern (or in addition to it), you would configure the following bean
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
<bean id="userSearch"
|
||||
class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
|
||||
<constructor-arg index="0" value=""/>
|
||||
<constructor-arg index="1" value="(uid={0})"/>
|
||||
<constructor-arg index="2" ref="contextSource" />
|
||||
</bean>
|
||||
----
|
||||
|
||||
and use it by setting the `BindAuthenticator` bean's `userSearch` property.
|
||||
The authenticator would then call the search object to obtain the correct user's DN before attempting to bind as this user.
|
||||
|
||||
|
||||
[[ldap-custom-user-details]]
|
||||
==== LDAP Attributes and Customized UserDetails
|
||||
The net result of an authentication using `LdapAuthenticationProvider` is the same as a normal Spring Security authentication using the standard `UserDetailsService` interface.
|
||||
A `UserDetails` object is created and stored in the returned `Authentication` object.
|
||||
As with using a `UserDetailsService`, a common requirement is to be able to customize this implementation and add extra properties.
|
||||
When using LDAP, these will normally be attributes from the user entry.
|
||||
The creation of the `UserDetails` object is controlled by the provider's `UserDetailsContextMapper` strategy, which is responsible for mapping user objects to and from LDAP context data:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public interface UserDetailsContextMapper {
|
||||
|
||||
UserDetails mapUserFromContext(DirContextOperations ctx, String username,
|
||||
Collection<GrantedAuthority> authorities);
|
||||
|
||||
void mapUserToContext(UserDetails user, DirContextAdapter ctx);
|
||||
}
|
||||
----
|
||||
|
||||
Only the first method is relevant for authentication.
|
||||
If you provide an implementation of this interface and inject it into the `LdapAuthenticationProvider`, you have control over exactly how the UserDetails object is created.
|
||||
The first parameter is an instance of Spring LDAP's `DirContextOperations` which gives you access to the LDAP attributes which were loaded during authentication.
|
||||
The `username` parameter is the name used to authenticate and the final parameter is the collection of authorities loaded for the user by the configured `LdapAuthoritiesPopulator`.
|
||||
|
||||
The way the context data is loaded varies slightly depending on the type of authentication you are using.
|
||||
With the `BindAuthenticator`, the context returned from the bind operation will be used to read the attributes, otherwise the data will be read using the standard context obtained from the configured `ContextSource` (when a search is configured to locate the user, this will be the data returned by the search object).
|
||||
|
||||
|
||||
[[ldap-active-directory]]
|
||||
=== Active Directory Authentication
|
||||
Active Directory supports its own non-standard authentication options, and the normal usage pattern doesn't fit too cleanly with the standard `LdapAuthenticationProvider`.
|
||||
Typically authentication is performed using the domain username (in the form `user@domain`), rather than using an LDAP distinguished name.
|
||||
To make this easier, Spring Security 3.1 has an authentication provider which is customized for a typical Active Directory setup.
|
||||
|
||||
|
||||
==== ActiveDirectoryLdapAuthenticationProvider
|
||||
Configuring `ActiveDirectoryLdapAuthenticationProvider` is quite straightforward.
|
||||
You just need to supply the domain name and an LDAP URL supplying the address of the server footnote:[It is also possible to obtain the server's IP address using a DNS lookup.
|
||||
This is not currently supported, but hopefully will be in a future version.].
|
||||
An example configuration would then look like this:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
<bean id="adAuthenticationProvider"
|
||||
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
|
||||
<constructor-arg value="mydomain.com" />
|
||||
<constructor-arg value="ldap://adserver.mydomain.com/" />
|
||||
</bean>
|
||||
}
|
||||
----
|
||||
|
||||
Note that there is no need to specify a separate `ContextSource` in order to define the server location - the bean is completely self-contained.
|
||||
A user named "Sharon", for example, would then be able to authenticate by entering either the username `sharon` or the full Active Directory `userPrincipalName`, namely `sharon@mydomain.com`.
|
||||
The user's directory entry will then be located, and the attributes returned for possible use in customizing the created `UserDetails` object (a `UserDetailsContextMapper` can be injected for this purpose, as described above).
|
||||
All interaction with the directory takes place with the identity of the user themselves.
|
||||
There is no concept of a "manager" user.
|
||||
|
||||
By default, the user authorities are obtained from the `memberOf` attribute values of the user entry.
|
||||
The authorities allocated to the user can again be customized using a `UserDetailsContextMapper`.
|
||||
You can also inject a `GrantedAuthoritiesMapper` into the provider instance to control the authorities which end up in the `Authentication` object.
|
||||
|
||||
===== Active Directory Error Codes
|
||||
By default, a failed result will cause a standard Spring Security `BadCredentialsException`.
|
||||
If you set the property `convertSubErrorCodesToExceptions` to `true`, the exception messages will be parsed to attempt to extract the Active Directory-specific error code and raise a more specific exception.
|
||||
Check the class Javadoc for more information.
|
|
@ -0,0 +1,376 @@
|
|||
|
||||
|
||||
[[mvc]]
|
||||
== Spring MVC Integration
|
||||
|
||||
Spring Security provides a number of optional integrations with Spring MVC.
|
||||
This section covers the integration in further detail.
|
||||
|
||||
[[mvc-enablewebmvcsecurity]]
|
||||
=== @EnableWebMvcSecurity
|
||||
|
||||
NOTE: As of Spring Security 4.0, `@EnableWebMvcSecurity` is deprecated.
|
||||
The replacement is `@EnableWebSecurity` which will determine adding the Spring MVC features based upon the classpath.
|
||||
|
||||
To enable Spring Security integration with Spring MVC add the `@EnableWebSecurity` annotation to your configuration.
|
||||
|
||||
NOTE: Spring Security provides the configuration using Spring MVC's https://docs.spring.io/spring/docs/5.0.0.RELEASE/spring-framework-reference/web.html#mvc-config-customize[WebMvcConfigurer].
|
||||
This means that if you are using more advanced options, like integrating with `WebMvcConfigurationSupport` directly, then you will need to manually provide the Spring Security configuration.
|
||||
|
||||
[[mvc-requestmatcher]]
|
||||
=== MvcRequestMatcher
|
||||
|
||||
Spring Security provides deep integration with how Spring MVC matches on URLs with `MvcRequestMatcher`.
|
||||
This is helpful to ensure your Security rules match the logic used to handle your requests.
|
||||
|
||||
In order to use `MvcRequestMatcher` you must place the Spring Security Configuration in the same `ApplicationContext` as your `DispatcherServlet`.
|
||||
This is necessary because Spring Security's `MvcRequestMatcher` expects a `HandlerMappingIntrospector` bean with the name of `mvcHandlerMappingIntrospector` to be registered by your Spring MVC configuration that is used to perform the matching.
|
||||
|
||||
For a `web.xml` this means that you should place your configuration in the `DispatcherServlet.xml`.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<!-- All Spring Configuration (both MVC and Security) are in /WEB-INF/spring/ -->
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>/WEB-INF/spring/*.xml</param-value>
|
||||
</context-param>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>spring</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
<!-- Load from the ContextLoaderListener -->
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value></param-value>
|
||||
</init-param>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>spring</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
----
|
||||
|
||||
Below `WebSecurityConfiguration` in placed in the ``DispatcherServlet``s `ApplicationContext`.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class SecurityInitializer extends
|
||||
AbstractAnnotationConfigDispatcherServletInitializer {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getRootConfigClasses() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getServletConfigClasses() {
|
||||
return new Class[] { RootConfiguration.class,
|
||||
WebMvcConfiguration.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getServletMappings() {
|
||||
return new String[] { "/" };
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
It is always recommended to provide authorization rules by matching on the `HttpServletRequest` and method security.
|
||||
|
||||
Providing authorization rules by matching on `HttpServletRequest` is good because it happens very early in the code path and helps reduce the https://en.wikipedia.org/wiki/Attack_surface[attack surface].
|
||||
Method security ensures that if someone has bypassed the web authorization rules, that your application is still secured.
|
||||
This is what is known as https://en.wikipedia.org/wiki/Defense_in_depth_(computing)[Defence in Depth]
|
||||
====
|
||||
|
||||
Consider a controller that is mapped as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RequestMapping("/admin")
|
||||
public String admin() {
|
||||
----
|
||||
|
||||
If we wanted to restrict access to this controller method to admin users, a developer can provide authorization rules by matching on the `HttpServletRequest` with the following:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
protected configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.antMatchers("/admin").hasRole("ADMIN");
|
||||
}
|
||||
----
|
||||
|
||||
or in XML
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
|
||||
</http>
|
||||
----
|
||||
|
||||
With either configuration, the URL `/admin` will require the authenticated user to be an admin user.
|
||||
However, depending on our Spring MVC configuration, the URL `/admin.html` will also map to our `admin()` method.
|
||||
Additionally, depending on our Spring MVC configuration, the URL `/admin/` will also map to our `admin()` method.
|
||||
|
||||
The problem is that our security rule is only protecting `/admin`.
|
||||
We could add additional rules for all the permutations of Spring MVC, but this would be quite verbose and tedious.
|
||||
|
||||
Instead, we can leverage Spring Security's `MvcRequestMatcher`.
|
||||
The following configuration will protect the same URLs that Spring MVC will match on by using Spring MVC to match on the URL.
|
||||
|
||||
|
||||
[source,java]
|
||||
----
|
||||
protected configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.mvcMatchers("/admin").hasRole("ADMIN");
|
||||
}
|
||||
----
|
||||
|
||||
or in XML
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<http request-matcher="mvc">
|
||||
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
|
||||
</http>
|
||||
----
|
||||
|
||||
[[mvc-authentication-principal]]
|
||||
=== @AuthenticationPrincipal
|
||||
|
||||
Spring Security provides `AuthenticationPrincipalArgumentResolver` which can automatically resolve the current `Authentication.getPrincipal()` for Spring MVC arguments.
|
||||
By using `@EnableWebSecurity` you will automatically have this added to your Spring MVC configuration.
|
||||
If you use XML based configuration, you must add this yourself.
|
||||
For example:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<mvc:annotation-driven>
|
||||
<mvc:argument-resolvers>
|
||||
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
|
||||
</mvc:argument-resolvers>
|
||||
</mvc:annotation-driven>
|
||||
----
|
||||
|
||||
Once `AuthenticationPrincipalArgumentResolver` is properly configured, you can be entirely decoupled from Spring Security in your Spring MVC layer.
|
||||
|
||||
Consider a situation where a custom `UserDetailsService` that returns an `Object` that implements `UserDetails` and your own `CustomUser` `Object`. The `CustomUser` of the currently authenticated user could be accessed using the following code:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RequestMapping("/messages/inbox")
|
||||
public ModelAndView findMessagesForUser() {
|
||||
Authentication authentication =
|
||||
SecurityContextHolder.getContext().getAuthentication();
|
||||
CustomUser custom = (CustomUser) authentication == null ? null : authentication.getPrincipal();
|
||||
|
||||
// .. find messages for this user and return them ...
|
||||
}
|
||||
----
|
||||
|
||||
As of Spring Security 3.2 we can resolve the argument more directly by adding an annotation. For example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
|
||||
// ...
|
||||
|
||||
@RequestMapping("/messages/inbox")
|
||||
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {
|
||||
|
||||
// .. find messages for this user and return them ...
|
||||
}
|
||||
----
|
||||
|
||||
Sometimes it may be necessary to transform the principal in some way.
|
||||
For example, if `CustomUser` needed to be final it could not be extended.
|
||||
In this situation the `UserDetailsService` might returns an `Object` that implements `UserDetails` and provides a method named `getCustomUser` to access `CustomUser`.
|
||||
For example, it might look like:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class CustomUserUserDetails extends User {
|
||||
// ...
|
||||
public CustomUser getCustomUser() {
|
||||
return customUser;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
We could then access the `CustomUser` using a https://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html[SpEL expression] that uses `Authentication.getPrincipal()` as the root object:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
|
||||
// ...
|
||||
|
||||
@RequestMapping("/messages/inbox")
|
||||
public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") CustomUser customUser) {
|
||||
|
||||
// .. find messags for this user and return them ...
|
||||
}
|
||||
----
|
||||
|
||||
We can also refer to Beans in our SpEL expressions.
|
||||
For example, the following could be used if we were using JPA to manage our Users and we wanted to modify and save a property on the current user.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
|
||||
// ...
|
||||
|
||||
@PutMapping("/users/self")
|
||||
public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") CustomUser attachedCustomUser,
|
||||
@RequestParam String firstName) {
|
||||
|
||||
// change the firstName on an attached instance which will be persisted to the database
|
||||
attachedCustomUser.setFirstName(firstName);
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
We can further remove our dependency on Spring Security by making `@AuthenticationPrincipal` a meta annotation on our own annotation.
|
||||
Below we demonstrate how we could do this on an annotation named `@CurrentUser`.
|
||||
|
||||
NOTE: It is important to realize that in order to remove the dependency on Spring Security, it is the consuming application that would create `@CurrentUser`.
|
||||
This step is not strictly required, but assists in isolating your dependency to Spring Security to a more central location.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Target({ElementType.PARAMETER, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@AuthenticationPrincipal
|
||||
public @interface CurrentUser {}
|
||||
----
|
||||
|
||||
Now that `@CurrentUser` has been specified, we can use it to signal to resolve our `CustomUser` of the currently authenticated user.
|
||||
We have also isolated our dependency on Spring Security to a single file.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RequestMapping("/messages/inbox")
|
||||
public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) {
|
||||
|
||||
// .. find messages for this user and return them ...
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[mvc-async]]
|
||||
=== Spring MVC Async Integration
|
||||
|
||||
Spring Web MVC 3.2+ has excellent support for http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-ann-async[Asynchronous Request Processing].
|
||||
With no additional configuration, Spring Security will automatically setup the `SecurityContext` to the `Thread` that executes a `Callable` returned by your controllers.
|
||||
For example, the following method will automatically have its `Callable` executed with the `SecurityContext` that was available when the `Callable` was created:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RequestMapping(method=RequestMethod.POST)
|
||||
public Callable<String> processUpload(final MultipartFile file) {
|
||||
|
||||
return new Callable<String>() {
|
||||
public Object call() throws Exception {
|
||||
// ...
|
||||
return "someView";
|
||||
}
|
||||
};
|
||||
}
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
.Associating SecurityContext to Callable's
|
||||
====
|
||||
More technically speaking, Spring Security integrates with `WebAsyncManager`.
|
||||
The `SecurityContext` that is used to process the `Callable` is the `SecurityContext` that exists on the `SecurityContextHolder` at the time `startCallableProcessing` is invoked.
|
||||
====
|
||||
|
||||
There is no automatic integration with a `DeferredResult` that is returned by controllers.
|
||||
This is because `DeferredResult` is processed by the users and thus there is no way of automatically integrating with it.
|
||||
However, you can still use <<concurrency,Concurrency Support>> to provide transparent integration with Spring Security.
|
||||
|
||||
[[mvc-csrf]]
|
||||
=== Spring MVC and CSRF Integration
|
||||
|
||||
==== Automatic Token Inclusion
|
||||
|
||||
Spring Security will automatically <<csrf-include-csrf-token,include the CSRF Token>> within forms that use the http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/view.html#view-jsp-formtaglib-formtag[Spring MVC form tag].
|
||||
For example, the following JSP:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
|
||||
xmlns:c="http://java.sun.com/jsp/jstl/core"
|
||||
xmlns:form="http://www.springframework.org/tags/form" version="2.0">
|
||||
<jsp:directive.page language="java" contentType="text/html" />
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
<!-- ... -->
|
||||
|
||||
<c:url var="logoutUrl" value="/logout"/>
|
||||
<form:form action="${logoutUrl}"
|
||||
method="post">
|
||||
<input type="submit"
|
||||
value="Log out" />
|
||||
<input type="hidden"
|
||||
name="${_csrf.parameterName}"
|
||||
value="${_csrf.token}"/>
|
||||
</form:form>
|
||||
|
||||
<!-- ... -->
|
||||
</html>
|
||||
</jsp:root>
|
||||
----
|
||||
|
||||
Will output HTML that is similar to the following:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<!-- ... -->
|
||||
|
||||
<form action="/context/logout" method="post">
|
||||
<input type="submit" value="Log out"/>
|
||||
<input type="hidden" name="_csrf" value="f81d4fae-7dec-11d0-a765-00a0c91e6bf6"/>
|
||||
</form>
|
||||
|
||||
<!-- ... -->
|
||||
----
|
||||
|
||||
[[mvc-csrf-resolver]]
|
||||
==== Resolving the CsrfToken
|
||||
|
||||
Spring Security provides `CsrfTokenArgumentResolver` which can automatically resolve the current `CsrfToken` for Spring MVC arguments.
|
||||
By using <<jc-hello-wsca,@EnableWebSecurity>> you will automatically have this added to your Spring MVC configuration.
|
||||
If you use XML based configuraiton, you must add this yourself.
|
||||
|
||||
Once `CsrfTokenArgumentResolver` is properly configured, you can expose the `CsrfToken` to your static HTML based application.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RestController
|
||||
public class CsrfController {
|
||||
|
||||
@RequestMapping("/csrf")
|
||||
public CsrfToken csrf(CsrfToken token) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
It is important to keep the `CsrfToken` a secret from other domains.
|
||||
This means if you are using https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS[Cross Origin Sharing (CORS)], you should **NOT** expose the `CsrfToken` to any external domains.
|
|
@ -0,0 +1,596 @@
|
|||
|
||||
|
||||
[[oauth2login-advanced]]
|
||||
== OAuth 2.0 Login -- Advanced Configuration
|
||||
|
||||
`HttpSecurity.oauth2Login()` provides a number of configuration options for customizing OAuth 2.0 Login.
|
||||
The main configuration options are grouped into their protocol endpoint counterparts.
|
||||
|
||||
For example, `oauth2Login().authorizationEndpoint()` allows configuring the _Authorization Endpoint_,
|
||||
whereas `oauth2Login().tokenEndpoint()` allows configuring the _Token Endpoint_.
|
||||
|
||||
The following code shows an example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.authorizationEndpoint()
|
||||
...
|
||||
.redirectionEndpoint()
|
||||
...
|
||||
.tokenEndpoint()
|
||||
...
|
||||
.userInfoEndpoint()
|
||||
...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The main goal of the `oauth2Login()` DSL was to closely align with the naming, as defined in the specifications.
|
||||
|
||||
The OAuth 2.0 Authorization Framework defines the https://tools.ietf.org/html/rfc6749#section-3[Protocol Endpoints] as follows:
|
||||
|
||||
The authorization process utilizes two authorization server endpoints (HTTP resources):
|
||||
|
||||
* Authorization Endpoint: Used by the client to obtain authorization from the resource owner via user-agent redirection.
|
||||
* Token Endpoint: Used by the client to exchange an authorization grant for an access token, typically with client authentication.
|
||||
|
||||
As well as one client endpoint:
|
||||
|
||||
* Redirection Endpoint: Used by the authorization server to return responses
|
||||
containing authorization credentials to the client via the resource owner user-agent.
|
||||
|
||||
The OpenID Connect Core 1.0 specification defines the http://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] as follows:
|
||||
|
||||
The UserInfo Endpoint is an OAuth 2.0 Protected Resource that returns claims about the authenticated end-user.
|
||||
To obtain the requested claims about the end-user, the client makes a request to the UserInfo Endpoint
|
||||
by using an access token obtained through OpenID Connect Authentication.
|
||||
These claims are normally represented by a JSON object that contains a collection of name-value pairs for the claims.
|
||||
|
||||
The following code shows the complete configuration options available for the `oauth2Login()` DSL:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.clientRegistrationRepository(this.clientRegistrationRepository())
|
||||
.authorizedClientService(this.authorizedClientService())
|
||||
.loginPage("/login")
|
||||
.authorizationEndpoint()
|
||||
.baseUri(this.authorizationRequestBaseUri())
|
||||
.authorizationRequestRepository(this.authorizationRequestRepository())
|
||||
.and()
|
||||
.redirectionEndpoint()
|
||||
.baseUri(this.authorizationResponseBaseUri())
|
||||
.and()
|
||||
.tokenEndpoint()
|
||||
.accessTokenResponseClient(this.accessTokenResponseClient())
|
||||
.and()
|
||||
.userInfoEndpoint()
|
||||
.userAuthoritiesMapper(this.userAuthoritiesMapper())
|
||||
.userService(this.oauth2UserService())
|
||||
.oidcUserService(this.oidcUserService())
|
||||
.customUserType(GitHubOAuth2User.class, "github");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The sections to follow go into more detail on each of the configuration options available:
|
||||
|
||||
* <<oauth2login-advanced-login-page>>
|
||||
* <<oauth2login-advanced-authorization-endpoint>>
|
||||
* <<oauth2login-advanced-redirection-endpoint>>
|
||||
* <<oauth2login-advanced-token-endpoint>>
|
||||
* <<oauth2login-advanced-userinfo-endpoint>>
|
||||
|
||||
|
||||
[[oauth2login-advanced-login-page]]
|
||||
=== OAuth 2.0 Login Page
|
||||
|
||||
By default, the OAuth 2.0 Login Page is auto-generated by the `DefaultLoginPageGeneratingFilter`.
|
||||
The default login page shows each configured OAuth Client with its `ClientRegistration.clientName`
|
||||
as a link, which is capable of initiating the Authorization Request (or OAuth 2.0 Login).
|
||||
|
||||
The link's destination for each OAuth Client defaults to the following:
|
||||
|
||||
`OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI` + "/{registrationId}"
|
||||
|
||||
The following line shows an example:
|
||||
|
||||
[source,html]
|
||||
----
|
||||
<a href="/oauth2/authorization/google">Google</a>
|
||||
----
|
||||
|
||||
To override the default login page,
|
||||
configure `oauth2Login().loginPage()` and (optionally) `oauth2Login().authorizationEndpoint().baseUri()`.
|
||||
|
||||
The following listing shows an example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.loginPage("/login/oauth2")
|
||||
...
|
||||
.authorizationEndpoint()
|
||||
.baseUri("/login/oauth2/authorization")
|
||||
....
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[IMPORTANT]
|
||||
You need to provide a `@Controller` with a `@RequestMapping("/login/oauth2")` that is capable of rendering the custom login page.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
As noted earlier, configuring `oauth2Login().authorizationEndpoint().baseUri()` is optional.
|
||||
However, if you choose to customize it, ensure the link to each OAuth Client matches the `authorizationEndpoint().baseUri()`.
|
||||
|
||||
The following line shows an example:
|
||||
|
||||
[source,html]
|
||||
----
|
||||
<a href="/login/oauth2/authorization/google">Google</a>
|
||||
----
|
||||
====
|
||||
|
||||
[[oauth2login-advanced-authorization-endpoint]]
|
||||
=== Authorization Endpoint
|
||||
|
||||
|
||||
[[oauth2login-advanced-authorization-request-repository]]
|
||||
==== `AuthorizationRequestRepository`
|
||||
|
||||
`AuthorizationRequestRepository` is responsible for the persistence of the `OAuth2AuthorizationRequest`
|
||||
from the time the Authorization Request is initiated to the time the Authorization Response
|
||||
is received (the callback).
|
||||
|
||||
[TIP]
|
||||
The `OAuth2AuthorizationRequest` is used to correlate and validate the Authorization Response.
|
||||
|
||||
The default implementation of `AuthorizationRequestRepository` is `HttpSessionOAuth2AuthorizationRequestRepository`,
|
||||
which stores the `OAuth2AuthorizationRequest` in the `HttpSession`.
|
||||
|
||||
If you would like to provide a custom implementation of `AuthorizationRequestRepository`
|
||||
that stores the attributes of `OAuth2AuthorizationRequest` in a `Cookie`,
|
||||
configure it as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.authorizationEndpoint()
|
||||
.authorizationRequestRepository(this.cookieAuthorizationRequestRepository())
|
||||
...
|
||||
}
|
||||
|
||||
private AuthorizationRequestRepository<OAuth2AuthorizationRequest> cookieAuthorizationRequestRepository() {
|
||||
return new HttpCookieOAuth2AuthorizationRequestRepository();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[oauth2login-advanced-redirection-endpoint]]
|
||||
=== Redirection Endpoint
|
||||
|
||||
The Redirection Endpoint is used by the Authorization Server for returning the Authorization Response
|
||||
(which contains the authorization credentials) to the client via the Resource Owner user-agent.
|
||||
|
||||
[TIP]
|
||||
OAuth 2.0 Login leverages the Authorization Code Grant.
|
||||
Therefore, the authorization credential is the authorization code.
|
||||
|
||||
The default Authorization Response `baseUri` (redirection endpoint) is `*/login/oauth2/code/**`, which is defined in `OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI`.
|
||||
|
||||
If you would like to customize the Authorization Response `baseUri`, configure it as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.redirectionEndpoint()
|
||||
.baseUri("/login/oauth2/callback/*")
|
||||
....
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
You also need to ensure the `ClientRegistration.redirectUriTemplate` matches the custom Authorization Response `baseUri`.
|
||||
|
||||
The following listing shows an example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
||||
.clientId("google-client-id")
|
||||
.clientSecret("google-client-secret")
|
||||
.redirectUriTemplate("{baseUrl}/login/oauth2/callback/{registrationId}")
|
||||
.build();
|
||||
----
|
||||
====
|
||||
|
||||
[[oauth2login-advanced-token-endpoint]]
|
||||
=== Token Endpoint
|
||||
|
||||
|
||||
[[oauth2login-advanced-token-client]]
|
||||
==== OAuth2AccessTokenResponseClient
|
||||
|
||||
`OAuth2AccessTokenResponseClient` is responsible for exchanging an authorization grant credential
|
||||
for an access token credential at the Authorization Server's Token Endpoint.
|
||||
|
||||
The default implementation of `OAuth2AccessTokenResponseClient` is `NimbusAuthorizationCodeTokenResponseClient`,
|
||||
which exchanges an authorization code for an access token at the Token Endpoint.
|
||||
|
||||
[NOTE]
|
||||
`NimbusAuthorizationCodeTokenResponseClient` uses the https://connect2id.com/products/nimbus-oauth-openid-connect-sdk[Nimbus OAuth 2.0 SDK] internally.
|
||||
|
||||
If you would like to provide a custom implementation of `OAuth2AccessTokenResponseClient`
|
||||
that uses Spring Framework 5 reactive `WebClient` for initiating requests to the Token Endpoint,
|
||||
configure it as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.tokenEndpoint()
|
||||
.accessTokenResponseClient(this.accessTokenResponseClient())
|
||||
...
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
|
||||
return new SpringWebClientAuthorizationCodeTokenResponseClient();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[oauth2login-advanced-userinfo-endpoint]]
|
||||
=== UserInfo Endpoint
|
||||
|
||||
The UserInfo Endpoint includes a number of configuration options, as described in the following sub-sections:
|
||||
|
||||
* <<oauth2login-advanced-map-authorities>>
|
||||
* <<oauth2login-advanced-custom-user>>
|
||||
* <<oauth2login-advanced-oauth2-user-service>>
|
||||
* <<oauth2login-advanced-oidc-user-service>>
|
||||
|
||||
|
||||
[[oauth2login-advanced-map-authorities]]
|
||||
==== Mapping User Authorities
|
||||
|
||||
After the user successfully authenticates with the OAuth 2.0 Provider,
|
||||
the `OAuth2User.getAuthorities()` (or `OidcUser.getAuthorities()`) may be mapped to a new set of `GrantedAuthority` instances,
|
||||
which will be supplied to `OAuth2AuthenticationToken` when completing the authentication.
|
||||
|
||||
[TIP]
|
||||
`OAuth2AuthenticationToken.getAuthorities()` is used for authorizing requests, such as in `hasRole('USER')` or `hasRole('ADMIN')`.
|
||||
|
||||
There are a couple of options to choose from when mapping user authorities:
|
||||
|
||||
* <<oauth2login-advanced-map-authorities-grantedauthoritiesmapper,Using a `GrantedAuthoritiesMapper`>>
|
||||
* <<oauth2login-advanced-map-authorities-oauth2userservice,Delegation-based strategy with `OAuth2UserService`>>
|
||||
|
||||
[[oauth2login-advanced-map-authorities-grantedauthoritiesmapper]]
|
||||
===== Using a `GrantedAuthoritiesMapper`
|
||||
|
||||
Provide an implementation of `GrantedAuthoritiesMapper` and configure it as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.userInfoEndpoint()
|
||||
.userAuthoritiesMapper(this.userAuthoritiesMapper())
|
||||
...
|
||||
}
|
||||
|
||||
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||
return (authorities) -> {
|
||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||
|
||||
authorities.forEach(authority -> {
|
||||
if (OidcUserAuthority.class.isInstance(authority)) {
|
||||
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
|
||||
|
||||
OidcIdToken idToken = oidcUserAuthority.getIdToken();
|
||||
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
|
||||
|
||||
// Map the claims found in idToken and/or userInfo
|
||||
// to one or more GrantedAuthority's and add it to mappedAuthorities
|
||||
|
||||
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
|
||||
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
|
||||
|
||||
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
|
||||
|
||||
// Map the attributes found in userAttributes
|
||||
// to one or more GrantedAuthority's and add it to mappedAuthorities
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return mappedAuthorities;
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Alternatively, you may register a `GrantedAuthoritiesMapper` `@Bean` to have it automatically applied to the configuration, as shown in the following example:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.oauth2Login();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||
...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[oauth2login-advanced-map-authorities-oauth2userservice]]
|
||||
===== Delegation-based strategy with `OAuth2UserService`
|
||||
|
||||
This strategy is advanced compared to using a `GrantedAuthoritiesMapper`, however, it's also more flexible
|
||||
as it gives you access to the `OAuth2UserRequest` and `OAuth2User` (when using an OAuth 2.0 UserService)
|
||||
or `OidcUserRequest` and `OidcUser` (when using an OpenID Connect 1.0 UserService).
|
||||
|
||||
The `OAuth2UserRequest` (and `OidcUserRequest`) provides you access to the associated `OAuth2AccessToken`,
|
||||
which is very useful in the cases where the _delegator_ needs to fetch authority information
|
||||
from a protected resource before it can map the custom authorities for the user.
|
||||
|
||||
The following example shows how to implement and configure a delegation-based strategy using an OpenID Connect 1.0 UserService:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.userInfoEndpoint()
|
||||
.oidcUserService(this.oidcUserService())
|
||||
...
|
||||
}
|
||||
|
||||
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
|
||||
final OidcUserService delegate = new OidcUserService();
|
||||
|
||||
return (userRequest) -> {
|
||||
// Delegate to the default implementation for loading a user
|
||||
OidcUser oidcUser = delegate.loadUser(userRequest);
|
||||
|
||||
OAuth2AccessToken accessToken = userRequest.getAccessToken();
|
||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||
|
||||
// TODO
|
||||
// 1) Fetch the authority information from the protected resource using accessToken
|
||||
// 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
|
||||
|
||||
// 3) Create a copy of oidcUser but use the mappedAuthorities instead
|
||||
oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
|
||||
|
||||
return oidcUser;
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[oauth2login-advanced-custom-user]]
|
||||
==== Configuring a Custom OAuth2User
|
||||
|
||||
`CustomUserTypesOAuth2UserService` is an implementation of an `OAuth2UserService`
|
||||
that provides support for custom `OAuth2User` types.
|
||||
|
||||
If the default implementation (`DefaultOAuth2User`) does not suit your needs,
|
||||
you can define your own implementation of `OAuth2User`.
|
||||
|
||||
The following code demonstrates how you would register a custom `OAuth2User` type for GitHub:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.userInfoEndpoint()
|
||||
.customUserType(GitHubOAuth2User.class, "github")
|
||||
...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The following code shows an example of a custom `OAuth2User` type for GitHub:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class GitHubOAuth2User implements OAuth2User {
|
||||
private List<GrantedAuthority> authorities =
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER");
|
||||
private Map<String, Object> attributes;
|
||||
private String id;
|
||||
private String name;
|
||||
private String login;
|
||||
private String email;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return this.authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
if (this.attributes == null) {
|
||||
this.attributes = new HashMap<>();
|
||||
this.attributes.put("id", this.getId());
|
||||
this.attributes.put("name", this.getName());
|
||||
this.attributes.put("login", this.getLogin());
|
||||
this.attributes.put("email", this.getEmail());
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return this.login;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return this.email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[TIP]
|
||||
`id`, `name`, `login`, and `email` are attributes returned in GitHub's UserInfo Response.
|
||||
For detailed information returned from the UserInfo Endpoint, see the API documentation
|
||||
for https://developer.github.com/v3/users/#get-the-authenticated-user["Get the authenticated user"].
|
||||
|
||||
[[oauth2login-advanced-oauth2-user-service]]
|
||||
==== OAuth 2.0 UserService
|
||||
|
||||
`DefaultOAuth2UserService` is an implementation of an `OAuth2UserService`
|
||||
that supports standard OAuth 2.0 Provider's.
|
||||
|
||||
[NOTE]
|
||||
`OAuth2UserService` obtains the user attributes
|
||||
of the end-user (the resource owner) from the UserInfo Endpoint (by using the
|
||||
access token granted to the client during the authorization flow)
|
||||
and returns an `AuthenticatedPrincipal` in the form of an `OAuth2User`.
|
||||
|
||||
If the default implementation does not suit your needs, you can define your own implementation of `OAuth2UserService`
|
||||
for standard OAuth 2.0 Provider's.
|
||||
|
||||
The following configuration demonstrates how to configure a custom `OAuth2UserService`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.userInfoEndpoint()
|
||||
.userService(this.oauth2UserService())
|
||||
...
|
||||
}
|
||||
|
||||
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
|
||||
return new CustomOAuth2UserService();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[[oauth2login-advanced-oidc-user-service]]
|
||||
==== OpenID Connect 1.0 UserService
|
||||
|
||||
`OidcUserService` is an implementation of an `OAuth2UserService`
|
||||
that supports OpenID Connect 1.0 Provider's.
|
||||
|
||||
[NOTE]
|
||||
`OAuth2UserService` is responsible for obtaining the user attributes
|
||||
of the end user (the resource owner) from the UserInfo Endpoint (by using the
|
||||
access token granted to the client during the authorization flow)
|
||||
and return an `AuthenticatedPrincipal` in the form of an `OidcUser`.
|
||||
|
||||
If the default implementation does not suit your needs, you can define your own implementation of `OAuth2UserService`
|
||||
for OpenID Connect 1.0 Provider's.
|
||||
|
||||
The following configuration demonstrates how to configure a custom OpenID Connect 1.0 `OAuth2UserService`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity
|
||||
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.oauth2Login()
|
||||
.userInfoEndpoint()
|
||||
.oidcUserService(this.oidcUserService())
|
||||
...
|
||||
}
|
||||
|
||||
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
|
||||
return new CustomOidcUserService();
|
||||
}
|
||||
}
|
||||
----
|
|
@ -0,0 +1,140 @@
|
|||
[[preauth]]
|
||||
== Pre-Authentication Scenarios
|
||||
There are situations where you want to use Spring Security for authorization, but the user has already been reliably authenticated by some external system prior to accessing the application.
|
||||
We refer to these situations as "pre-authenticated" scenarios.
|
||||
Examples include X.509, Siteminder and authentication by the Java EE container in which the application is running.
|
||||
When using pre-authentication, Spring Security has to
|
||||
|
||||
* Identify the user making the request.
|
||||
|
||||
* Obtain the authorities for the user.
|
||||
|
||||
|
||||
The details will depend on the external authentication mechanism.
|
||||
A user might be identified by their certificate information in the case of X.509, or by an HTTP request header in the case of Siteminder.
|
||||
If relying on container authentication, the user will be identified by calling the `getUserPrincipal()` method on the incoming HTTP request.
|
||||
In some cases, the external mechanism may supply role/authority information for the user but in others the authorities must be obtained from a separate source, such as a `UserDetailsService`.
|
||||
|
||||
|
||||
=== Pre-Authentication Framework Classes
|
||||
Because most pre-authentication mechanisms follow the same pattern, Spring Security has a set of classes which provide an internal framework for implementing pre-authenticated authentication providers.
|
||||
This removes duplication and allows new implementations to be added in a structured fashion, without having to write everything from scratch.
|
||||
You don't need to know about these classes if you want to use something like <<x509,X.509 authentication>>, as it already has a namespace configuration option which is simpler to use and get started with.
|
||||
If you need to use explicit bean configuration or are planning on writing your own implementation then an understanding of how the provided implementations work will be useful.
|
||||
You will find classes under the `org.springframework.security.web.authentication.preauth`.
|
||||
We just provide an outline here so you should consult the Javadoc and source where appropriate.
|
||||
|
||||
|
||||
==== AbstractPreAuthenticatedProcessingFilter
|
||||
This class will check the current contents of the security context and, if empty, it will attempt to extract user information from the HTTP request and submit it to the `AuthenticationManager`.
|
||||
Subclasses override the following methods to obtain this information:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);
|
||||
|
||||
protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
|
||||
----
|
||||
|
||||
|
||||
After calling these, the filter will create a `PreAuthenticatedAuthenticationToken` containing the returned data and submit it for authentication.
|
||||
By "authentication" here, we really just mean further processing to perhaps load the user's authorities, but the standard Spring Security authentication architecture is followed.
|
||||
|
||||
Like other Spring Security authentication filters, the pre-authentication filter has an `authenticationDetailsSource` property which by default will create a `WebAuthenticationDetails` object to store additional information such as the session-identifier and originating IP address in the `details` property of the `Authentication` object.
|
||||
In cases where user role information can be obtained from the pre-authentication mechanism, the data is also stored in this property, with the details implementing the `GrantedAuthoritiesContainer` interface.
|
||||
This enables the authentication provider to read the authorities which were externally allocated to the user.
|
||||
We'll look at a concrete example next.
|
||||
|
||||
|
||||
[[j2ee-preauth-details]]
|
||||
===== J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource
|
||||
If the filter is configured with an `authenticationDetailsSource` which is an instance of this class, the authority information is obtained by calling the `isUserInRole(String role)` method for each of a pre-determined set of "mappable roles".
|
||||
The class gets these from a configured `MappableAttributesRetriever`.
|
||||
Possible implementations include hard-coding a list in the application context and reading the role information from the `<security-role>` information in a `web.xml` file.
|
||||
The pre-authentication sample application uses the latter approach.
|
||||
|
||||
There is an additional stage where the roles (or attributes) are mapped to Spring Security `GrantedAuthority` objects using a configured `Attributes2GrantedAuthoritiesMapper`.
|
||||
The default will just add the usual `ROLE_` prefix to the names, but it gives you full control over the behaviour.
|
||||
|
||||
|
||||
==== PreAuthenticatedAuthenticationProvider
|
||||
The pre-authenticated provider has little more to do than load the `UserDetails` object for the user.
|
||||
It does this by delegating to an `AuthenticationUserDetailsService`.
|
||||
The latter is similar to the standard `UserDetailsService` but takes an `Authentication` object rather than just user name:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public interface AuthenticationUserDetailsService {
|
||||
UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException;
|
||||
}
|
||||
----
|
||||
|
||||
This interface may have also other uses but with pre-authentication it allows access to the authorities which were packaged in the `Authentication` object, as we saw in the previous section.
|
||||
The `PreAuthenticatedGrantedAuthoritiesUserDetailsService` class does this.
|
||||
Alternatively, it may delegate to a standard `UserDetailsService` via the `UserDetailsByNameServiceWrapper` implementation.
|
||||
|
||||
==== Http403ForbiddenEntryPoint
|
||||
The `AuthenticationEntryPoint` was discussed in the <<tech-intro-auth-entry-point,technical overview>> chapter.
|
||||
Normally it is responsible for kick-starting the authentication process for an unauthenticated user (when they try to access a protected resource), but in the pre-authenticated case this doesn't apply.
|
||||
You would only configure the `ExceptionTranslationFilter` with an instance of this class if you aren't using pre-authentication in combination with other authentication mechanisms.
|
||||
It will be called if the user is rejected by the `AbstractPreAuthenticatedProcessingFilter` resulting in a null authentication.
|
||||
It always returns a `403`-forbidden response code if called.
|
||||
|
||||
|
||||
=== Concrete Implementations
|
||||
X.509 authentication is covered in its <<x509,own chapter>>.
|
||||
Here we'll look at some classes which provide support for other pre-authenticated scenarios.
|
||||
|
||||
|
||||
==== Request-Header Authentication (Siteminder)
|
||||
An external authentication system may supply information to the application by setting specific headers on the HTTP request.
|
||||
A well-known example of this is Siteminder, which passes the username in a header called `SM_USER`.
|
||||
This mechanism is supported by the class `RequestHeaderAuthenticationFilter` which simply extracts the username from the header.
|
||||
It defaults to using the name `SM_USER` as the header name.
|
||||
See the Javadoc for more details.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
Note that when using a system like this, the framework performs no authentication checks at all and it is __extremely__ important that the external system is configured properly and protects all access to the application.
|
||||
If an attacker is able to forge the headers in their original request without this being detected then they could potentially choose any username they wished.
|
||||
====
|
||||
|
||||
===== Siteminder Example Configuration
|
||||
A typical configuration using this filter would look like this:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<security:http>
|
||||
<!-- Additional http configuration omitted -->
|
||||
<security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
|
||||
</security:http>
|
||||
|
||||
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
|
||||
<property name="principalRequestHeader" value="SM_USER"/>
|
||||
<property name="authenticationManager" ref="authenticationManager" />
|
||||
</bean>
|
||||
|
||||
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
|
||||
<property name="preAuthenticatedUserDetailsService">
|
||||
<bean id="userDetailsServiceWrapper"
|
||||
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
|
||||
<property name="userDetailsService" ref="userDetailsService"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<security:authentication-manager alias="authenticationManager">
|
||||
<security:authentication-provider ref="preauthAuthProvider" />
|
||||
</security:authentication-manager>
|
||||
----
|
||||
|
||||
We've assumed here that the <<ns-config,security namespace>> is being used for configuration.
|
||||
It's also assumed that you have added a `UserDetailsService` (called "userDetailsService") to your configuration to load the user's roles.
|
||||
|
||||
|
||||
==== Java EE Container Authentication
|
||||
The class `J2eePreAuthenticatedProcessingFilter` will extract the username from the `userPrincipal` property of the `HttpServletRequest`.
|
||||
Use of this filter would usually be combined with the use of Java EE roles as described above in <<j2ee-preauth-details>>.
|
||||
|
||||
There is a sample application in the codebase which uses this approach, so get hold of the code from github and have a look at the application context file if you are interested.
|
||||
The code is in the `samples/xml/preauth` directory.
|
|
@ -0,0 +1,67 @@
|
|||
|
||||
[[runas]]
|
||||
== Run-As Authentication Replacement
|
||||
|
||||
[[runas-overview]]
|
||||
=== Overview
|
||||
The `AbstractSecurityInterceptor` is able to temporarily replace the `Authentication` object in the `SecurityContext` and `SecurityContextHolder` during the secure object callback phase.
|
||||
This only occurs if the original `Authentication` object was successfully processed by the `AuthenticationManager` and `AccessDecisionManager`.
|
||||
The `RunAsManager` will indicate the replacement `Authentication` object, if any, that should be used during the `SecurityInterceptorCallback`.
|
||||
|
||||
By temporarily replacing the `Authentication` object during the secure object callback phase, the secured invocation will be able to call other objects which require different authentication and authorization credentials.
|
||||
It will also be able to perform any internal security checks for specific `GrantedAuthority` objects.
|
||||
Because Spring Security provides a number of helper classes that automatically configure remoting protocols based on the contents of the `SecurityContextHolder`, these run-as replacements are particularly useful when calling remote web services
|
||||
|
||||
[[runas-config]]
|
||||
=== Configuration
|
||||
A `RunAsManager` interface is provided by Spring Security:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
Authentication buildRunAs(Authentication authentication, Object object,
|
||||
List<ConfigAttribute> config);
|
||||
|
||||
boolean supports(ConfigAttribute attribute);
|
||||
|
||||
boolean supports(Class clazz);
|
||||
----
|
||||
|
||||
|
||||
|
||||
The first method returns the `Authentication` object that should replace the existing `Authentication` object for the duration of the method invocation.
|
||||
If the method returns `null`, it indicates no replacement should be made.
|
||||
The second method is used by the `AbstractSecurityInterceptor` as part of its startup validation of configuration attributes.
|
||||
The `supports(Class)` method is called by a security interceptor implementation to ensure the configured `RunAsManager` supports the type of secure object that the security interceptor will present.
|
||||
|
||||
One concrete implementation of a `RunAsManager` is provided with Spring Security.
|
||||
The `RunAsManagerImpl` class returns a replacement `RunAsUserToken` if any `ConfigAttribute` starts with `RUN_AS_`.
|
||||
If any such `ConfigAttribute` is found, the replacement `RunAsUserToken` will contain the same principal, credentials and granted authorities as the original `Authentication` object, along with a new `SimpleGrantedAuthority` for each `RUN_AS_` `ConfigAttribute`.
|
||||
Each new `SimpleGrantedAuthority` will be prefixed with `ROLE_`, followed by the `RUN_AS` `ConfigAttribute`.
|
||||
For example, a `RUN_AS_SERVER` will result in the replacement `RunAsUserToken` containing a `ROLE_RUN_AS_SERVER` granted authority.
|
||||
|
||||
The replacement `RunAsUserToken` is just like any other `Authentication` object.
|
||||
It needs to be authenticated by the `AuthenticationManager`, probably via delegation to a suitable `AuthenticationProvider`.
|
||||
The `RunAsImplAuthenticationProvider` performs such authentication.
|
||||
It simply accepts as valid any `RunAsUserToken` presented.
|
||||
|
||||
To ensure malicious code does not create a `RunAsUserToken` and present it for guaranteed acceptance by the `RunAsImplAuthenticationProvider`, the hash of a key is stored in all generated tokens.
|
||||
The `RunAsManagerImpl` and `RunAsImplAuthenticationProvider` is created in the bean context with the same key:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
<bean id="runAsManager"
|
||||
class="org.springframework.security.access.intercept.RunAsManagerImpl">
|
||||
<property name="key" value="my_run_as_password"/>
|
||||
</bean>
|
||||
|
||||
<bean id="runAsAuthenticationProvider"
|
||||
class="org.springframework.security.access.intercept.RunAsImplAuthenticationProvider">
|
||||
<property name="key" value="my_run_as_password"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
|
||||
By using the same key, each `RunAsUserToken` can be validated it was created by an approved `RunAsManagerImpl`.
|
||||
The `RunAsUserToken` is immutable after creation for security reasons
|
|
@ -0,0 +1,79 @@
|
|||
|
||||
[[x509]]
|
||||
== X.509 Authentication
|
||||
|
||||
|
||||
[[x509-overview]]
|
||||
=== Overview
|
||||
The most common use of X.509 certificate authentication is in verifying the identity of a server when using SSL, most commonly when using HTTPS from a browser.
|
||||
The browser will automatically check that the certificate presented by a server has been issued (ie digitally signed) by one of a list of trusted certificate authorities which it maintains.
|
||||
|
||||
You can also use SSL with "mutual authentication"; the server will then request a valid certificate from the client as part of the SSL handshake.
|
||||
The server will authenticate the client by checking that its certificate is signed by an acceptable authority.
|
||||
If a valid certificate has been provided, it can be obtained through the servlet API in an application.
|
||||
Spring Security X.509 module extracts the certificate using a filter.
|
||||
It maps the certificate to an application user and loads that user's set of granted authorities for use with the standard Spring Security infrastructure.
|
||||
|
||||
You should be familiar with using certificates and setting up client authentication for your servlet container before attempting to use it with Spring Security.
|
||||
Most of the work is in creating and installing suitable certificates and keys.
|
||||
For example, if you're using Tomcat then read the instructions here http://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html[http://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html].
|
||||
It's important that you get this working before trying it out with Spring Security
|
||||
|
||||
|
||||
=== Adding X.509 Authentication to Your Web Application
|
||||
Enabling X.509 client authentication is very straightforward.
|
||||
Just add the `<x509/>` element to your http security namespace configuration.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
...
|
||||
<x509 subject-principal-regex="CN=(.*?)," user-service-ref="userService"/>;
|
||||
</http>
|
||||
----
|
||||
|
||||
The element has two optional attributes:
|
||||
|
||||
* `subject-principal-regex`.
|
||||
The regular expression used to extract a username from the certificate's subject name.
|
||||
The default value is shown above.
|
||||
This is the username which will be passed to the `UserDetailsService` to load the authorities for the user.
|
||||
* `user-service-ref`.
|
||||
This is the bean Id of the `UserDetailsService` to be used with X.509.
|
||||
It isn't needed if there is only one defined in your application context.
|
||||
|
||||
The `subject-principal-regex` should contain a single group.
|
||||
For example the default expression "CN=(.*?)," matches the common name field.
|
||||
So if the subject name in the certificate is "CN=Jimi Hendrix, OU=...", this will give a user name of "Jimi Hendrix".
|
||||
The matches are case insensitive.
|
||||
So "emailAddress=(.?)," will match "EMAILADDRESS=jimi@hendrix.org,CN=..." giving a user name "jimi@hendrix.org".
|
||||
If the client presents a certificate and a valid username is successfully extracted, then there should be a valid `Authentication` object in the security context.
|
||||
If no certificate is found, or no corresponding user could be found then the security context will remain empty.
|
||||
This means that you can easily use X.509 authentication with other options such as a form-based login.
|
||||
|
||||
[[x509-ssl-config]]
|
||||
=== Setting up SSL in Tomcat
|
||||
There are some pre-generated certificates in the `samples/certificate` directory in the Spring Security project.
|
||||
You can use these to enable SSL for testing if you don't want to generate your own.
|
||||
The file `server.jks` contains the server certificate, private key and the issuing certificate authority certificate.
|
||||
There are also some client certificate files for the users from the sample applications.
|
||||
You can install these in your browser to enable SSL client authentication.
|
||||
|
||||
To run tomcat with SSL support, drop the `server.jks` file into the tomcat `conf` directory and add the following connector to the `server.xml` file
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" scheme="https" secure="true"
|
||||
clientAuth="true" sslProtocol="TLS"
|
||||
keystoreFile="${catalina.home}/conf/server.jks"
|
||||
keystoreType="JKS" keystorePass="password"
|
||||
truststoreFile="${catalina.home}/conf/server.jks"
|
||||
truststoreType="JKS" truststorePass="password"
|
||||
/>
|
||||
|
||||
----
|
||||
|
||||
`clientAuth` can also be set to `want` if you still want SSL connections to succeed even if the client doesn't provide a certificate.
|
||||
Clients which don't present a certificate won't be able to access any objects secured by Spring Security unless you use a non-X.509 authentication mechanism, such as form authentication.
|
||||
|
|
@ -16,7 +16,7 @@ include::{include-dir}/web/index.adoc[]
|
|||
|
||||
include::{include-dir}/authorization/index.adoc[]
|
||||
|
||||
include::{include-dir}/additional-topics.adoc[]
|
||||
include::{include-dir}/additional-topics/index.adoc[]
|
||||
|
||||
include::{include-dir}/data.adoc[]
|
||||
|
||||
|
|
Loading…
Reference in New Issue