Start Servlet Authentication Cleanup

Issue gh-7628
This commit is contained in:
Rob Winch 2019-12-06 10:39:55 -06:00
parent 4d9cee116c
commit 9c991a5430
23 changed files with 711 additions and 768 deletions

View File

@ -1,7 +1,12 @@
[[authentication]]
= Authentication
Spring Security provides comprehensive support for https://en.wikipedia.org/wiki/Authentication[authentication].
Authentication how we verify the identity of who is trying to access a particular resource.
Authentication is how we verify the identity of who is trying to access a particular resource.
A common way to authenticate users is by requiring the user to enter a username and password.
Once authentication is performed we know the identity and can perform <<authorization>>.
Once authentication is performed we know the identity and can perform authorization.
// FIXME: Link authorization to authorization
include::supported.adoc[leveloffset=+1]
include::password-storage.adoc[leveloffset=+1]

View File

@ -1,8 +1,5 @@
[[authentication-password-storage]]
= Password Storage
:toc: macro
toc::[]
Spring Security's `PasswordEncoder` interface is used to perform a one way transformation of a password to allow the password to be stored securely.
Given `PasswordEncoder` is a one way transformation, it is not intended when the password transformation needs to be two way (i.e. storing credentials used to authenticate to a database).
@ -248,30 +245,8 @@ java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the i
The easiest way to resolve the error is to switch to explicitly provide the `PasswordEncoder` that you passwords are encoded with.
The easiest way to resolve it is to figure out how your passwords are currently being stored and explicitly provide the correct `PasswordEncoder`.
If you are migrating from Spring Security 4.2.x you can revert to the previous behavior by exposing a `NoOpPasswordEncoder` bean.
For example, if you are using Java Configuration, you can create a configuration that looks like:
[WARNING]
====
Reverting to `NoOpPasswordEncoder` is not considered to be secure.
You should instead migrate to using `DelegatingPasswordEncoder` to support secure password encoding.
====
[source,java]
----
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
----
if you are using XML configuration, you can expose a `PasswordEncoder` with the id `passwordEncoder`:
[source,xml]
----
<b:bean id="passwordEncoder"
class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
----
If you are migrating from Spring Security 4.2.x you can revert to the previous behavior by <<authentication-password-storage-configuration,exposing a `NoOpPasswordEncoder` bean>>.
Alternatively, you can prefix all of your passwords with the correct id and continue to use `DelegatingPasswordEncoder`.
For example, if you are using BCrypt, you would migrate your password from something like:
@ -362,31 +337,37 @@ However, there are no plans to remove them since it is difficult to migrate exis
[[authentication-password-storage-configuration]]
== Password Storage Configuration
Passwords should always be encoded using a secure hashing algorithm designed for the purpose (not a standard algorithm like SHA or MD5).
Spring Security uses <<authentication-password-storage-dpe>> by default.
However, this can be customized by exposing a `PasswordEncoder` as a Spring bean.
This is supported by the `<password-encoder>` element.
With bcrypt encoded passwords, the original authentication provider configuration would look like this:
If you are migrating from Spring Security 4.2.x you can revert to the previous behavior by exposing a `NoOpPasswordEncoder` bean.
For example, if you are using Java Configuration, you can create a configuration that looks like:
[WARNING]
====
Reverting to `NoOpPasswordEncoder` is not considered to be secure.
You should instead migrate to using `DelegatingPasswordEncoder` to support secure password encoding.
====
.NoOpPasswordEncoder with Java Configuration
====
[source,java]
----
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
----
====
if you are using XML configuration, you can expose a `PasswordEncoder` with the id `passwordEncoder`:
.NoPasswordEncoder with XML
====
[source,xml]
----
<beans:bean name="bcryptEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<authentication-manager>
<authentication-provider>
<password-encoder ref="bcryptEncoder"/>
<user-service>
<user name="jimi" password="$2a$10$ddEWZUl8aU0GdZPPpy7wbu82dvEw/pBpbRvDQRqA41y6mK1CoH00m"
authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="$2a$10$/elFpMBnAYYig6KRR5bvOOYeZr1ie1hSogJryg9qDlhza4oCw1Qka"
authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
<b:bean id="passwordEncoder"
class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
----
bcrypt is a good choice for most cases, unless you have a legacy system which forces you to use a different algorithm.
If you are using a simple hashing algorithm or, even worse, storing plain text passwords, then you should consider migrating to a more secure option like bcrypt.
====

View File

@ -0,0 +1,5 @@
[[authentication-support]]
= Authentication Support
Spring Security provides built in support for authenticating users.
Refer to the sections on authentication for <<servlet-authentication,Servlet>> and WebFlux for details on what is supported for each stack.

View File

@ -1,7 +1,9 @@
[[features]]
= Features
Spring Security provides comprehensive support for authentication, authorization, and protection against <<exploits,common exploits>>.
Spring Security provides comprehensive support for <<authentication,authentication>>, authorization, and protection against <<exploits,common exploits>>.
It also provides integration with other libraries to simplify its usage.
include::authentication/index.adoc[leveloffset=+1]
include::exploits/index.adoc[leveloffset=+1]

View File

@ -1,4 +1,4 @@
[[appendix-schema]]
__[[__appendix-schema]]
== Security Database Schema
There are various database schema used by the framework and this appendix provides a single reference point to them all.
You only need to provide the tables for the areas of functionality you require.

View File

