Add remaining servlet Kotlin examples

Issue gh-8172
This commit is contained in:
Eleftheria Stein 2021-06-16 10:31:29 +02:00
parent 1b72e9d4e0
commit 56fd50fa2f
15 changed files with 2695 additions and 167 deletions

View File

@ -196,7 +196,9 @@ This will be different in different companies, so you have to find it out yourse
Before adding a Spring Security LDAP configuration to an application, it's a good idea to write a simple test using standard Java LDAP code (without Spring Security involved), and make sure you can get that to work first.
For example, to authenticate a user, you could use the following code:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Test
@ -214,6 +216,22 @@ public void ldapAuthenticationIsSuccessful() throws Exception {
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test
fun ldapAuthenticationIsSuccessful() {
val env = Hashtable<String, String>()
env[Context.SECURITY_AUTHENTICATION] = "simple"
env[Context.SECURITY_PRINCIPAL] = "cn=joe,ou=users,dc=mycompany,dc=com"
env[Context.PROVIDER_URL] = "ldap://mycompany.com:389/dc=mycompany,dc=com"
env[Context.SECURITY_CREDENTIALS] = "joespassword"
env[Context.INITIAL_CONTEXT_FACTORY] = "com.sun.jndi.ldap.LdapCtxFactory"
val ctx = InitialLdapContext(env, null)
}
----
====
==== Session Management
Session management issues are a common source of forum questions.
@ -498,7 +516,9 @@ To load the data from an alternative source, you must be using an explicitly dec
You can't use the namespace.
You would then implement `FilterInvocationSecurityMetadataSource` to load the data as you please for a particular `FilterInvocation` footnote:[The `FilterInvocation` object contains the `HttpServletRequest`, so you can obtain the URL or any other relevant information on which to base your decision on what the list of returned attributes will contain.]. A very basic outline would look something like this:
[source,java]
====
.Java
[source,java,role="primary"]
----
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@ -526,6 +546,31 @@ You would then implement `FilterInvocationSecurityMetadataSource` to load the da
----
.Kotlin
[source,kotlin,role="secondary"]
----
class MyFilterSecurityMetadataSource : FilterInvocationSecurityMetadataSource {
override fun getAttributes(securedObject: Any): List<ConfigAttribute> {
val fi = securedObject as FilterInvocation
val url = fi.requestUrl
val httpMethod = fi.request.method
// Lookup your database (or other source) using this information and populate the
// list of attributes
return ArrayList()
}
override fun getAllConfigAttributes(): Collection<ConfigAttribute>? {
return null
}
override fun supports(clazz: Class<*>): Boolean {
return FilterInvocation::class.java.isAssignableFrom(clazz)
}
}
----
====
For more information, look at the code for `DefaultFilterInvocationSecurityMetadataSource`.
@ -537,7 +582,9 @@ The `DefaultLdapAuthoritiesPopulator` loads the user authorities from the LDAP d
To use JDBC instead, you can implement the interface yourself, using whatever SQL is appropriate for your schema:
[source,java]
====
.Java
[source,java,role="primary"]
----
public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator {
@ -562,6 +609,28 @@ To use JDBC instead, you can implement the interface yourself, using whatever SQ
----
.Kotlin
[source,kotlin,role="secondary"]
----
class MyAuthoritiesPopulator : LdapAuthoritiesPopulator {
@Autowired
lateinit var template: JdbcTemplate
override fun getGrantedAuthorities(userData: DirContextOperations, username: String): MutableList<GrantedAuthority?> {
return template.query("select role from roles where username = ?",
arrayOf(username)
) { rs, _ ->
/**
* We're assuming here that you're using the standard convention of using the role
* prefix "ROLE_" to mark attributes which are supported by Spring Security's RoleVoter.
*/
SimpleGrantedAuthority("ROLE_" + rs.getString(1))
}
}
}
----
====
You would then add a bean of this type to your application context and inject it into the `LdapAuthenticationProvider`. This is covered in the section on configuring LDAP using explicit Spring beans in the LDAP chapter of the reference manual.
Note that you can't use the namespace for configuration in this case.
You should also consult the Javadoc for the relevant classes and interfaces.
@ -578,7 +647,9 @@ More information can be found in the https://docs.spring.io/spring/docs/3.0.x/sp
Normally, you would add the functionality you require to the `postProcessBeforeInitialization` method of `BeanPostProcessor`. Let's say that you want to customize the `AuthenticationDetailsSource` used by the `UsernamePasswordAuthenticationFilter`, (created by the `form-login` element). You want to extract a particular header called `CUSTOM_HEADER` from the request and make use of it while authenticating the user.
The processor class would look like this:
[source,java]
====
.Java
[source,java,role="primary"]
----
public class CustomBeanPostProcessor implements BeanPostProcessor {
@ -603,5 +674,25 @@ public class CustomBeanPostProcessor implements BeanPostProcessor {
----
.Kotlin
[source,kotlin,role="secondary"]
----
class CustomBeanPostProcessor : BeanPostProcessor {
override fun postProcessAfterInitialization(bean: Any, name: String): Any {
if (bean is UsernamePasswordAuthenticationFilter) {
println("********* Post-processing $name")
bean.setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, Any?> { context -> context.getHeader("CUSTOM_HEADER") })
}
return bean
}
override fun postProcessBeforeInitialization(bean: Any, name: String?): Any {
return bean
}
}
----
====
You would then register this bean in your application context.
Spring will automatically invoke it on the beans defined in the application context.

View File

@ -340,7 +340,9 @@ Now that Spring Security obtains PGTs, you can use them to create proxy tickets
The CAS <<samples,sample application>> contains a working example in the `ProxyTicketSampleServlet`.
Example code can be found below:
[source,java]
====
.Java
[source,java,role="primary"]
----
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
@ -358,6 +360,24 @@ String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
protected fun doGet(request: HttpServletRequest, response: HttpServletResponse?) {
// NOTE: The CasAuthenticationToken can also be obtained using
// SecurityContextHolder.getContext().getAuthentication()
val token = request.userPrincipal as CasAuthenticationToken
// proxyTicket could be reused to make calls to the CAS service even if the
// target url differs
val proxyTicket = token.assertion.principal.getProxyTicketFor(targetUrl)
// Make a remote call using the proxy ticket
val serviceUrl: String = targetUrl + "?ticket=" + URLEncoder.encode(proxyTicket, "UTF-8")
val proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8")
}
----
====
[[cas-pt]]
==== Proxy Ticket Authentication
The `CasAuthenticationProvider` distinguishes between stateful and stateless clients.

View File

@ -148,7 +148,9 @@ We do not intend to support non-long identifiers in Spring Security's ACL module
The following fragment of code shows how to create an `Acl`, or modify an existing `Acl`:
[source,java]
====
.Java
[source,java,role="primary"]
----
// Prepare the information we'd like in our access control entry (ACE)
ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
@ -168,6 +170,26 @@ acl.insertAce(acl.getEntries().length, p, sid, true);
aclService.updateAcl(acl);
----
.Kotlin
[source,kotlin,role="secondary"]
----
val oi: ObjectIdentity = ObjectIdentityImpl(Foo::class.java, 44)
val sid: Sid = PrincipalSid("Samantha")
val p: Permission = BasePermission.ADMINISTRATION
// Create or update the relevant ACL
var acl: MutableAcl? = null
acl = try {
aclService.readAclById(oi) as MutableAcl
} catch (nfe: NotFoundException) {
aclService.createAcl(oi)
}
// Now grant some permissions via an access control entry (ACE)
acl!!.insertAce(acl.entries.size, 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.

View File

@ -603,7 +603,9 @@ and it will be invoked after the `@PostAuthorize` interceptor.
We can enable annotation-based security using the `@EnableGlobalMethodSecurity` annotation on any `@Configuration` instance.
For example, the following would enable Spring Security's `@Secured` annotation.
[source,java]
====
.Java
[source,java,role="primary"]
----
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
@ -611,11 +613,23 @@ public class MethodSecurityConfig {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableGlobalMethodSecurity(securedEnabled = true)
open class MethodSecurityConfig {
// ...
}
----
====
Adding an annotation to a method (on a class or interface) would then limit the access to that method accordingly.
Spring Security's native annotation support defines a set of attributes for the method.
These will be passed to the AccessDecisionManager for it to make the actual decision:
[source,java]
====
.Java
[source,java,role="primary"]
----
public interface BankService {
@ -630,9 +644,27 @@ public Account post(Account account, double amount);
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
fun readAccount(id: Long): Account
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
fun findAccounts(): Array<Account>
@Secured("ROLE_TELLER")
fun post(account: Account, amount: Double): Account
}
----
====
Support for JSR-250 annotations can be enabled using
[source,java]
====
.Java
[source,java,role="primary"]
----
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
@ -640,10 +672,22 @@ public class MethodSecurityConfig {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableGlobalMethodSecurity(jsr250Enabled = true)
open class MethodSecurityConfig {
// ...
}
----
====
These are standards-based and allow simple role-based constraints to be applied but do not have the power Spring Security's native annotations.
To use the new expression-based syntax, you would use
[source,java]
====
.Java
[source,java,role="primary"]
----
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
@ -651,9 +695,21 @@ public class MethodSecurityConfig {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableGlobalMethodSecurity(prePostEnabled = true)
open class MethodSecurityConfig {
// ...
}
----
====
and the equivalent Java code would be
[source,java]
====
.Java
[source,java,role="primary"]
----
public interface BankService {
@ -668,13 +724,31 @@ public Account post(Account account, double amount);
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
interface BankService {
@PreAuthorize("isAnonymous()")
fun readAccount(id: Long): Account
@PreAuthorize("isAnonymous()")
fun findAccounts(): Array<Account>
@PreAuthorize("hasAuthority('ROLE_TELLER')")
fun post(account: Account, amount: Double): Account
}
----
====
=== GlobalMethodSecurityConfiguration
Sometimes you may need to perform operations that are more complicated than are possible with the `@EnableGlobalMethodSecurity` annotation allow.
For these instances, you can extend the `GlobalMethodSecurityConfiguration` ensuring that the `@EnableGlobalMethodSecurity` annotation is present on your subclass.
For example, if you wanted to provide a custom `MethodSecurityExpressionHandler`, you could use the following configuration:
[source,java]
====
.Java
[source,java,role="primary"]
----
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@ -686,6 +760,19 @@ public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableGlobalMethodSecurity(prePostEnabled = true)
open class MethodSecurityConfig : GlobalMethodSecurityConfiguration() {
override fun createExpressionHandler(): MethodSecurityExpressionHandler {
// ... create and return custom MethodSecurityExpressionHandler ...
return expressionHandler
}
}
----
====
For additional information about methods that can be overridden, refer to the `GlobalMethodSecurityConfiguration` Javadoc.
[[ns-global-method]]
@ -703,7 +790,9 @@ Adding an annotation to a method (on an class or interface) would then limit the
Spring Security's native annotation support defines a set of attributes for the method.
These will be passed to the `AccessDecisionManager` for it to make the actual decision:
[source,java]
====
.Java
[source,java,role="primary"]
----
public interface BankService {
@ -718,6 +807,23 @@ public Account post(Account account, double amount);
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
fun readAccount(id: Long): Account
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
fun findAccounts(): Array<Account>
@Secured("ROLE_TELLER")
fun post(account: Account, amount: Double): Account
}
----
====
Support for JSR-250 annotations can be enabled using
[source,xml]
@ -735,7 +841,9 @@ To use the new expression-based syntax, you would use
and the equivalent Java code would be
[source,java]
====
.Java
[source,java,role="primary"]
----
public interface BankService {
@ -750,6 +858,22 @@ public Account post(Account account, double amount);
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
interface BankService {
@PreAuthorize("isAnonymous()")
fun readAccount(id: Long): Account
@PreAuthorize("isAnonymous()")
fun findAccounts(): Array<Account>
@PreAuthorize("hasAuthority('ROLE_TELLER')")
fun post(account: Account, amount: Double): Account
}
----
====
Expression-based annotations are a good choice if you need to define simple rules that go beyond checking the role names against the user's list of authorities.
[NOTE]

View File

@ -14,7 +14,9 @@ It wraps a delegate `Runnable` in order to initialize the `SecurityContextHolder
It then invokes the delegate Runnable ensuring to clear the `SecurityContextHolder` afterwards.
The `DelegatingSecurityContextRunnable` looks something like this:
[source,java]
====
.Java
[source,java,role="primary"]
----
public void run() {
try {
@ -26,13 +28,29 @@ try {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
fun 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]
====
.Java
[source,java,role="primary"]
----
Runnable originalRunnable = new Runnable() {
public void run() {
@ -47,6 +65,19 @@ DelegatingSecurityContextRunnable wrappedRunnable =
new Thread(wrappedRunnable).start();
----
.Kotlin
[source,kotlin,role="secondary"]
----
val originalRunnable = Runnable {
// invoke secured service
}
val context: SecurityContext = SecurityContextHolder.getContext()
val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable, context)
Thread(wrappedRunnable).start()
----
====
The code above performs the following steps:
* Creates a `Runnable` that will be invoking our secured service.
@ -59,7 +90,9 @@ Since it is quite common to create a `DelegatingSecurityContextRunnable` with th
The following code is the same as the code above:
[source,java]
====
.Java
[source,java,role="primary"]
----
Runnable originalRunnable = new Runnable() {
public void run() {
@ -73,6 +106,19 @@ DelegatingSecurityContextRunnable wrappedRunnable =
new Thread(wrappedRunnable).start();
----
.Kotlin
[source,kotlin,role="secondary"]
----
val originalRunnable = Runnable {
// invoke secured service
}
val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable)
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.
@ -85,7 +131,9 @@ The design of `DelegatingSecurityContextExecutor` is very similar to that of `De
You can see an example of how it might be used below:
[source,java]
====
.Java
[source,java,role="primary"]
----
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
@ -106,6 +154,25 @@ public void run() {
executor.execute(originalRunnable);
----
.Kotlin
[source,kotlin,role="secondary"]
----
val context: SecurityContext = SecurityContextHolder.createEmptyContext()
val authentication: Authentication =
UsernamePasswordAuthenticationToken("user", "doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"))
context.authentication = authentication
val delegateExecutor = SimpleAsyncTaskExecutor()
val executor = DelegatingSecurityContextExecutor(delegateExecutor, context)
val originalRunnable = Runnable {
// invoke secured service
}
executor.execute(originalRunnable)
----
====
The code performs the following steps:
* Creates the `SecurityContext` to be used for our `DelegatingSecurityContextExecutor`.
@ -118,7 +185,9 @@ In this instance, the same `SecurityContext` will be used for every Runnable sub
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]
====
.Java
[source,java,role="primary"]
----
@Autowired
private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor
@ -133,6 +202,21 @@ executor.execute(originalRunnable);
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Autowired
lateinit var executor: Executor // becomes an instance of our DelegatingSecurityContextExecutor
fun submitRunnable() {
val originalRunnable = Runnable {
// invoke secured service
}
executor.execute(originalRunnable)
}
----
====
Now our code is unaware that the `SecurityContext` is being propagated to the `Thread`, then the `originalRunnable` is run, and then the `SecurityContextHolder` is cleared out.
In this example, the same user is being used to run 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``?
@ -140,13 +224,23 @@ This can be done by removing the `SecurityContext` argument from our `Delegating
For example:
[source,java]
====
.Java
[source,java,role="primary"]
----
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
----
.Kotlin
[source,kotlin,role="secondary"]
----
val delegateExecutor = SimpleAsyncTaskExecutor()
val executor = 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 running our `Runnable` with the same user that was used to invoke the `executor.execute(Runnable)` code.

View File

@ -8,7 +8,9 @@ If the request does not contain any cookies and Spring Security is first, the re
The easiest way to ensure that CORS is handled first is to use the `CorsFilter`.
Users can integrate the `CorsFilter` with Spring Security by providing a `CorsConfigurationSource` using the following:
[source,java]
====
.Java
[source,java,role="primary"]
----
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@ -33,6 +35,32 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableWebSecurity
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
// by default uses a Bean by the name of corsConfigurationSource
cors { }
// ...
}
}
@Bean
open fun corsConfigurationSource(): CorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = listOf("https://example.com")
configuration.allowedMethods = listOf("GET", "POST")
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", configuration)
return source
}
}
----
====
or in XML
[source,xml]
@ -48,7 +76,9 @@ or in XML
If you are using Spring MVC's CORS support, you can omit specifying the `CorsConfigurationSource` and Spring Security will leverage the CORS configuration provided to Spring MVC.
[source,java]
====
.Java
[source,java,role="primary"]
----
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@ -64,6 +94,23 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableWebSecurity
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
// if Spring MVC is on classpath and no CorsConfigurationSource is provided,
// Spring Security will use CORS configuration provided to Spring MVC
cors { }
// ...
}
}
}
----
====
or in XML
[source,xml]

View File

@ -10,7 +10,9 @@ It is not only useful but necessary to include the user in the queries to suppor
To use this support, add `org.springframework.security:spring-security-data` dependency and provide a bean of type `SecurityEvaluationContextExtension`.
In Java Configuration, this would look like:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
@ -18,6 +20,16 @@ public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension {
return SecurityEvaluationContextExtension()
}
----
====
In XML Configuration, this would look like:
[source,xml]
@ -31,7 +43,9 @@ In XML Configuration, this would look like:
Now Spring Security can be used within your queries.
For example:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Repository
public interface MessageRepository extends PagingAndSortingRepository<Message,Long> {
@ -40,6 +54,17 @@ public interface MessageRepository extends PagingAndSortingRepository<Message,Lo
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Repository
interface MessageRepository : PagingAndSortingRepository<Message?, Long?> {
@Query("select m from Message m where m.to.id = ?#{ principal?.id }")
fun findInbox(pageable: Pageable?): Page<Message?>?
}
----
====
This checks to see if the `Authentication.getPrincipal().getId()` is equal to the recipient of the `Message`.
Note that this example assumes you have customized the principal to be an Object that has an id property.
By exposing the `SecurityEvaluationContextExtension` bean, all of the <<common-expressions,Common Security Expressions>> are available within the Query.

View File

@ -6,7 +6,9 @@ This can improve the performance of serializing Spring Security related classes
To use it, register the `SecurityJackson2Modules.getModules(ClassLoader)` with `ObjectMapper` (https://github.com/FasterXML/jackson-databind[jackson-databind]):
[source,java]
====
.Java
[source,java,role="primary"]
----
ObjectMapper mapper = new ObjectMapper();
ClassLoader loader = getClass().getClassLoader();
@ -19,6 +21,21 @@ SecurityContext context = new SecurityContextImpl();
String json = mapper.writeValueAsString(context);
----
.Kotlin
[source,kotlin,role="secondary"]
----
val mapper = ObjectMapper()
val loader = javaClass.classLoader
val modules: MutableList<Module> = SecurityJackson2Modules.getModules(loader)
mapper.registerModules(modules)
// ... use ObjectMapper as normally ...
val context: SecurityContext = SecurityContextImpl()
// ...
val json: String = mapper.writeValueAsString(context)
----
====
[NOTE]
====
The following Spring Security modules provide Jackson support:

View File

@ -56,7 +56,9 @@ For a `web.xml` this means that you should place your configuration in the `Disp
Below `WebSecurityConfiguration` in placed in the ``DispatcherServlet``s `ApplicationContext`.
[source,java]
====
.Java
[source,java,role="primary"]
----
public class SecurityInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
@ -79,6 +81,28 @@ public class SecurityInitializer extends
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
class SecurityInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
override fun getRootConfigClasses(): Array<Class<*>>? {
return null
}
override fun getServletConfigClasses(): Array<Class<*>> {
return arrayOf(
RootConfiguration::class.java,
WebMvcConfiguration::class.java
)
}
override fun getServletMappings(): Array<String> {
return arrayOf("/")
}
}
----
====
[NOTE]
====
It is always recommended to provide authorization rules by matching on the `HttpServletRequest` and method security.
@ -90,15 +114,27 @@ This is what is known as https://en.wikipedia.org/wiki/Defense_in_depth_(computi
Consider a controller that is mapped as follows:
[source,java]
====
.Java
[source,java,role="primary"]
----
@RequestMapping("/admin")
public String admin() {
----
.Kotlin
[source,kotlin,role="secondary"]
----
@RequestMapping("/admin")
fun admin(): String {
----
====
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]
====
.Java
[source,java,role="primary"]
----
protected configure(HttpSecurity http) throws Exception {
http
@ -108,6 +144,19 @@ protected configure(HttpSecurity http) throws Exception {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(AntPathRequestMatcher("/admin"), hasRole("ADMIN"))
}
}
}
----
====
or in XML
[source,xml]
@ -128,7 +177,9 @@ 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]
====
.Java
[source,java,role="primary"]
----
protected configure(HttpSecurity http) throws Exception {
http
@ -138,6 +189,19 @@ protected configure(HttpSecurity http) throws Exception {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize("/admin", hasRole("ADMIN"))
}
}
}
----
====
or in XML
[source,xml]
@ -168,7 +232,9 @@ Once `AuthenticationPrincipalArgumentResolver` is properly configured, you can b
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]
====
.Java
[source,java,role="primary"]
----
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser() {
@ -180,9 +246,24 @@ public ModelAndView findMessagesForUser() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(): ModelAndView {
val authentication: Authentication = SecurityContextHolder.getContext().authentication
val custom: CustomUser? = if (authentication as CustomUser == null) null else authentication.principal
// .. 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]
====
.Java
[source,java,role="primary"]
----
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@ -195,12 +276,25 @@ public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser cust
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@AuthenticationPrincipal customUser: CustomUser?): ModelAndView {
// .. 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]
====
.Java
[source,java,role="primary"]
----
public class CustomUserUserDetails extends User {
// ...
@ -210,9 +304,25 @@ public class CustomUserUserDetails extends User {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
class CustomUserUserDetails(
username: String?,
password: String?,
authorities: MutableCollection<out GrantedAuthority>?
) : User(username, password, authorities) {
// ...
val customUser: CustomUser? = null
}
----
====
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]
====
.Java
[source,java,role="primary"]
----
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@ -225,10 +335,27 @@ public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "c
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
import org.springframework.security.core.annotation.AuthenticationPrincipal
// ...
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@AuthenticationPrincipal(expression = "customUser") customUser: CustomUser?): ModelAndView {
// .. find messages 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]
====
.Java
[source,java,role="primary"]
----
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@ -245,13 +372,36 @@ public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntity
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
import org.springframework.security.core.annotation.AuthenticationPrincipal
// ...
@PutMapping("/users/self")
open fun updateName(
@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") attachedCustomUser: CustomUser,
@RequestParam firstName: String?
): ModelAndView {
// 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]
====
.Java
[source,java,role="primary"]
----
@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ -260,10 +410,23 @@ This step is not strictly required, but assists in isolating your dependency to
public @interface CurrentUser {}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@AuthenticationPrincipal
annotation class 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]
====
.Java
[source,java,role="primary"]
----
@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) {
@ -272,6 +435,17 @@ public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@RequestMapping("/messages/inbox")
open fun findMessagesForUser(@CurrentUser customUser: CustomUser?): ModelAndView {
// .. find messages for this user and return them ...
}
----
====
[[mvc-async]]
=== Spring MVC Async Integration
@ -280,7 +454,9 @@ Spring Web MVC 3.2+ has excellent support for https://docs.spring.io/spring/docs
With no additional configuration, Spring Security will automatically setup the `SecurityContext` to the `Thread` that invokes a `Callable` returned by your controllers.
For example, the following method will automatically have its `Callable` invoked with the `SecurityContext` that was available when the `Callable` was created:
[source,java]
====
.Java
[source,java,role="primary"]
----
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {
@ -294,6 +470,19 @@ return new Callable<String>() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@RequestMapping(method = [RequestMethod.POST])
open fun processUpload(file: MultipartFile?): Callable<String> {
return Callable {
// ...
"someView"
}
}
----
====
[NOTE]
.Associating SecurityContext to Callable's
====
@ -360,7 +549,9 @@ If you use XML based configuration, you must add this yourself.
Once `CsrfTokenArgumentResolver` is properly configured, you can expose the `CsrfToken` to your static HTML based application.
[source,java]
====
.Java
[source,java,role="primary"]
----
@RestController
public class CsrfController {
@ -372,5 +563,18 @@ public class CsrfController {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@RestController
class CsrfController {
@RequestMapping("/csrf")
fun csrf(token: CsrfToken): CsrfToken {
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.

View File

@ -24,7 +24,9 @@ For example, you might have created a custom `UserDetailsService` that returns a
You could obtain this information with the following:
[source,java]
====
.Java
[source,java,role="primary"]
----
Authentication auth = httpServletRequest.getUserPrincipal();
// assume integrated custom UserDetails called MyCustomUserDetails
@ -34,6 +36,18 @@ String firstName = userDetails.getFirstName();
String lastName = userDetails.getLastName();
----
.Kotlin
[source,kotlin,role="secondary"]
----
val auth: Authentication = httpServletRequest.getUserPrincipal()
// assume integrated custom UserDetails called MyCustomUserDetails
// by default, typically instance of UserDetails
val userDetails: MyCustomUserDetails = auth.principal as MyCustomUserDetails
val firstName: String = userDetails.firstName
val lastName: String = userDetails.lastName
----
====
[NOTE]
====
It should be noted that it is typically bad practice to perform so much logic throughout your application.
@ -46,11 +60,20 @@ The https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.h
Typically users should not pass in the "ROLE_" prefix into this method since it is added automatically.
For example, if you want to determine if the current user has the authority "ROLE_ADMIN", you could use the following:
[source,java]
====
.Java
[source,java,role="primary"]
----
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
----
.Kotlin
[source,kotlin,role="secondary"]
----
val isAdmin: Boolean = httpServletRequest.isUserInRole("ADMIN")
----
====
This might be useful to determine if certain UI components should be displayed.
For example, you might display admin links only if the current user is an admin.
@ -70,7 +93,9 @@ If they are not authenticated, the configured AuthenticationEntryPoint will be u
The https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#login%28java.lang.String,%20java.lang.String%29[HttpServletRequest.login(String,String)] method can be used to authenticate the user with the current `AuthenticationManager`.
For example, the following would attempt to authenticate with the username "user" and password "password":
[source,java]
====
.Java
[source,java,role="primary"]
----
try {
httpServletRequest.login("user","password");
@ -79,6 +104,17 @@ httpServletRequest.login("user","password");
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
try {
httpServletRequest.login("user", "password")
} catch (ex: ServletException) {
// fail to authenticate
}
----
====
[NOTE]
====
It is not necessary to catch the ServletException if you want Spring Security to process the failed authentication attempt.
@ -99,7 +135,9 @@ The https://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html#start%2
Using Spring Security's concurrency support, Spring Security overrides the AsyncContext.start(Runnable) to ensure that the current SecurityContext is used when processing the Runnable.
For example, the following would output the current user's Authentication:
[source,java]
====
.Java
[source,java,role="primary"]
----
final AsyncContext async = httpServletRequest.startAsync();
async.start(new Runnable() {
@ -117,6 +155,24 @@ async.start(new Runnable() {
});
----
.Kotlin
[source,kotlin,role="secondary"]
----
val async: AsyncContext = httpServletRequest.startAsync()
async.start {
val authentication: Authentication = SecurityContextHolder.getContext().authentication
try {
val asyncResponse = async.response as HttpServletResponse
asyncResponse.status = HttpServletResponse.SC_OK
asyncResponse.writer.write(String.valueOf(authentication))
async.complete()
} catch (ex: Exception) {
throw RuntimeException(ex)
}
}
----
====
[[servletapi-async]]
==== Async Servlet Support
If you are using Java Based configuration, you are ready to go.
@ -161,7 +217,9 @@ Prior to Spring Security 3.2, the SecurityContext from the SecurityContextHolder
This can cause issues in an Async environment.
For example, consider the following:
[source,java]
====
.Java
[source,java,role="primary"]
----
httpServletRequest.startAsync();
new Thread("AsyncThread") {
@ -180,6 +238,26 @@ new Thread("AsyncThread") {
}.start();
----
.Kotlin
[source,kotlin,role="secondary"]
----
httpServletRequest.startAsync()
object : Thread("AsyncThread") {
override fun run() {
try {
// Do work
TimeUnit.SECONDS.sleep(1)
// Write to and commit the httpServletResponse
httpServletResponse.outputStream.flush()
} catch (ex: java.lang.Exception) {
ex.printStackTrace()
}
}
}.start()
----
====
The issue is that this Thread is not known to Spring Security, so the SecurityContext is not propagated to it.
This means when we commit the HttpServletResponse there is no SecurityContext.
When Spring Security automatically saved the SecurityContext on committing the HttpServletResponse it would lose our logged in user.

View File

@ -18,7 +18,9 @@ Spring Security 4.0 has introduced authorization support for WebSockets through
To configure authorization using Java Configuration, simply extend the `AbstractSecurityWebSocketMessageBrokerConfigurer` and configure the `MessageSecurityMetadataSourceRegistry`.
For example:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Configuration
public class WebSocketSecurityConfig
@ -31,6 +33,18 @@ public class WebSocketSecurityConfig
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Configuration
open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() { // <1> <2>
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
messages.simpDestMatchers("/user/**").authenticated() // <3>
}
}
----
====
This will ensure that:
<1> Any inbound CONNECT message requires a valid CSRF token to enforce <<websocket-sameorigin,Same Origin Policy>>
@ -70,7 +84,9 @@ Spring Security 4.0 has introduced authorization support for WebSockets through
To configure authorization using Java Configuration, simply extend the `AbstractSecurityWebSocketMessageBrokerConfigurer` and configure the `MessageSecurityMetadataSourceRegistry`.
For example:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@ -89,6 +105,24 @@ public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBro
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Configuration
open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() {
override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) {
messages
.nullDestMatcher().authenticated() // <1>
.simpSubscribeDestMatchers("/user/queue/errors").permitAll() // <2>
.simpDestMatchers("/app/**").hasRole("USER") // <3>
.simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") // <4>
.simpTypeMatchers(MESSAGE, SUBSCRIBE).denyAll() // <5>
.anyMessage().denyAll() // <6>
}
}
----
====
This will ensure that:
<1> Any message without a destination (i.e. anything other than Message type of MESSAGE or SUBSCRIBE) will require the user to be authenticated
@ -232,7 +266,9 @@ var token = "${_csrf.token}";
If you are using static HTML, you can expose the `CsrfToken` on a REST endpoint.
For example, the following would expose the `CsrfToken` on the URL /csrf
[source,java]
====
.Java
[source,java,role="primary"]
----
@RestController
public class CsrfController {
@ -244,6 +280,19 @@ public class CsrfController {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@RestController
class CsrfController {
@RequestMapping("/csrf")
fun csrf(token: CsrfToken): CsrfToken {
return token
}
}
----
====
The JavaScript can make a REST call to the endpoint and use the response to populate the headerName and the token.
We can now include the token in our Stomp client.
@ -266,7 +315,9 @@ stompClient.connect(headers, function(frame) {
If you want to allow other domains to access your site, you can disable Spring Security's protection.
For example, in Java Configuration you can use the following:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@ -280,6 +331,21 @@ public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBro
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Configuration
open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfigurer() {
// ...
override fun sameOriginDisabled(): Boolean {
return true
}
}
----
====
[[websocket-sockjs]]
=== Working with SockJS
@ -311,7 +377,9 @@ For example, the following will instruct Spring Security to use "X-Frame-Options
Similarly, you can customize frame options to use the same origin within Java Configuration using the following:
[source,java]
====
.Java
[source,java,role="primary"]
----
@EnableWebSecurity
public class WebSecurityConfig extends
@ -330,6 +398,25 @@ public class WebSecurityConfig extends
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableWebSecurity
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
// ...
headers {
frameOptions {
sameOrigin = true
}
}
}
}
}
----
====
[[websocket-sockjs-csrf]]
==== SockJS & Relaxing CSRF
@ -347,7 +434,9 @@ We can easily achieve this by providing a CSRF RequestMatcher.
Our Java Configuration makes this extremely easy.
For example, if our stomp endpoint is "/chat" we can disable CSRF protection for only URLs that start with "/chat/" using the following configuration:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Configuration
@EnableWebSecurity
@ -373,6 +462,30 @@ public class WebSecurityConfig
...
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Configuration
@EnableWebSecurity
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
csrf {
ignoringAntMatchers("/chat/**")
}
headers {
frameOptions {
sameOrigin = true
}
}
authorizeRequests {
// ...
}
// ...
----
====
If we are using XML based configuration, we can use the <<nsa-csrf-request-matcher-ref,csrf@request-matcher-ref>>.
For example:

View File

@ -502,6 +502,51 @@ public class OAuth2LoginConfig {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Configuration
open class OAuth2LoginConfig {
@EnableWebSecurity
open class OAuth2LoginSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2Login { }
}
}
}
@Bean
open fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(googleClientRegistration())
}
@Bean
open fun authorizedClientService(
clientRegistrationRepository: ClientRegistrationRepository?
): OAuth2AuthorizedClientService {
return InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository)
}
@Bean
open fun authorizedClientRepository(
authorizedClientService: OAuth2AuthorizedClientService?
): OAuth2AuthorizedClientRepository {
return AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService)
}
private fun googleClientRegistration(): ClientRegistration {
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.build()
}
}
----
.Xml
[source,xml,role="secondary"]
----

View File

@ -196,13 +196,27 @@ The resulting `Authentication#getPrincipal` is a Spring Security `Saml2Authentic
Any class that uses both Spring Security and OpenSAML should statically initialize `OpenSamlInitializationService` at the beginning of the class, like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
static {
OpenSamlInitializationService.initialize();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
companion object {
init {
OpenSamlInitializationService.initialize()
}
}
----
====
This replaces OpenSAML's `InitializationService#initialize`.
Occasionally, it can be valuable to customize how OpenSAML builds, marshalls, and unmarshalls SAML objects.
@ -211,7 +225,9 @@ In these circumstances, you may instead want to call `OpenSamlInitializationServ
For example, when sending an unsigned AuthNRequest, you may want to force reauthentication.
In that case, you can register your own `AuthnRequestMarshaller`, like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
static {
OpenSamlInitializationService.requireInitialize(factory -> {
@ -237,6 +253,34 @@ static {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
companion object {
init {
OpenSamlInitializationService.requireInitialize {
val marshaller = object : AuthnRequestMarshaller() {
override fun marshall(xmlObject: XMLObject, element: Element): Element {
configureAuthnRequest(xmlObject as AuthnRequest)
return super.marshall(xmlObject, element)
}
override fun marshall(xmlObject: XMLObject, document: Document): Element {
configureAuthnRequest(xmlObject as AuthnRequest)
return super.marshall(xmlObject, document)
}
private fun configureAuthnRequest(authnRequest: AuthnRequest) {
authnRequest.isForceAuthn = true
}
}
it.marshallerFactory.registerMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME, marshaller)
}
}
}
----
====
The `requireInitialize` method may only be called once per application instance.
[[servlet-saml2login-sansboot]]
@ -327,7 +371,8 @@ For example, you can look up the asserting party's configuration by hitting its
.Relying Party Registration Repository
====
[source,java]
.Java
[source,java,role="primary"]
----
@Value("${metadata.location}")
String assertingPartyMetadataLocation;
@ -341,13 +386,30 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
return new InMemoryRelyingPartyRegistrationRepository(registration);
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Value("\${metadata.location}")
var assertingPartyMetadataLocation: String? = null
@Bean
open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
val registration = RelyingPartyRegistrations
.fromMetadataLocation(assertingPartyMetadataLocation)
.registrationId("example")
.build()
return InMemoryRelyingPartyRegistrationRepository(registration)
}
----
====
Or you can provide each detail manually, as you can see below:
.Relying Party Registration Repository Manual Configuration
====
[source,java]
.Java
[source,java,role="primary"]
----
@Value("${verification.key}")
File verificationKey;
@ -368,6 +430,34 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exc
return new InMemoryRelyingPartyRegistrationRepository(registration);
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Value("\${verification.key}")
var verificationKey: File? = null
@Bean
open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository {
val certificate: X509Certificate? = X509Support.decodeCertificate(verificationKey!!)
val credential: Saml2X509Credential = Saml2X509Credential.verification(certificate)
val registration = RelyingPartyRegistration
.withRegistrationId("example")
.assertingPartyDetails { party: AssertingPartyDetails.Builder ->
party
.entityId("https://idp.example.com/issuer")
.singleSignOnServiceLocation("https://idp.example.com/SSO.saml2")
.wantAuthnRequestsSigned(false)
.verificationX509Credentials { c: MutableCollection<Saml2X509Credential?> ->
c.add(
credential
)
}
}
.build()
return InMemoryRelyingPartyRegistrationRepository(registration)
}
----
====
[NOTE]
@ -431,7 +521,9 @@ Also, you can provide asserting party metadata like its `Issuer` value, where it
The following `RelyingPartyRegistration` is the minimum required for most setups:
[source,java]
====
.Java
[source,java,role="primary"]
----
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata")
@ -439,9 +531,21 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
.build();
----
.Kotlin
[source,kotlin,role="secondary"]
----
val relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata")
.registrationId("my-id")
.build()
----
====
Though a more sophisticated setup is also possible, like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("my-id")
.entityId("{baseUrl}/{registrationId}")
@ -455,6 +559,25 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit
.build();
----
.Kotlin
[source,kotlin,role="secondary"]
----
val relyingPartyRegistration =
RelyingPartyRegistration.withRegistrationId("my-id")
.entityId("{baseUrl}/{registrationId}")
.decryptionX509Credentials { c: MutableCollection<Saml2X509Credential?> ->
c.add(relyingPartyDecryptingCredential())
}
.assertionConsumerServiceLocation("/my-login-endpoint/{registrationId}")
.assertingPartyDetails { party -> party
.entityId("https://ap.example.org")
.verificationX509Credentials { c -> c.add(assertingPartyVerifyingCredential()) }
.singleSignOnServiceLocation("https://ap.example.org/SSO.saml2")
}
.build()
----
====
[TIP]
The top-level metadata methods are details about the relying party.
The methods inside `assertingPartyDetails` are details about the asserting party.
@ -512,7 +635,9 @@ At a minimum, it's necessary to have a certificate from the asserting party so t
To construct a `Saml2X509Credential` that you'll use to verify assertions from the asserting party, you can load the file and use
the `CertificateFactory` like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
Resource resource = new ClassPathResource("ap.crt");
try (InputStream is = resource.getInputStream()) {
@ -522,13 +647,27 @@ try (InputStream is = resource.getInputStream()) {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
val resource = ClassPathResource("ap.crt")
resource.inputStream.use {
return Saml2X509Credential.verification(
CertificateFactory.getInstance("X.509").generateCertificate(it) as X509Certificate?
)
}
----
====
Let's say that the asserting party is going to also encrypt the assertion.
In that case, the relying party will need a private key to be able to decrypt the encrypted value.
In that case, you'll need an `RSAPrivateKey` as well as its corresponding `X509Certificate`.
You can load the first using Spring Security's `RsaKeyConverters` utility class and the second as you did before:
[source,java]
====
.Java
[source,java,role="primary"]
----
X509Certificate certificate = relyingPartyDecryptionCertificate();
Resource resource = new ClassPathResource("rp.crt");
@ -538,6 +677,18 @@ try (InputStream is = resource.getInputStream()) {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
val certificate: X509Certificate = relyingPartyDecryptionCertificate()
val resource = ClassPathResource("rp.crt")
resource.inputStream.use {
val rsa: RSAPrivateKey = RsaKeyConverters.pkcs8().convert(it)
return Saml2X509Credential.decryption(rsa, certificate)
}
----
====
[TIP]
When you specify the locations of these files as the appropriate Spring Boot properties, then Spring Boot will perform these conversions for you.
@ -556,7 +707,9 @@ The default looks up the registration id from the URI's last path element and lo
You can provide a simpler resolver that, for example, always returns the same relying party:
[source,java]
====
.Java
[source,java,role="primary"]
----
public class SingleRelyingPartyRegistrationResolver
implements Converter<HttpServletRequest, RelyingPartyRegistration> {
@ -568,6 +721,17 @@ public class SingleRelyingPartyRegistrationResolver
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
class SingleRelyingPartyRegistrationResolver : Converter<HttpServletRequest?, RelyingPartyRegistration?> {
override fun convert(request: HttpServletRequest?): RelyingPartyRegistration? {
return this.relyingParty
}
}
----
====
Then, you can provide this resolver to the appropriate filters that <<servlet-saml2login-sp-initiated-factory, produce `<saml2:AuthnRequest>` s>>, <<servlet-saml2login-authenticate-responses, authenticate `<saml2:Response>` s>>, and <<servlet-saml2login-metadata, produce `<saml2:SPSSODescriptor>` metadata>>.
[NOTE]
@ -610,7 +774,9 @@ Second, in a database, it's not necessary to replicate `RelyingPartyRegistration
Third, in Java, you can create a custom configuration method, like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
private RelyingPartyRegistration.Builder
addRelyingPartyDetails(RelyingPartyRegistration.Builder builder) {
@ -636,6 +802,36 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
private fun addRelyingPartyDetails(builder: RelyingPartyRegistration.Builder): RelyingPartyRegistration.Builder {
val signingCredential: Saml2X509Credential = ...
builder.signingX509Credentials { c: MutableCollection<Saml2X509Credential?> ->
c.add(
signingCredential
)
}
// ... other relying party configurations
}
@Bean
open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
val okta = addRelyingPartyDetails(
RelyingPartyRegistrations
.fromMetadataLocation(oktaMetadataUrl)
.registrationId("okta")
).build()
val azure = addRelyingPartyDetails(
RelyingPartyRegistrations
.fromMetadataLocation(oktaMetadataUrl)
.registrationId("azure")
).build()
return InMemoryRelyingPartyRegistrationRepository(okta, azure)
}
----
====
[[servlet-saml2login-sp-initiated-factory]]
=== Producing `<saml2:AuthnRequest>` s
@ -682,7 +878,21 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit
.assertingPartyDetails(party -> party
// ...
.wantAuthnRequestsSigned(false)
);
)
.build();
----
.Kotlin
[source,java,role="secondary"]
----
var relyingPartyRegistration: RelyingPartyRegistration =
RelyingPartyRegistration.withRegistrationId("okta")
// ...
.assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
// ...
.wantAuthnRequestsSigned(false)
}
.build();
----
====
@ -695,7 +905,9 @@ You can configure the algorithm based on the asserting party's <<servlet-saml2lo
Or, you can provide it manually:
[source,java]
====
.Java
[source,java,role="primary"]
----
String metadataLocation = "classpath:asserting-party-metadata.xml";
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
@ -703,9 +915,29 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fr
.assertingPartyDetails((party) -> party
// ...
.signingAlgorithms((sign) -> sign.add(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512))
);
)
.build();
----
.Kotlin
[source,kotlin,role="secondary"]
----
var metadataLocation = "classpath:asserting-party-metadata.xml"
var relyingPartyRegistration: RelyingPartyRegistration =
RelyingPartyRegistrations.fromMetadataLocation(metadataLocation)
// ...
.assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
// ...
.signingAlgorithms { sign: MutableList<String?> ->
sign.add(
SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA512
)
}
}
.build();
----
====
NOTE: The snippet above uses the OpenSAML `SignatureConstants` class to supply the algorithm name.
But, that's just for convenience.
Since the datatype is `String`, you can supply the name of the algorithm directly.
@ -714,16 +946,32 @@ Since the datatype is `String`, you can supply the name of the algorithm directl
Some asserting parties require that the `<saml2:AuthnRequest>` be POSTed.
This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
// ...
.assertingPartyDetails(party -> party
// ...
.singleSignOnServiceBinding(Saml2MessageType.POST)
);
.singleSignOnServiceBinding(Saml2MessageBinding.POST)
)
.build();
----
.Kotlin
[source,kotlin,role="secondary"]
----
var relyingPartyRegistration: RelyingPartyRegistration? =
RelyingPartyRegistration.withRegistrationId("okta")
// ...
.assertingPartyDetails { party: AssertingPartyDetails.Builder -> party
// ...
.singleSignOnServiceBinding(Saml2MessageBinding.POST)
}
.build()
----
====
[[servlet-saml2login-sp-initiated-factory-custom-authnrequest]]
==== Customizing OpenSAML's `AuthnRequest` Instance
@ -736,7 +984,9 @@ This will give you access to post-process the `AuthnRequest` instance before it'
But, if you do need something from the request, then you can use create a custom `Saml2AuthenticationRequestContext` implementation and then a `Converter<Saml2AuthenticationRequestContext, AuthnRequest>` to build an `AuthnRequest` yourself, like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Component
public class AuthnRequestConverter implements
@ -765,9 +1015,37 @@ public class AuthnRequestConverter implements
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Component
class AuthnRequestConverter : Converter<MySaml2AuthenticationRequestContext, AuthnRequest> {
private val authnRequestBuilder: AuthnRequestBuilder? = null
private val issuerBuilder: IssuerBuilder? = null
// ... constructor
override fun convert(context: MySaml2AuthenticationRequestContext): AuthnRequest {
val myContext: MySaml2AuthenticationRequestContext = context
val issuer: Issuer = issuerBuilder.buildObject()
issuer.value = myContext.getIssuer()
val authnRequest: AuthnRequest = authnRequestBuilder.buildObject()
authnRequest.issuer = issuer
authnRequest.destination = myContext.getDestination()
authnRequest.assertionConsumerServiceURL = myContext.getAssertionConsumerServiceUrl()
// ... additional settings
authRequest.setForceAuthn(myContext.getForceAuthn())
return authnRequest
}
}
----
====
Then, you can construct your own `Saml2AuthenticationRequestContextResolver` and `Saml2AuthenticationRequestFactory` and publish them as `@Bean` s:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Bean
Saml2AuthenticationRequestContextResolver authenticationRequestContextResolver() {
@ -790,6 +1068,32 @@ Saml2AuthenticationRequestFactory authenticationRequestFactory(
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
open fun authenticationRequestContextResolver(): Saml2AuthenticationRequestContextResolver {
val resolver: Saml2AuthenticationRequestContextResolver = DefaultSaml2AuthenticationRequestContextResolver()
return Saml2AuthenticationRequestContextResolver { request: HttpServletRequest ->
val context = resolver.resolve(request)
MySaml2AuthenticationRequestContext(
context,
request.getParameter("force") != null
)
}
}
@Bean
open fun authenticationRequestFactory(
authnRequestConverter: AuthnRequestConverter?
): Saml2AuthenticationRequestFactory? {
val authenticationRequestFactory = OpenSamlAuthenticationRequestFactory()
authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter)
return authenticationRequestFactory
}
----
====
[[servlet-saml2login-authenticate-responses]]
=== Authenticating `<saml2:Response>` s
@ -810,7 +1114,9 @@ To configure these, you'll use the `saml2Login#authenticationManager` method in
It's not uncommon for the asserting and relying parties to have system clocks that aren't perfectly synchronized.
For that reason, you can configure `OpenSamlAuthenticationProvider` 's default assertion validator with some tolerance:
[source,java]
====
.Java
[source,java,role="primary"]
----
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@ -838,13 +1144,44 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableWebSecurity
open class SecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
val authenticationProvider = OpenSamlAuthenticationProvider()
authenticationProvider.setAssertionValidator(
OpenSamlAuthenticationProvider
.createDefaultAssertionValidator(Converter<OpenSamlAuthenticationProvider.AssertionToken, ValidationContext> {
val params: MutableMap<String, Any> = HashMap()
params[CLOCK_SKEW] =
Duration.ofMinutes(10).toMillis()
ValidationContext(params)
})
)
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
saml2Login {
authenticationManager = ProviderManager(authenticationProvider)
}
}
}
}
----
====
[[servlet-saml2login-opensamlauthenticationprovider-userdetailsservice]]
==== Coordinating with a `UserDetailsService`
Or, perhaps you would like to include user details from a legacy `UserDetailsService`.
In that case, the response authentication converter can come in handy, as can be seen below:
[source,java]
====
.Java
[source,java,role="primary"]
----
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@ -874,6 +1211,38 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableWebSecurity
open class SecurityConfig : WebSecurityConfigurerAdapter() {
@Autowired
var userDetailsService: UserDetailsService? = null
override fun configure(http: HttpSecurity) {
val authenticationProvider = OpenSamlAuthenticationProvider()
authenticationProvider.setResponseAuthenticationConverter { responseToken: OpenSamlAuthenticationProvider.ResponseToken ->
val authentication = OpenSamlAuthenticationProvider
.createDefaultResponseAuthenticationConverter() <1>
.convert(responseToken)
val assertion: Assertion = responseToken.response.assertions[0]
val username: String = assertion.subject.nameID.value
val userDetails = userDetailsService!!.loadUserByUsername(username) <2>
MySaml2Authentication(userDetails, authentication) <3>
}
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
saml2Login {
authenticationManager = ProviderManager(authenticationProvider)
}
}
}
}
----
====
<1> First, call the default converter, which extracts attributes and authorities from the response
<2> Second, call the <<servlet-authentication-userdetailsservice, `UserDetailsService`>> using the relevant information
<3> Third, return a custom authentication that includes the user details
@ -896,7 +1265,9 @@ To perform additional validation, you can configure your own assertion validator
[[servlet-saml2login-opensamlauthenticationprovider-onetimeuse]]
For example, you can use OpenSAML's `OneTimeUseConditionValidator` to also validate a `<OneTimeUse>` condition, like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
OneTimeUseConditionValidator validator = ...;
@ -918,6 +1289,30 @@ provider.setAssertionValidator(assertionToken -> {
});
----
.Kotlin
[source,kotlin,role="secondary"]
----
var provider = OpenSamlAuthenticationProvider()
var validator: OneTimeUseConditionValidator = ...
provider.setAssertionValidator { assertionToken ->
val result = OpenSamlAuthenticationProvider
.createDefaultAssertionValidator()
.convert(assertionToken)
val assertion: Assertion = assertionToken.assertion
val oneTimeUse: OneTimeUse = assertion.conditions.oneTimeUse
val context = ValidationContext()
try {
if (validator.validate(oneTimeUse, assertion, context) == ValidationResult.VALID) {
return@setAssertionValidator result
}
} catch (e: Exception) {
return@setAssertionValidator result.concat(Saml2Error(INVALID_ASSERTION, e.message))
}
result.concat(Saml2Error(INVALID_ASSERTION, context.validationFailureMessage))
}
----
====
[NOTE]
While recommended, it's not necessary to call `OpenSamlAuthenticationProvider` 's default assertion validator.
A circumstance where you would skip it would be if you don't need it to check the `<AudienceRestriction>` or the `<SubjectConfirmation>` since you are doing those yourself.
@ -934,20 +1329,40 @@ The assertion decrypter is for decrypting encrypted elements of the `<saml2:Asse
You can replace `OpenSamlAuthenticationProvider`'s default decryption strategy with your own.
For example, if you have a separate service that decrypts the assertions in a `<saml2:Response>`, you can use it instead like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
MyDecryptionService decryptionService = ...;
OpenSamlAuthenticationProvider provider = new OpenSamlAuthenticationProvider();
provider.setResponseElementsDecrypter((responseToken) -> decryptionService.decrypt(responseToken.getResponse()));
----
.Kotlin
[source,kotlin,role="secondary"]
----
val decryptionService: MyDecryptionService = ...
val provider = OpenSamlAuthenticationProvider()
provider.setResponseElementsDecrypter { responseToken -> decryptionService.decrypt(responseToken.response) }
----
====
If you are also decrypting individual elements in a `<saml2:Assertion>`, you can customize the assertion decrypter, too:
[source,java]
====
.Java
[source,java,role="primary"]
----
provider.setAssertionElementsDecrypter((assertionToken) -> decryptionService.decrypt(assertionToken.getAssertion()));
----
.Kotlin
[source,kotlin,role="secondary"]
----
provider.setAssertionElementsDecrypter { assertionToken -> decryptionService.decrypt(assertionToken.assertion) }
----
====
NOTE: There are two separate decrypters since assertions can be signed separately from responses.
Trying to decrypt a signed assertion's elements before signature verification may invalidate the signature.
If your asserting party signs the response only, then it's safe to decrypt all elements using only the response decrypter.
@ -959,7 +1374,9 @@ If your asserting party signs the response only, then it's safe to decrypt all e
Of course, the `authenticationManager` DSL method can be also used to perform a completely custom SAML 2.0 authentication.
This authentication manager should expect a `Saml2AuthenticationToken` object containing the SAML 2.0 Response XML data.
[source,java]
====
.Java
[source,java,role="primary"]
----
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@ -979,6 +1396,26 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableWebSecurity
open class SecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
val customAuthenticationManager: AuthenticationManager = MySaml2AuthenticationManager(...)
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
saml2Login {
authenticationManager = customAuthenticationManager
}
}
}
}
----
====
[[servlet-saml2login-authenticatedprincipal]]
=== Using `Saml2AuthenticatedPrincipal`
@ -987,7 +1424,9 @@ Once the relying party validates an assertion, the result is a `Saml2Authenticat
This means that you can access the principal in your controller like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Controller
public class MainController {
@ -1000,6 +1439,21 @@ public class MainController {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Controller
class MainController {
@GetMapping("/")
fun index(@AuthenticationPrincipal principal: Saml2AuthenticatedPrincipal, model: Model): String {
val email = principal.getFirstAttribute<String>("email")
model.setAttribute("email", email)
return "index"
}
}
----
====
[TIP]
Because the SAML 2.0 specification allows for each attribute to have multiple values, you can either call `getAttribute` to get the list of attributes or `getFirstAttribute` to get the first in the list.
`getFirstAttribute` is quite handy when you know that there is only one value.
@ -1009,7 +1463,9 @@ Because the SAML 2.0 specification allows for each attribute to have multiple va
You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below:
[source,java]
====
.Java
[source,java,role="primary"]
----
Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver =
new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository);
@ -1023,26 +1479,62 @@ http
.addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
----
.Kotlin
[source,kotlin,role="secondary"]
----
val relyingPartyRegistrationResolver: Converter<HttpServletRequest, RelyingPartyRegistration> =
DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrationRepository)
val filter = Saml2MetadataFilter(
relyingPartyRegistrationResolver,
OpenSamlMetadataResolver()
)
http {
//...
saml2Login { }
addFilterBefore<Saml2WebSsoAuthenticationFilter>(filter)
}
----
====
You can use this metadata endpoint to register your relying party with your asserting party.
This is often as simple as finding the correct form field to supply the metadata endpoint.
By default, the metadata endpoint is `+/saml2/service-provider-metadata/{registrationId}+`.
You can change this by calling the `setRequestMatcher` method on the filter:
[source,java]
====
.Java
[source,java,role="primary"]
----
filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata/{registrationId}", "GET"));
----
.Kotlin
[source,kotlin,role="secondary"]
----
filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata/{registrationId}", "GET"))
----
====
ensuring that the `registrationId` hint is at the end of the path.
Or, if you have registered a custom relying party registration resolver in the constructor, then you can specify a path without a `registrationId` hint, like so:
[source,java]
====
.Java
[source,java,role="primary"]
----
filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata", "GET"));
----
.Kotlin
[source,kotlin,role="secondary"]
----
filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata", "GET"))
----
====
[[servlet-saml2login-logout]]
=== Performing Single Logout
@ -1050,7 +1542,9 @@ Spring Security does not yet support single logout.
Generally speaking, though, you can achieve this by creating and registering a custom `LogoutSuccessHandler` and `RequestMatcher`:
[source,java]
====
.Java
[source,java,role="primary"]
----
http
// ...
@ -1060,6 +1554,19 @@ http
)
----
.Kotlin
[source,kotlin,role="secondary"]
----
http {
logout {
// ...
logoutSuccessHandler = myCustomSuccessHandler()
logoutRequestMatcher = myRequestMatcher()
}
}
----
====
The success handler will send logout requests to the asserting party.
The request matcher will detect logout requests from the asserting party.

View File

@ -30,13 +30,24 @@ Hello org.springframework.security.authentication.UsernamePasswordAuthentication
Before we can use Spring Security Test support, we must perform some setup. An example can be seen below:
[source,java]
====
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class) // <1>
@ContextConfiguration // <2>
public class WithMockUserTests {
----
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ContextConfiguration
class WithMockUserTests {
----
====
This is a basic example of how to setup Spring Security Test. The highlights are:
<1> `@RunWith` instructs the spring-test module that it should create an `ApplicationContext`. This is no different than using the existing Spring Test support. For additional information, refer to the https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#integration-testing-annotations-standard[Spring Reference]
@ -51,7 +62,9 @@ If you only need Spring Security related support, you can replace `@ContextConfi
Remember we added the `@PreAuthorize` annotation to our `HelloMessageService` and so it requires an authenticated user to invoke it.
If we ran the following test, we would expect the following test will pass:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void getMessageUnauthenticated() {
@ -59,6 +72,16 @@ public void getMessageUnauthenticated() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test(expected = AuthenticationCredentialsNotFoundException::class)
fun getMessageUnauthenticated() {
messageService.getMessage()
}
----
====
[[test-method-withmockuser]]
=== @WithMockUser
@ -66,7 +89,9 @@ The question is "How could we most easily run the test as a specific user?"
The answer is to use `@WithMockUser`.
The following test will be run as a user with the username "user", the password "password", and the roles "ROLE_USER".
[source,java]
====
.Java
[source,java,role="primary"]
----
@Test
@WithMockUser
@ -76,6 +101,18 @@ String message = messageService.getMessage();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test
@WithMockUser
fun getMessageWithMockUser() {
val message: String = messageService.getMessage()
// ...
}
----
====
Specifically the following is true:
* The user with the username "user" does not have to exist since we are mocking the user
@ -87,7 +124,9 @@ Our example is nice because we are able to leverage a lot of defaults.
What if we wanted to run the test with a different username?
The following test would run with the username "customUser". Again, the user does not need to actually exist.
[source,java]
====
.Java
[source,java,role="primary"]
----
@Test
@WithMockUser("customUsername")
@ -97,10 +136,24 @@ public void getMessageWithMockUserCustomUsername() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test
@WithMockUser("customUsername")
fun getMessageWithMockUserCustomUsername() {
val message: String = messageService.getMessage()
// ...
}
----
====
We can also easily customize the roles.
For example, this test will be invoked with the username "admin" and the roles "ROLE_USER" and "ROLE_ADMIN".
[source,java]
====
.Java
[source,java,role="primary"]
----
@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
@ -110,10 +163,24 @@ public void getMessageWithMockUserCustomUser() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test
@WithMockUser(username="admin",roles=["USER","ADMIN"])
fun getMessageWithMockUserCustomUser() {
val message: String = messageService.getMessage()
// ...
}
----
====
If we do not want the value to automatically be prefixed with ROLE_ we can leverage the authorities attribute.
For example, this test will be invoked with the username "admin" and the authorities "USER" and "ADMIN".
[source,java]
====
.Java
[source,java,role="primary"]
----
@Test
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
@ -123,11 +190,25 @@ public void getMessageWithMockUserCustomAuthorities() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test
@WithMockUser(username = "admin", authorities = ["ADMIN", "USER"])
fun getMessageWithMockUserCustomUsername() {
val message: String = messageService.getMessage()
// ...
}
----
====
Of course it can be a bit tedious placing the annotation on every test method.
Instead, we can place the annotation at the class level and every test will use the specified user.
For example, the following would run every test with a user with the username "admin", the password "password", and the roles "ROLE_USER" and "ROLE_ADMIN".
[source,java]
====
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@ -135,10 +216,22 @@ For example, the following would run every test with a user with the username "a
public class WithMockUserTests {
----
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@ContextConfiguration
@WithMockUser(username="admin",roles=["USER","ADMIN"])
class WithMockUserTests {
----
====
If you are using JUnit 5's `@Nested` test support, you can also place the annotation on the enclosing class to apply to all nested classes.
For example, the following would run every test with a user with the username "admin", the password "password", and the roles "ROLE_USER" and "ROLE_ADMIN" for both test methods.
[source,java]
====
.Java
[source,java,role="primary"]
----
@ExtendWith(SpringExtension.class)
@ContextConfiguration
@ -157,6 +250,24 @@ public class WithMockUserTests {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@ExtendWith(SpringExtension::class)
@ContextConfiguration
@WithMockUser(username = "admin", roles = ["USER", "ADMIN"])
class WithMockUserTests {
@Nested
inner class TestSuite1 { // ... all test methods use admin user
}
@Nested
inner class TestSuite2 { // ... all test methods use admin user
}
}
----
====
By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
This is the equivalent of happening before JUnit's `@Before`.
You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked.
@ -174,7 +285,9 @@ Using `@WithAnonymousUser` allows running as an anonymous user.
This is especially convenient when you wish to run most of your tests with a specific user, but want to run a few tests as an anonymous user.
For example, the following will run withMockUser1 and withMockUser2 using <<test-method-withmockuser,@WithMockUser>> and anonymous as an anonymous user.
[source,java]
====
.Java
[source,java,role="primary"]
----
@RunWith(SpringJUnit4ClassRunner.class)
@WithMockUser
@ -196,6 +309,29 @@ public class WithUserClassLevelAuthenticationTests {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@RunWith(SpringJUnit4ClassRunner::class)
@WithMockUser
class WithUserClassLevelAuthenticationTests {
@Test
fun withMockUser1() {
}
@Test
fun withMockUser2() {
}
@Test
@WithAnonymousUser
fun anonymous() {
// override default to run as anonymous user
}
}
----
====
By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
This is the equivalent of happening before JUnit's `@Before`.
You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked.
@ -219,7 +355,9 @@ That is exactly what `@WithUserDetails` does.
Assuming we have a `UserDetailsService` exposed as a bean, the following test will be invoked with an `Authentication` of type `UsernamePasswordAuthenticationToken` and a principal that is returned from the `UserDetailsService` with the username of "user".
[source,java]
====
.Java
[source,java,role="primary"]
----
@Test
@WithUserDetails
@ -229,10 +367,24 @@ public void getMessageWithUserDetails() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test
@WithUserDetails
fun getMessageWithUserDetails() {
val message: String = messageService.getMessage()
// ...
}
----
====
We can also customize the username used to lookup the user from our `UserDetailsService`.
For example, this test would be run with a principal that is returned from the `UserDetailsService` with the username of "customUsername".
[source,java]
====
.Java
[source,java,role="primary"]
----
@Test
@WithUserDetails("customUsername")
@ -242,10 +394,24 @@ public void getMessageWithUserDetailsCustomUsername() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test
@WithUserDetails("customUsername")
fun getMessageWithUserDetailsCustomUsername() {
val message: String = messageService.getMessage()
// ...
}
----
====
We can also provide an explicit bean name to look up the `UserDetailsService`.
For example, this test would look up the username of "customUsername" using the `UserDetailsService` with the bean name "myUserDetailsService".
[source,java]
====
.Java
[source,java,role="primary"]
----
@Test
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
@ -255,6 +421,18 @@ public void getMessageWithUserDetailsServiceBeanName() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Test
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
fun getMessageWithUserDetailsServiceBeanName() {
val message: String = messageService.getMessage()
// ...
}
----
====
Like `@WithMockUser` we can also place our annotation at the class level so that every test uses the same user.
However unlike `@WithMockUser`, `@WithUserDetails` requires the user to exist.
@ -278,7 +456,9 @@ We will now see an option that allows the most flexibility.
We can create our own annotation that uses the `@WithSecurityContext` to create any `SecurityContext` we want.
For example, we might create an annotation named `@WithMockCustomUser` as shown below:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
@ -290,12 +470,23 @@ public @interface WithMockCustomUser {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Retention(AnnotationRetention.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class)
annotation class WithMockCustomUser(val username: String = "rob", val name: String = "Rob Winch")
----
====
You can see that `@WithMockCustomUser` is annotated with the `@WithSecurityContext` annotation.
This is what signals to Spring Security Test support that we intend to create a `SecurityContext` for the test.
The `@WithSecurityContext` annotation requires we specify a `SecurityContextFactory` that will create a new `SecurityContext` given our `@WithMockCustomUser` annotation.
You can find our `WithMockCustomUserSecurityContextFactory` implementation below:
[source,java]
====
.Java
[source,java,role="primary"]
----
public class WithMockCustomUserSecurityContextFactory
implements WithSecurityContextFactory<WithMockCustomUser> {
@ -313,12 +504,30 @@ public class WithMockCustomUserSecurityContextFactory
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory<WithMockCustomUser> {
override fun createSecurityContext(customUser: WithMockCustomUser): SecurityContext {
val context = SecurityContextHolder.createEmptyContext()
val principal = CustomUserDetails(customUser.name, customUser.username)
val auth: Authentication =
UsernamePasswordAuthenticationToken(principal, "password", principal.authorities)
context.authentication = auth
return context
}
}
----
====
We can now annotate a test class or a test method with our new annotation and Spring Security's `WithSecurityContextTestExecutionListener` will ensure that our `SecurityContext` is populated appropriately.
When creating your own `WithSecurityContextFactory` implementations, it is nice to know that they can be annotated with standard Spring annotations.
For example, the `WithUserDetailsSecurityContextFactory` uses the `@Autowired` annotation to acquire the `UserDetailsService`:
[source,java]
====
.Java
[source,java,role="primary"]
----
final class WithUserDetailsSecurityContextFactory
implements WithSecurityContextFactory<WithUserDetails> {
@ -342,6 +551,25 @@ final class WithUserDetailsSecurityContextFactory
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
class WithUserDetailsSecurityContextFactory @Autowired constructor(private val userDetailsService: UserDetailsService) :
WithSecurityContextFactory<WithUserDetails> {
override fun createSecurityContext(withUser: WithUserDetails): SecurityContext {
val username: String = withUser.value
Assert.hasLength(username, "value() must be non-empty String")
val principal = userDetailsService.loadUserByUsername(username)
val authentication: Authentication =
UsernamePasswordAuthenticationToken(principal, principal.password, principal.authorities)
val context = SecurityContextHolder.createEmptyContext()
context.authentication = authentication
return context
}
}
----
====
By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
This is the equivalent of happening before JUnit's `@Before`.
You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked.
@ -358,21 +586,41 @@ You can change this to happen during the `TestExecutionListener.beforeTestExecut
If you reuse the same user within your tests often, it is not ideal to have to repeatedly specify the attributes.
For example, if there are many tests related to an administrative user with the username "admin" and the roles `ROLE_USER` and `ROLE_ADMIN` you would have to write:
[source,java]
====
.Java
[source,java,role="primary"]
----
@WithMockUser(username="admin",roles={"USER","ADMIN"})
----
.Kotlin
[source,kotlin,role="secondary"]
----
@WithMockUser(username="admin",roles=["USER","ADMIN"])
----
====
Rather than repeating this everywhere, we can use a meta annotation.
For example, we could create a meta annotation named `WithMockAdmin`:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(value="rob",roles="ADMIN")
public @interface WithMockAdmin { }
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Retention(AnnotationRetention.RUNTIME)
@WithMockUser(value = "rob", roles = ["ADMIN"])
annotation class WithMockAdmin
----
====
Now we can use `@WithMockAdmin` in the same way as the more verbose `@WithMockUser`.
Meta annotations work with any of the testing annotations described above.