@ -1,41 +0,0 @@
[[basic]]
== Basic and Digest Authentication
Basic and digest authentication are alternative authentication mechanisms which are popular in web applications.
Basic authentication is often used with stateless clients which pass their credentials on each request.
It's quite common to use it in combination with form-based authentication where an application is used through both a browser-based user interface and as a web-service.
However, basic authentication transmits the password as plain text so it should only really be used over an encrypted transport layer such as HTTPS.
[[basic-processing-filter]]
=== BasicAuthenticationFilter
`BasicAuthenticationFilter` is responsible for processing basic authentication credentials presented in HTTP headers.
This can be used for authenticating calls made by Spring remoting protocols (such as Hessian and Burlap), as well as normal browser user agents (such as Firefox and Internet Explorer).
The standard governing HTTP Basic Authentication is defined by RFC 1945, Section 11, and `BasicAuthenticationFilter` conforms with this RFC.
Basic Authentication is an attractive approach to authentication, because it is very widely deployed in user agents and implementation is extremely simple (it's just a Base64 encoding of the username:password, specified in an HTTP header).
[[basic-config]]
=== Configuration
To implement HTTP Basic Authentication, you need to add a `BasicAuthenticationFilter` to your filter chain.
The application context should contain `BasicAuthenticationFilter` and its required collaborator:
[source,xml]
----
<bean id="basicAuthenticationFilter"
class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
</bean>
<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
<property name="realmName" value="Name Of Your Realm"/>
</bean>
----
The configured `AuthenticationManager` processes each authentication request.
If authentication fails, the configured `AuthenticationEntryPoint` will be used to retry the authentication process.
Usually you will use the filter in combination with a `BasicAuthenticationEntryPoint`, which returns a 401 response with a suitable header to retry HTTP Basic authentication.
If authentication is successful, the resulting `Authentication` object will be placed into the `SecurityContextHolder` as usual.
If the authentication event was successful, or authentication was not attempted because the HTTP header did not contain a supported authentication request, the filter chain will continue as normal.
The only time the filter chain will be interrupted is if authentication fails and the `AuthenticationEntryPoint` is called.

View File

@ -1,85 +0,0 @@
[[digest-processing-filter]]
== DigestAuthenticationFilter
`DigestAuthenticationFilter` is capable of processing digest authentication credentials presented in HTTP headers.
Digest Authentication attempts to solve many of the weaknesses of Basic authentication, specifically by ensuring credentials are never sent in clear text across the wire.
Many user agents support Digest Authentication, including Mozilla Firefox and Internet Explorer.
The standard governing HTTP Digest Authentication is defined by RFC 2617, which updates an earlier version of the Digest Authentication standard prescribed by RFC 2069.
Most user agents implement RFC 2617.
Spring Security's `DigestAuthenticationFilter` is compatible with the "`auth`" quality of protection (`qop`) prescribed by RFC 2617, which also provides backward compatibility with RFC 2069.
Digest Authentication is a more attractive option if you need to use unencrypted HTTP (i.e. no TLS/HTTPS) and wish to maximise security of the authentication process.
Indeed Digest Authentication is a mandatory requirement for the WebDAV protocol, as noted by RFC 2518 Section 17.1.
[NOTE]
====
You should not use Digest in modern applications because it is not considered secure.
The most obvious problem is that you must store your passwords in plaintext, encrypted, or an MD5 format.
All of these storage formats are considered insecure.
Instead, you should use a one way adaptive password hash (i.e. bCrypt, PBKDF2, SCrypt, etc).
====
Central to Digest Authentication is a "nonce".
This is a value the server generates.
Spring Security's nonce adopts the following format:
[source,txt]
----
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
expirationTime: The date and time when the nonce expires, expressed in milliseconds
key: A private key to prevent modification of the nonce token
----
The `DigestAuthenticationEntryPoint` has a property specifying the `key` used for generating the nonce tokens, along with a `nonceValiditySeconds` property for determining the expiration time (default 300, which equals five minutes).
Whist ever the nonce is valid, the digest is computed by concatenating various strings including the username, password, nonce, URI being requested, a client-generated nonce (merely a random value which the user agent generates each request), the realm name etc, then performing an MD5 hash.
Both the server and user agent perform this digest computation, resulting in different hash codes if they disagree on an included value (eg password).
In Spring Security implementation, if the server-generated nonce has merely expired (but the digest was otherwise valid), the `DigestAuthenticationEntryPoint` will send a `"stale=true"` header.
This tells the user agent there is no need to disturb the user (as the password and username etc is correct), but simply to try again using a new nonce.
An appropriate value for the `nonceValiditySeconds` parameter of `DigestAuthenticationEntryPoint` depends on your application.
Extremely secure applications should note that an intercepted authentication header can be used to impersonate the principal until the `expirationTime` contained in the nonce is reached.
This is the key principle when selecting an appropriate setting, but it would be unusual for immensely secure applications to not be running over TLS/HTTPS in the first instance.
Because of the more complex implementation of Digest Authentication, there are often user agent issues.
For example, Internet Explorer fails to present an "`opaque`" token on subsequent requests in the same session.
Spring Security filters therefore encapsulate all state information into the "`nonce`" token instead.
In our testing, Spring Security's implementation works reliably with Mozilla Firefox and Internet Explorer, correctly handling nonce timeouts etc.
[[digest-config]]
=== Configuration
Now that we've reviewed the theory, let's see how to use it.
To implement HTTP Digest Authentication, it is necessary to define `DigestAuthenticationFilter` in the filter chain.
The application context will need to define the `DigestAuthenticationFilter` and its required collaborators:
[source,xml]
----
<bean id="digestFilter" class=
"org.springframework.security.web.authentication.www.DigestAuthenticationFilter">
<property name="userDetailsService" ref="jdbcDaoImpl"/>
<property name="authenticationEntryPoint" ref="digestEntryPoint"/>
<property name="userCache" ref="userCache"/>
</bean>
<bean id="digestEntryPoint" class=
"org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint">
<property name="realmName" value="Contacts Realm via Digest Authentication"/>
<property name="key" value="acegi"/>
<property name="nonceValiditySeconds" value="10"/>
</bean>
----
The configured `UserDetailsService` is needed because `DigestAuthenticationFilter` must have direct access to the clear text password of a user.
Digest Authentication will NOT work if you are using encoded passwords in your DAO footnote:[It is possible to encode the password in the format HEX( MD5(username:realm:password) ) provided the `DigestAuthenticationFilter.passwordAlreadyEncoded` is set to `true`.
However, other password encodings will not work with digest authentication.].
The DAO collaborator, along with the `UserCache`, are typically shared directly with a `DaoAuthenticationProvider`.
The `authenticationEntryPoint` property must be `DigestAuthenticationEntryPoint`, so that `DigestAuthenticationFilter` can obtain the correct `realmName` and `key` for digest calculations.
Like `BasicAuthenticationFilter`, if authentication is successful an `Authentication` request token will be placed into the `SecurityContextHolder`.
If the authentication event was successful, or authentication was not attempted because the HTTP header did not contain a Digest Authentication request, the filter chain will continue as normal.
The only time the filter chain will be interrupted is if authentication fails and the `AuthenticationEntryPoint` is called, as discussed in the previous paragraph.
Digest Authentication's RFC offers a range of additional features to further increase security.
For example, the nonce can be changed on every request.
Despite this, Spring Security implementation was designed to minimise the complexity of the implementation (and the doubtless user agent incompatibilities that would emerge), and avoid needing to store server-side state.
You are invited to review RFC 2617 if you wish to explore these features in more detail.
As far as we are aware, Spring Security's implementation does comply with the minimum standards of this RFC.

View File

@ -1,138 +0,0 @@
[[jc-form]]
== Form Login
=== Form Login Java Configuration
You might be wondering where the login form came from when you were prompted to log in, since we made no mention of any HTML files or JSPs.
Since Spring Security's default configuration does not explicitly set a URL for the login page, Spring Security generates one automatically, based on the features that are enabled and using standard values for the URL which processes the submitted login, the default target URL the user will be sent to after logging in and so on.
While the automatically generated log in page is convenient to get up and running quickly, most applications will want to provide their own login page.
When we want to change the default configuration, we can customize the `WebSecurityConfigurerAdapter` that we mentioned earlier by extending it like so:
[source,java]
----
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// ...
}
----
And then override the `configure` method as seen below:
[source,java]
----
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.anyRequest().authenticated()
)
.formLogin(formLogin ->
formLogin
.loginPage("/login") // <1>
.permitAll() // <2>
);
}
----
<1> The updated configuration specifies the location of the log in page.
<2> We must grant all users (i.e. unauthenticated users) access to our log in page.
The `formLogin().permitAll()` method allows granting access to all users for all URLs associated with form based log in.
An example log in page implemented with JSPs for our current configuration can be seen below:
NOTE: The login page below represents our current configuration.
We could easily update our configuration if some of the defaults do not meet our needs.
[source,html]
----
<c:url value="/login" var="loginUrl"/>
<form action="${loginUrl}" method="post"> <1>
<c:if test="${param.error != null}"> <2>
<p>
Invalid username and password.
</p>
</c:if>
<c:if test="${param.logout != null}"> <3>
<p>
You have been logged out.
</p>
</c:if>
<p>
<label for="username">Username</label>
<input type="text" id="username" name="username"/> <4>
</p>
<p>
<label for="password">Password</label>
<input type="password" id="password" name="password"/> <5>
</p>
<input type="hidden" <6>
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
<button type="submit" class="btn">Log in</button>
</form>
----
<1> A POST to the `/login` URL will attempt to authenticate the user
<2> If the query parameter `error` exists, authentication was attempted and failed
<3> If the query parameter `logout` exists, the user was successfully logged out
<4> The username must be present as the HTTP parameter named __username__
<5> The password must be present as the HTTP parameter named __password__
<6> We must <<servlet-csrf-include>> To learn more read the <<csrf>> section of the reference
=== Form Login XML Configuration
[[ns-form-and-basic]]
==== Form and Basic Login Options
You might be wondering where the login form came from when you were prompted to log in, since we made no mention of any HTML files or JSPs.
In fact, since we didn't explicitly set a URL for the login page, Spring Security generates one automatically, based on the features that are enabled and using standard values for the URL which processes the submitted login, the default target URL the user will be sent to after logging in and so on.
However, the namespace offers plenty of support to allow you to customize these options.
For example, if you want to supply your own login page, you could use:
[source,xml]
----
<http>
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
</http>
----
Also note that we've added an extra `intercept-url` element to say that any requests for the login page should be available to anonymous users footnote:[See the chapter on pass:specialcharacters,macros[<<anonymous>>]] and also the <<authz-authenticated-voter,AuthenticatedVoter>> class for more details on how the value `IS_AUTHENTICATED_ANONYMOUSLY` is processed.].
Otherwise the request would be matched by the pattern /** and it wouldn't be possible to access the login page itself!
This is a common configuration error and will result in an infinite loop in the application.
Spring Security will emit a warning in the log if your login page appears to be secured.
It is also possible to have all requests matching a particular pattern bypass the security filter chain completely, by defining a separate `http` element for the pattern like this:
[source,xml]
----
<http pattern="/css/**" security="none"/>
<http pattern="/login.jsp*" security="none"/>
<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/>
</http>
----
From Spring Security 3.1 it is now possible to use multiple `http` elements to define separate security filter chain configurations for different request patterns.
If the `pattern` attribute is omitted from an `http` element, it matches all requests.
Creating an unsecured pattern is a simple example of this syntax, where the pattern is mapped to an empty filter chain footnote:[The use of multiple `<http>` elements is an important feature, allowing the namespace to simultaneously support both stateful and stateless paths within the same application, for example.
The previous syntax, using the attribute `filters="none"` on an `intercept-url` element is incompatible with this change and is no longer supported in 3.1.].
We'll look at this new syntax in more detail in the chapter on the <<filter-chains-with-ns,Security Filter Chain>>.
It's important to realise that these unsecured requests will be completely oblivious to any Spring Security web-related configuration or additional attributes such as `requires-channel`, so you will not be able to access information on the current user or call secured methods during the request.
Use `access='IS_AUTHENTICATED_ANONYMOUSLY'` as an alternative if you still want the security filter chain to be applied.
If you want to use basic authentication instead of form login, then change the configuration to
[source,xml]
----
<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<http-basic />
</http>
----
Basic authentication will then take precedence and will be used to prompt for a login when a user attempts to access a protected resource.
Form login is still available in this configuration if you wish to use it, for example through a login form embedded in another web page.

View File

@ -1,18 +0,0 @@
[[jc-authentication-inmemory]]
== In-Memory Authentication
We have already seen an example of configuring in-memory authentication for a single user.
Below is an example to configure multiple users:
[source,java]
----
@Bean
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
----

View File

@ -1,18 +1,16 @@
[[jc-authentication]]
[[servlet-authentication]]
= Authentication
include::in-memory.adoc[]
Spring Security provides comprehensive support for <<authentication>>.
This section discusses:
include::jdbc.adoc[]
* <<servlet-authentication-unpwd>>
// FIXME: Add other mechanisms
include::ldap.adoc[]
include::unpwd/index.adoc[leveloffset=+1]
include::authentication-provider.adoc[]
include::user-details-service.adoc[]
include::password-encoder.adoc[]
include::authentication-manager.adoc[]
include::session-management.adoc[]
@ -33,12 +31,6 @@ include::x509.adoc[]
include::runas.adoc[]
include::form.adoc[]
include::basic.adoc[]
include::digest.adoc[]
include::logout.adoc[]
include::authentication-entry-point.adoc[]

View File

@ -1,24 +0,0 @@
[[jc-authentication-jdbc]]
== JDBC Authentication
You can find the updates to support JDBC based authentication.
The example below assumes that you have already defined a `DataSource` within your application.
The https://github.com/spring-projects/spring-security/tree/master/samples/javaconfig/jdbc[jdbc-javaconfig] sample provides a complete example of using JDBC based authentication.
[source,java]
----
@Autowired
private DataSource dataSource;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser(users.username("user").password("password").roles("USER"))
.withUser(users.username("admin").password("password").roles("USER","ADMIN"));
}
----

View File

@ -1,350 +0,0 @@
[[core-services-password-encoding]]
== Password Encoding
Spring Security's `PasswordEncoder` interface is used to perform a one way transformation of a password to allow the password to be stored securely.
Given `PasswordEncoder` is a one way transformation, it is not intended when the password transformation needs to be two way (i.e. storing credentials used to authenticate to a database).
Typically `PasswordEncoder` is used for storing a password that needs to be compared to a user provided password at the time of authentication.
[[pe-history]]
=== Password History
Throughout the years the standard mechanism for storing passwords has evolved.
In the beginning passwords were stored in plain text.
The passwords were assumed to be safe because the data store the passwords were saved in required credentials to access it.
However, malicious users were able to find ways to get large "data dumps" of usernames and passwords using attacks like SQL Injection.
As more and more user credentials became public security experts realized we needed to do more to protect users passwords.
Developers were then encouraged to store passwords after running them through a one way hash such as SHA-256.
When a user tried to authenticate, the hashed password would be compared to the hash of the password that they typed.
This meant that the system only needed to store the one way hash of the password.
If a breach occurred, then only the one way hashes of the passwords were exposed.
Since the hashes were one way and it was computationally difficult to guess the passwords given the hash, it would not be worth the effort to figure out each password in the system.
To defeat this new system malicious users decided to create lookup tables known as https://en.wikipedia.org/wiki/Rainbow_table[Rainbow Tables].
Rather than doing the work of guessing each password every time, they computed the password once and stored it in a lookup table.
To mitigate the effectiveness of Rainbow Tables, developers were encouraged to use salted passwords.
Instead of using just the password as input to the hash function, random bytes (known as salt) would be generated for every users' password.
The salt and the user's password would be ran through the hash function which produced a unique hash.
The salt would be stored alongside the user's password in clear text.
Then when a user tried to authenticate, the hashed password would be compared to the hash of the stored salt and the password that they typed.
The unique salt meant that Rainbow Tables were no longer effective because the hash was different for every salt and password combination.
In modern times we realize that cryptographic hashes (like SHA-256) are no longer secure.
The reason is that with modern hardware we can perform billions of hash calculations a second.
This means that we can crack each password individually with ease.
Developers are now encouraged to leverage adaptive one-way functions to store a password.
Validation of passwords with adaptive one-way functions are intentionally resource (i.e. CPU, memory, etc) intensive.
An adaptive one-way function allows configuring a "work factor" which can grow as hardware gets better.
It is recommended that the "work factor" be tuned to take about 1 second to verify a password on your system.
This trade off is to make it difficult for attackers to crack the password, but not so costly it puts excessive burden on your own system.
Spring Security has attempted to provide a good starting point for the "work factor", but users are encouraged to customize the "work factor" for their own system since the performance will vary drastically from system to system.
Examples of adaptive one-way functions that should be used include
https://en.wikipedia.org/wiki/Bcrypt[bcrypt],
https://en.wikipedia.org/wiki/PBKDF2[PBKDF2],
https://en.wikipedia.org/wiki/Scrypt[scrypt],
and https://en.wikipedia.org/wiki/Argon2[Argon2].
Because adaptive one-way functions are intentionally resource intensive, validating a username and password for every request will degrade performance of an application significantly.
There is nothing Spring Security (or any other library) can do to speed up the validation of the password since security is gained by making the validation resource intensive.
Users are encouraged to exchange the long term credentials (i.e. username and password) for a short term credential (i.e. session, OAuth Token, etc).
The short term credential can be validated quickly without any loss in security.
[[pe-dpe]]
=== DelegatingPasswordEncoder
Prior to Spring Security 5.0 the default `PasswordEncoder` was `NoOpPasswordEncoder` which required plain text passwords.
Based upon the <<password-history,Password History>> section you might expect that the default `PasswordEncoder` is now something like `BCryptPasswordEncoder`.
However, this ignores three real world problems:
- There are many applications using old password encodings that cannot easily migrate
- The best practice for password storage will change again.
- As a framework Spring Security cannot make breaking changes frequently
Instead Spring Security introduces `DelegatingPasswordEncoder` which solves all of the problems by:
- Ensuring that passwords are encoded using the current password storage recommendations
- Allowing for validating passwords in modern and legacy formats
- Allowing for upgrading the encoding in the future
You can easily construct an instance of `DelegatingPasswordEncoder` using `PasswordEncoderFactories`.
[source,java]
----
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
----
Alternatively, you may create your own custom instance. For example:
[source,java]
----
String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(idForEncode, encoders);
----
[[pe-dpe-format]]
==== Password Storage Format
The general format for a password is:
[source,text]
----
{id}encodedPassword
----
Such that `id` is an identifier used to look up which `PasswordEncoder` should be used and `encodedPassword` is the original encoded password for the selected `PasswordEncoder`.
The `id` must be at the beginning of the password, start with `{` and end with `}`.
If the `id` cannot be found, the `id` will be null.
For example, the following might be a list of passwords encoded using different `id`.
All of the original passwords are "password".
[source,text]
----
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG // <1>
{noop}password // <2>
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc // <3>
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= // <4>
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 // <5>
----
<1> The first password would have a `PasswordEncoder` id of `bcrypt` and encodedPassword of `$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG`.
When matching it would delegate to `BCryptPasswordEncoder`
<2> The second password would have a `PasswordEncoder` id of `noop` and encodedPassword of `password`.
When matching it would delegate to `NoOpPasswordEncoder`
<3> The third password would have a `PasswordEncoder` id of `pbkdf2` and encodedPassword of `5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc`.
When matching it would delegate to `Pbkdf2PasswordEncoder`
<4> The fourth password would have a `PasswordEncoder` id of `scrypt` and encodedPassword of `$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=`
When matching it would delegate to `SCryptPasswordEncoder`
<5> The final password would have a `PasswordEncoder` id of `sha256` and encodedPassword of `97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0`.
When matching it would delegate to `StandardPasswordEncoder`
[NOTE]
====
Some users might be concerned that the storage format is provided for a potential hacker.
This is not a concern because the storage of the password does not rely on the algorithm being a secret.
Additionally, most formats are easy for an attacker to figure out without the prefix.
For example, BCrypt passwords often start with `$2a$`.
====
==== Password Encoding
The `idForEncode` passed into the constructor determines which `PasswordEncoder` will be used for encoding passwords.
In the `DelegatingPasswordEncoder` we constructed above, that means that the result of encoding `password` would be delegated to `BCryptPasswordEncoder` and be prefixed with `{bcrypt}`.
The end result would look like:
[source,text]
----
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
==== Password Matching
Matching is done based upon the `{id}` and the mapping of the `id` to the `PasswordEncoder` provided in the constructor.
Our example in <<Password Storage Format>> provides a working example of how this is done.
By default, the result of invoking `matches(CharSequence, String)` with a password and an `id` that is not mapped (including a null id) will result in an `IllegalArgumentException`.
This behavior can be customized using `DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)`.
By using the `id` we can match on any password encoding, but encode passwords using the most modern password encoding.
This is important, because unlike encryption, password hashes are designed so that there is no simple way to recover the plaintext.
Since there is no way to recover the plaintext, it makes it difficult to migrate the passwords.
While it is simple for users to migrate `NoOpPasswordEncoder`, we chose to include it by default to make it simple for the getting started experience.
==== Getting Started Experience
If you are putting together a demo or a sample, it is a bit cumbersome to take time to hash the passwords of your users.
There are convenience mechanisms to make this easier, but this is still not intended for production.
[source,java]
----
User user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("user")
.build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
If you are creating multiple users, you can also reuse the builder.
[source,java]
----
UserBuilder users = User.withDefaultPasswordEncoder();
User user = users
.username("user")
.password("password")
.roles("USER")
.build();
User admin = users
.username("admin")
.password("password")
.roles("USER","ADMIN")
.build();
----
This does hash the password that is stored, but the passwords are still exposed in memory and in the compiled source code.
Therefore, it is still not considered secure for a production environment.
For production, you should hash your passwords externally.
==== Troubleshooting
The following error occurs when one of the passwords that are stored has no id as described in <<pe-dpe-format>>.
----
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)
----
The easiest way to resolve the error is to switch to explicitly provide the `PasswordEncoder` that you passwords are encoded with.
The easiest way to resolve it is to figure out how your passwords are currently being stored and explicitly provide the correct `PasswordEncoder`.
If you are migrating from Spring Security 4.2.x you can revert to the previous behavior by exposing a `NoOpPasswordEncoder` bean.
For example, if you are using Java Configuration, you can create a configuration that looks like:
[WARNING]
====
Reverting to `NoOpPasswordEncoder` is not considered to be secure.
You should instead migrate to using `DelegatingPasswordEncoder` to support secure password encoding.
====
[source,java]
----
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
----
if you are using XML configuration, you can expose a `PasswordEncoder` with the id `passwordEncoder`:
[source,xml]
----
<b:bean id="passwordEncoder"
class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
----
Alternatively, you can prefix all of your passwords with the correct id and continue to use `DelegatingPasswordEncoder`.
For example, if you are using BCrypt, you would migrate your password from something like:
----
$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
to
----
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
For a complete listing of the mappings refer to the Javadoc on
https://docs.spring.io/spring-security/site/docs/5.0.x/api/org/springframework/security/crypto/factory/PasswordEncoderFactories.html[PasswordEncoderFactories].
[[pe-bcpe]]
=== BCryptPasswordEncoder
The `BCryptPasswordEncoder` implementation uses the widely supported https://en.wikipedia.org/wiki/Bcrypt[bcrypt] algorithm to hash the passwords.
In order to make it more resistent to password cracking, bcrypt is deliberately slow.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
[source,java]
----
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
[[pe-a2pe]]
=== Argon2PasswordEncoder
The `Argon2PasswordEncoder` implementation uses the https://en.wikipedia.org/wiki/Argon2[Argon2] algorithm to hash the passwords.
Argon2 is the winner of the https://en.wikipedia.org/wiki/Password_Hashing_Competition[Password Hashing Competition].
In order to defeat password cracking on custom hardware, Argon2 is a deliberately slow algorithm that requires large amounts of memory.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
The current implementation if the `Argon2PasswordEncoder` requires BouncyCastle.
[source,java]
----
// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = new Argon2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
[[pe-pbkdf2pe]]
=== Pbkdf2PasswordEncoder
The `Pbkdf2PasswordEncoder` implementation uses the https://en.wikipedia.org/wiki/PBKDF2[PBKDF2] algorithm to hash the passwords.
In order to defeat password cracking PBKDF2 is a deliberately slow algorithm.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
This algorithm is a good choice when FIPS certification is required.
[source,java]
----
// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
[[pe-scpe]]
=== SCryptPasswordEncoder
The `SCryptPasswordEncoder` implementation uses https://en.wikipedia.org/wiki/Scrypt[scrypt] algorithm to hash the passwords.
In order to defeat password cracking on custom hardware scrypt is a deliberately slow algorithm that requires large amounts of memory.
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
[source,java]
----
// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
=== Other PasswordEncoders
There are a significant number of other `PasswordEncoder` implementations that exist entirely for backward compatibility.
They are all deprecated to indicate that they are no longer considered secure.
However, there are no plans to remove them since it is difficult to migrate existing legacy systems.
[[ns-password-encoder]]
=== Password Encoder XML Configuration
Passwords should always be encoded using a secure hashing algorithm designed for the purpose (not a standard algorithm like SHA or MD5).
This is supported by the `<password-encoder>` element.
With bcrypt encoded passwords, the original authentication provider configuration would look like this:
[source,xml]
----
<beans:bean name="bcryptEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<authentication-manager>
<authentication-provider>
<password-encoder ref="bcryptEncoder"/>
<user-service>
<user name="jimi" password="$2a$10$ddEWZUl8aU0GdZPPpy7wbu82dvEw/pBpbRvDQRqA41y6mK1CoH00m"
authorities="ROLE_USER, ROLE_ADMIN" />
<user name="bob" password="$2a$10$/elFpMBnAYYig6KRR5bvOOYeZr1ie1hSogJryg9qDlhza4oCw1Qka"
authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
----
bcrypt is a good choice for most cases, unless you have a legacy system which forces you to use a different algorithm.
If you are using a simple hashing algorithm or, even worse, storing plain text passwords, then you should consider migrating to a more secure option like bcrypt.

View File

@ -0,0 +1,9 @@
[[servlet-authentication-unpwd]]
= Username/Password Authentication
One of the most common ways to authenticate a user is by validating a username and password.
As such, Spring Security provides comprehensive support for user <<servlet-authentication-unpwd-input,input>> and <<servlet-authentication-unpwd-storage,storage>> of a username and password.
include::input/index.adoc[leveloffset=+1]
include::storage/index.adoc[leveloffset=+1]

View File

@ -0,0 +1,35 @@
[[servlet-authentication-basic]]
= Basic Authentication
This section provides details on how Spring Security provides support for https://tools.ietf.org/html/rfc7617[Basic HTTP Authentication] for servlet based applications.
// FIXME: describe authenticationentrypoint, authenticationfailurehandler, authenticationsuccesshandler
Spring Security's HTTP Basic Authentication support in is enabled by default.
However, as soon as any servlet based configuration is provided, HTTP Basic must be explicitly provided.
A minimal, explicit Java configuration can be found below:
.HTTP Basic Java Configuration
====
[source,java]
----
protected void configure(HttpSecurity http) {
http
// ...
.httpBasic(withDefaults());
}
----
====
A minimal XML configuration can be found below:
.HTTP Basic XML Configuration
====
[source,xml]
----
<http>
<!-- ... -->
<http-basic />
</http>
----
====

View File

@ -0,0 +1,91 @@
[[servlet-authentication-digest]]
= Digest Authentication
This section provides details on how Spring Security provides support for https://tools.ietf.org/html/rfc2617[Digest Authentication] which is provided `DigestAuthenticationFilter`.
[WARNING]
====
You should not use Digest Authentication in modern applications because it is not considered secure.
The most obvious problem is that you must store your passwords in plaintext, encrypted, or an MD5 format.
All of these storage formats are considered insecure.
Instead, you should store credentials using a one way adaptive password hash (i.e. bCrypt, PBKDF2, SCrypt, etc) which is not supported by Digest Authentication.
====
Digest Authentication attempts to solve many of the weaknesses of <<servlet-authentication-basic,Basic authentication>>, specifically by ensuring credentials are never sent in clear text across the wire.
Many https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Digest#Browser_compatibility[browsers support Digest Authentication].
The standard governing HTTP Digest Authentication is defined by https://tools.ietf.org/html/rfc2617[RFC 2617], which updates an earlier version of the Digest Authentication standard prescribed by https://tools.ietf.org/html/rfc2069[RFC 2069].
Most user agents implement RFC 2617.
Spring Security's Digest Authentication support is compatible with the "`auth`" quality of protection (`qop`) prescribed by RFC 2617, which also provides backward compatibility with RFC 2069.
Digest Authentication was seen as a more attractive option if you need to use unencrypted HTTP (i.e. no TLS/HTTPS) and wish to maximise security of the authentication process.
However, everyone should use <<http,HTTPS>>.
Central to Digest Authentication is a "nonce".
This is a value the server generates.
Spring Security's nonce adopts the following format:
.Digest Syntax
====
[source,txt]
----
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
expirationTime: The date and time when the nonce expires, expressed in milliseconds
key: A private key to prevent modification of the nonce token
----
====
You will need to ensure you <<authentication-password-storage-configuration,configure>> insecure plain text <<authentication-password-storage,Password Storage>> using NoOpPasswordEncoder`.
The following provides an example of configuring Digest Authentication with Java Configuration:
.Digest Authentication with Java Configuration
====
[source,java]
----
@Autowired
UserDetailsService userDetailsService;
DigestAuthenticationEntryPoint entryPoint() {
DigestAuthenticationEntryPoint result = new DigestAuthenticationEntryPoint();
result.setRealmName("My App Relam");
result.setKey("3028472b-da34-4501-bfd8-a355c42bdf92");
}
DigestAuthenticationFilter digestAuthenticationFilter() {
DigestAuthenticationFilter result = new DigestAuthenticationFilter();
result.setUserDetailsService(userDetailsService);
result.setAuthenticationEntryPoint(entryPoint());
}
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.exceptionHandling(e -> e.authenticationEntryPoint(authenticationEntryPoint()))
.addFilterBefore(digestFilter());
}
----
====
The following provides an example of configuring Digest Authentication with XML Configuration:
.Digest Authentication with XML Configuration
====
[source,xml]
----
<b:bean id="digestFilter"
class="org.springframework.security.web.authentication.www.DigestAuthenticationFilter"
p:userDetailsService-ref="jdbcDaoImpl"
p:authenticationEntryPoint-ref="digestEntryPoint"
/>
<b:bean id="digestEntryPoint"
class="org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint"
p:realmName="My App Relam"
p:key="3028472b-da34-4501-bfd8-a355c42bdf92"
/>
<http>
<!-- ... -->
<custom-filter ref="userFilter" position="DIGEST_AUTH_FILTER"/>
</http>
----
====

View File

@ -0,0 +1,142 @@
[[servlet-authentication-form]]
= Form Login
Spring Security provides support for username and password being provided through an html form.
This section provides details on how form based authentication works within Spring Security.
// FIXME: describe authenticationentrypoint, authenticationfailurehandler, authenticationsuccesshandler
[[servlet-authentication-form-min]]
== Form Login Configuration
Spring Security form log in is enabled by default.
However, as soon as any servlet based configuration is provided, form based log in must be explicitly provided.
A minimal, explicit Java configuration can be found below:
.Form Log In Java Configuration
====
[source,java]
----
protected void configure(HttpSecurity http) {
http
// ...
.formLogin(withDefaults());
}
----
====
A minimal XML configuration can be found below:
.Form Log In XML Configuration
====
[source,xml]
----
<http>
<!-- ... -->
<form-login />
</http>
----
====
In this configuration Spring Security will render a default log in page.
Most production applications will require a custom log in form.
[[servlet-authentication-form-custom]]
== Custom Log In Form
The configuration below demonstrates how to provide a custom log in form.
.Custom Log In Form with Java Configuration
====
[source,java]
----
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
}
----
====
A minimal XML configuration can be found below:
.Custom Log In Form with XML Configuration
====
[source,xml]
----
<http>
<!-- ... -->
<intercept-url pattern="/login" access="permitAll" />
<form-login login-page="/login" />
</http>
----
====
[[servlet-authentication-form-custom-html]]
=== HTML Form
When the login page is specified in the Spring Security configuration, you are responsible for rendering the page.
Below is a https://www.thymeleaf.org/[Thymeleaf] template that produces an HTML login form that complies with a login page of `/login`.:
.Log In Form src/main/resources/templates/login.html
====
[source,xml]
----
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<title>Please Log In</title>
</head>
<body>
<h1>Please Log In</h1>
<div th:if="${param.error}">
Invalid username and password.</div>
<div th:if="${param.logout}">
You have been logged out.</div>
<form th:action="@{/login}" method="post">
<div>
<input type="text" name="username" placeholder="Username"/>
</div>
<div>
<input type="password" name="password" placeholder="Password"/>
</div>
<input type="submit" value="Log in" />
</form>
</body>
</html>
----
====
There are a few key points about the default HTML form:
* The form should perform a `post` to `/login`
* The form will need to include a <<servlet-csrf,CSRF Token>> which is <<servlet-csrf-include-form-auto,automatically included>> by Thymeleaf.
* The form should specify the username in a parameter named `username`
* The form should specify the password in a parameter named `password`
* If the HTTP parameter error is found, it indicates the user failed to provide a valid username / password
* If the HTTP parameter logout is found, it indicates the user has logged out successfully
Many users will not need much more than to customize the log in page.
However, if needed everything above can be customized with additional configuration.
[[servlet-authentication-form-custom-controller]]
== LoginController
If you are using Spring MVC, you will need a controller that maps `GET /login` to the login template we created.
A minimal sample `LoginController` can be see below:
.LoginController
====
[source,java]
----
@Controller
class LoginController {
@GetMapping("/login")
String login() {
return "login";
}
}
----
====

View File

@ -0,0 +1,16 @@
[[servlet-authentication-unpwd-input]]
= Username/Password Input
Spring Security provides multiple ways for a user to enter their username and password.
Each of the supported mechanisms leverage any of the supported <<servlet-authentication-unpwd-storage,storage>> mechanisms.
This section discusses how a username and password can be provided to Spring Security:
* <<servlet-authentication-form,Form Login>>
* <<servlet-authentication-basic,Basic Authentication>>
* <<servlet-authentication-digest,Digest Authentication>>
include::form.adoc[leveloffset=+1]
include::basic.adoc[leveloffset=+1]
include::digest.adoc[leveloffset=+1]

View File

@ -0,0 +1,4 @@
[[servlet-password-storage]]
= Password Storage
Spring Security provides

View File

@ -0,0 +1,94 @@
[[servlet-authentication-inmemory]]
= In-Memory Authentication
Spring Security's `InMemoryUserDetailsManager` implements <<servlet-authentication-userdetailsservice,UserDetailsService>> to provide support for username/password based authentication that is retrieved in memory.
`InMemoryUserDetailsManager` provides management of `UserDetails` by implementing the `UserDetailsManager` interface.
`UserDetails` based authentication is used by Spring Security when it is configured to <<servlet-authentication-unpwd-input,accept a username/password>> for authentication.
In this sample we use <<authentication-password-storage-boot-cli,Spring Boot CLI>> to encode the password of `password` and get the encoded password of `{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW`.
.InMemoryUserDetailsManager Java Configuration
====
[source,java]
----
@Bean
public UserDetailsService users() {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
----
====
The same configuration in XML looks like:
.<user-service> XML Configuration
====
[source,xml]
----
<user-service>
<user name="user"
password="{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW"
authorities="ROLE_USER" />
<user name="admin"
password="{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW"
authorities="ROLE_USER,ROLE_ADMIN" />
</user-service>
----
====
The samples above store the passwords in a secure format, but leave a lot to be desired in terms of getting started experience.
In the sample below we leverage <<authentication-password-storage-dep-getting-started,User.withDefaultPasswordEncoder>> to ensure that the password stored in memory is protected.
However, it does not protect the password against obtaining the password by decompiling the source code.
For this reason, `User.withDefaultPasswordEncoder` should only be used for "getting started" and is not intended for production.
.InMemoryUserDetailsManager with User.withDefaultPasswordEncoder
====
[source,java]
----
@Bean
public UserDetailsService users() {
// The builder will ensure the passwords are encoded before saving in memory
UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails user = users
.username("admin")
.password("password")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
----
====
There is no simple way to use `User.withDefaultPasswordEncoder` with XML based configuration.
For demos or just getting started, you can choose to prefix the password with `{noop}` to indicate <<authentication-password-storage-dpe-format,no encoding should be used>>.
.<user-service> `{noop}` XML Configuration
====
[source,xml]
----
<user-service>
<user name="user"
password="{noop}password"
authorities="ROLE_USER" />
<user name="admin"
password="{noop}password"
authorities="ROLE_USER,ROLE_ADMIN" />
</user-service>
----
====

View File

@ -0,0 +1,25 @@
[[servlet-authentication-unpwd-storage]]
= User Storage
Spring Security's <<servlet-authentication-userdetailsservice,`UserDetailsService`>> allows for storing user information including a username and password.
`UserDetailsService` is used by Spring Security when it is configured to <<servlet-authentication-unpwd-input,accept a username/password>> for authentication.
// FIXME: Once it is retrieved it is validated using DaoAuthenticationProvider
Spring Security provides support for storing user information with the following stores:
* Simple Storage with <<servlet-authentication-inmemory>>
* Relational Databases with <<servlet-authentication-jdbc>>
* LDAP Servers with <<servlet-authentication-ldap>>
* Custom data stores with <<servlet-authentication-userdetailsservice>>
include::in-memory.adoc[leveloffset=+1]
include::jdbc.adoc[leveloffset=+1]
include::ldap.adoc[leveloffset=+1]
include::user-details-service.adoc[leveloffset=+1]
// FIXME: UserDetailsManager

View File

@ -0,0 +1,187 @@
[[servlet-authentication-jdbc]]
= JDBC Authentication
Spring Security's `JdbcDaoImpl` implements <<servlet-authentication-userdetailsservice,UserDetailsService>> to provide support for username/password based authentication that is retrieved using JDBC.
`JdbcUserDetailsManager` extends `JdbcDaoImpl` to provide management of `UserDetails` through the `UserDetailsManager` interface.
In the following sections we will discuss:
* The <<servlet-authentication-jdbc-schema>> used by Spring Security JDBC Authentication
* <<servlet-authentication-jdbc-datasource>>
* <<servlet-authentication-jdbc-bean>>
[[servlet-authentication-jdbc-when]]
== When is it Used?
JDBC authentication is used for authenticating a username and password.
Spring Security leverages username/password based authentication when any of the following are enabled:
* <<servlet-authentication-form>>
* <<servlet-authentication-basic>>
[[servlet-authentication-jdbc-schema]]
== Default Schema
Spring Security provides default queries for JDBC based authentication.
This section provides the corresponding default schemas used with the default queries.
You will need to adjust the schema to match any customizations to the queries and the database dialect you are using.
[[servlet-authentication-jdbc-schema-user]]
=== User Schema
`JdbcDaoImpl` requires tables to load the password, account status (enabled or disabled) and a list of authorities (roles) for the user.
The default schema required can be found below.
[NOTE]
====
The default schema is also exposed as a classpath resource named `org/springframework/security/core/userdetails/jdbc/users.ddl`.
====
.Default User Schema
====
[source,sql]
----
create table users(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(50) not null,
enabled boolean not null
);
create table authorities (
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username,authority);
----
====
Oracle is a popular database choice, but requires a slightly different schema.
You can find the default Oracle Schema for users below.
.Default User Schema for Oracle Databases
====
[source,sql]
----
CREATE TABLE USERS (
USERNAME NVARCHAR2(128) PRIMARY KEY,
PASSWORD NVARCHAR2(128) NOT NULL,
ENABLED CHAR(1) CHECK (ENABLED IN ('Y','N') ) NOT NULL
);
CREATE TABLE AUTHORITIES (
USERNAME NVARCHAR2(128) NOT NULL,
AUTHORITY NVARCHAR2(128) NOT NULL
);
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_UNIQUE UNIQUE (USERNAME, AUTHORITY);
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_FK1 FOREIGN KEY (USERNAME) REFERENCES USERS (USERNAME) ENABLE;
----
====
[[servlet-authentication-jdbc-schema-group]]
=== Group Schema
If your application is leveraging groups, you will need to provide the groups schema.
The default schema for groups can be found below.
.Default Group Schema
====
[source,sql]
----
create table groups (
id bigint generated by default as identity(start with 0) primary key,
group_name varchar_ignorecase(50) not null
);
create table group_authorities (
group_id bigint not null,
authority varchar(50) not null,
constraint fk_group_authorities_group foreign key(group_id) references groups(id)
);
create table group_members (
id bigint generated by default as identity(start with 0) primary key,
username varchar(50) not null,
group_id bigint not null,
constraint fk_group_members_group foreign key(group_id) references groups(id)
);
----
====
[[servlet-authentication-jdbc-datasource]]
== Setting up a DataSource
Before we configure `JdbcUserDetailsManager`, we must create a `DataSource`.
In our example, we will setup an https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#jdbc-embedded-database-support[embedded DataSource] that is initialized with the <<servlet-authentication-jdbc-schema,default user schema>>.
.Embedded Data Source with Java Configuration
====
[source,java]
----
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(H2)
.addScript("classpath:org/springframework/security/core/userdetails/jdbc/users.ddl")
.build();
}
----
====
.Embedded Data Source with XML Configuration
====
[source,xml]
----
<jdbc:embedded-database>
<jdbc:script location="classpath:org/springframework/security/core/userdetails/jdbc/users.ddl"/>
</jdbc:embedded-database>
----
====
In a production environment, you will want to ensure you setup a connection to an external database.
[[servlet-authentication-jdbc-bean]]
== JdbcUserDetailsManager Bean
In this sample we use <<authentication-password-storage-boot-cli,Spring Boot CLI>> to encode the password of `password` and get the encoded password of `{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW`.
See the <<authentication-password-storage,PasswordEncoder>> section for more details about how to store passwords.
.JdbcUserDetailsManager with Java Configuration
====
[source,java]
----
@Bean
UserDetailsManager users(DataSource dataSource) {
UserDetails user = User.builder()
.username("user")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
.roles("USER", "ADMIN")
.build();
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
users.createUser()
}
----
====
The same configuration in XML looks like:
.<jdbc-user-service> XML Configuration
====
[source,xml]
----
<jdbc-user-service>
<user name="user"
password="{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW"
authorities="ROLE_USER" />
<user name="admin"
password="{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW"
authorities="ROLE_USER,ROLE_ADMIN" />
</jdbc-user-service>
----
====

View File

@ -1,9 +1,6 @@
[[ldap]]
== LDAP Authentication
[[servlet-authentication-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.
@ -18,27 +15,13 @@ We don't use any third-party LDAP libraries (Mozilla, JLDAP etc.) in the LDAP pr
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 https://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]]
[[servlet-authentication-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:
@ -47,9 +30,13 @@ This can be configured to point at an external LDAP server, using the `url` attr
<ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />
----
NOTE: `spring-security` provides integration with `apacheds` and `unboundid` as a embedded ldap servers. You can choose between them using the attribute `mode` in `ldap-server`.
[NOTE]
====
`spring-security` provides integration with `apacheds` and `unboundid` as a embedded ldap servers. You can choose between them using the attribute `mode` in `ldap-server`.
====
=== Using an Embedded Test Server
[[servlet-authentication-ldap-embedded]]
== 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:
@ -73,7 +60,8 @@ 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
[[servlet-authentication-ldap-bind]]
== Using Bind Authentication
This is the most common LDAP authentication scenario.
[source,xml]
@ -95,7 +83,8 @@ If used with the server definition above, this would perform a search under the
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
[[servlet-authentication-ldap-authorities]]
== Loading Authorities
How authorities are loaded from groups in the LDAP directory is controlled by the following attributes.
* `group-search-base`.
@ -123,6 +112,7 @@ 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.
[[servlet-authentication-ldap-implementation]]
== 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.
@ -132,7 +122,7 @@ If you're happy using namespace configuration then you can skip this section and
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]]
[[servlet-authentication-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.
@ -145,8 +135,8 @@ There are currently two authentication strategies supplied with Spring Security:
* 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
[[servlet-authentication-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}`.
@ -157,34 +147,34 @@ For information on using a search, see the section on <<ldap-searchobjects,searc
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
[[servlet-authentication-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
[[servlet-authentication-ldap-authenticators-password]]
=== PasswordComparisonAuthenticator
The class `PasswordComparisonAuthenticator` implements the password comparison authentication strategy.
[[ldap-context-source]]
=== Connecting to the LDAP Server
[[servlet-authentication-ldap-authenticators-connect]]
== 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]]
[[servlet-authentication-ldap-search]]
=== 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
[[servlet-authentication-ldap-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 https://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.
@ -192,7 +182,7 @@ For this class, the only valid parameter is `{0}` which will be replaced with th
[[ldap-authorities]]
=== LdapAuthoritiesPopulator
== 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.
@ -200,7 +190,7 @@ 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
== Spring Bean Configuration
A typical configuration, using some of the beans we've discussed here, might look like this:
[source,xml]
@ -255,7 +245,7 @@ The authenticator would then call the search object to obtain the correct user's
[[ldap-custom-user-details]]
=== LDAP Attributes and Customized UserDetails
== 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.
@ -289,7 +279,7 @@ Typically authentication is performed using the domain username (in the form `us
To make this easier, Spring Security 3.1 has an authentication provider which is customized for a typical Active Directory setup.
=== ActiveDirectoryLdapAuthenticationProvider
== 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.].
@ -315,7 +305,7 @@ By default, the user authorities are obtained from the `memberOf` attribute valu
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
=== 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.
@ -386,3 +376,24 @@ objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
----
[[servlet-authentication-ldap-using]]
== 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).

View File

@ -1,4 +1,4 @@
[[jc-authentication-userdetailsservice]]
[[servlet-authentication-userdetailsservice]]
== UserDetailsService
You can define custom authentication by exposing a custom `UserDetailsService` as a bean.