Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 096ce047c4 | |||
| c5d48fe3a9 | |||
| 22a9300003 | |||
| 44c8a1ec27 | |||
| ed00ddabf6 | |||
| a8c642370b | |||
| bd9df5cd70 | |||
| 4d6cc92bcd | |||
| 1aa315a6b0 | |||
| a9e884fb02 | |||
| f98f0c86b8 | |||
| 86a19b482c | |||
| 52ad9547a7 | |||
| 752beab1b0 | |||
| f882f0dda7 | |||
| a232bd2890 | |||
| 3824c90cea | |||
| 00506b32f1 | |||
| ae1028eda2 | |||
| 000d02d1ad | |||
| e140147dd6 | |||
| 676c456a77 | |||
| 5d30357ab8 | |||
| 176a2746e4 | |||
| 840d5d48bd | |||
| c268e62b7f | |||
| eadbc7cba5 | |||
| d2d093999f | |||
| 83a1ac26ab | |||
| 2b8c489c31 | |||
| ed638a42f9 | |||
| 6b99d523c6 | |||
| 57a0aeba56 | |||
| 69758b0ca7 | |||
| d678202a23 | |||
| c026dec505 | |||
| f6cd3fa826 | |||
| 783caa4aaf | |||
| 01310d10e6 | |||
| 5f996c8700 | |||
| 14bda4cb03 | |||
| 3f72e3eb19 | |||
| ff3f559094 | |||
| 8299e71b06 | |||
| 1349a733de | |||
| d2179e0e78 | |||
| d0f93fa6d8 | |||
| e5694ac7b5 | |||
| ad86ae0a79 | |||
| 9de0aad31b | |||
| 7293fc023b | |||
| f7b380e3f3 | |||
| 94ec06496e | |||
| 8b924e9dc5 | |||
| e8ace55031 | |||
| 7c909ca1a5 | |||
| fcb6223f90 | |||
| ed3ed28b3c | |||
| 704a324273 | |||
| 0fc884f592 | |||
| 6162e71e18 | |||
| bf8dba5681 | |||
| d4184819da | |||
| 1b8f5dae57 | |||
| d3e372b659 | |||
| a2f67c17b3 | |||
| 627de76f67 | |||
| 5d55c8d13a | |||
| 342b9c3539 | |||
| 537845491e | |||
| 1ac2b31168 | |||
| fa95c42dd3 | |||
| aa1f60f7ec | |||
| f0ea11fa5e | |||
| d473b21872 | |||
| 13e6a505da | |||
| e68738dd94 | |||
| 9f32f62d34 | |||
| 9fb4db12a5 | |||
| f493388513 | |||
| 85af6abe9e | |||
| ab634d1099 | |||
| a79a2b031a | |||
| eca821471f | |||
| 3d25473ee6 | |||
| cc30c901c7 | |||
| 35f09461ef | |||
| 8f75b4c350 | |||
| 84bc892997 | |||
| dd986b0932 | |||
| 653f22d4a1 | |||
| f54c293078 | |||
| 34fccf45c2 | |||
| f840ee06eb | |||
| 8429c23108 | |||
| 97f3567702 | |||
| d2e934ca54 | |||
| fee4d08de3 | |||
| eeb67650ee | |||
| b4fc01194f | |||
| c982753d46 | |||
| 0ff9f10696 | |||
| 7e3bf9662c | |||
| 9d64880ea9 | |||
| 8b2a453301 | |||
| 857ca9c412 | |||
| 894105aab5 | |||
| 312c2049f3 | |||
| 3dd4686feb | |||
| 8a1e2a22f9 | |||
| 0404996f87 | |||
| 0f63d98c84 | |||
| fbfbb1e571 | |||
| d002e68231 | |||
| 62b5b1a77c | |||
| 523222c24d | |||
| 69f38d4933 | |||
| 0179a811c7 | |||
| de4ceffc4f | |||
| 8c920a7ee7 | |||
| b9653346a1 | |||
| 9e6bcbd1d0 | |||
| 5453224377 | |||
| a14ad770ab | |||
| 8846b44b81 | |||
| c06ff59747 | |||
| f1194de45e | |||
| 1f16009c8d | |||
| 3e13437b1a | |||
| 20a6863e07 | |||
| e145c07d5b | |||
| 3e8b606aac | |||
| a933089ec2 | |||
| a8da9ec7f7 | |||
| acaed1ad96 | |||
| 0549bf566b | |||
| 01c8cea00f |
+6
-2
@@ -83,8 +83,12 @@ public class VerifyDependenciesVersionsPlugin implements Plugin<Project> {
|
||||
String transitiveNimbusJoseJwtVersion = TransitiveDependencyLookupUtils.lookupJwtVersion(oauth2OidcSdkVersion);
|
||||
String expectedNimbusJoseJwtVersion = this.getExpectedNimbusJoseJwtVersion().get();
|
||||
if (!transitiveNimbusJoseJwtVersion.equals(expectedNimbusJoseJwtVersion)) {
|
||||
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, but the project contains a different version of nimbus-jose-jwt [%s]. Please align the versions.", transitiveNimbusJoseJwtVersion, oauth2OidcSdkVersion, expectedNimbusJoseJwtVersion);
|
||||
throw new VerificationException(message);
|
||||
String transitiveNimbusJoseJwtMajorMinorVersion = transitiveNimbusJoseJwtVersion.substring(0, transitiveNimbusJoseJwtVersion.lastIndexOf("."));
|
||||
String expectedNimbusJoseJwtMajorMinorVersion = expectedNimbusJoseJwtVersion.substring(0, expectedNimbusJoseJwtVersion.lastIndexOf("."));
|
||||
if (!transitiveNimbusJoseJwtMajorMinorVersion.equals(expectedNimbusJoseJwtMajorMinorVersion)) {
|
||||
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, but the project contains a different version of nimbus-jose-jwt [%s]. Please align the major/minor versions.", transitiveNimbusJoseJwtVersion, oauth2OidcSdkVersion, expectedNimbusJoseJwtVersion);
|
||||
throw new VerificationException(message);
|
||||
}
|
||||
}
|
||||
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, the project contains expected version of nimbus-jose-jwt [%s]. Verified all versions align.", transitiveNimbusJoseJwtVersion, oauth2OidcSdkVersion, expectedNimbusJoseJwtVersion);
|
||||
try {
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector
|
||||
* @property channelProcessors the [ChannelProcessor] instances to use in
|
||||
* [ChannelDecisionManagerImpl]
|
||||
*/
|
||||
@Deprecated(message="since 6.5 use redirectToHttps instead")
|
||||
class RequiresChannelDsl : AbstractRequestMatcherDsl() {
|
||||
private val channelSecurityRules = mutableListOf<AuthorizationRule>()
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@ import org.springframework.security.saml2.provider.service.registration.OpenSaml
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
|
||||
import org.springframework.security.web.PortResolverImpl;
|
||||
import org.springframework.security.web.authentication.AuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
|
||||
@@ -457,7 +458,7 @@ final class SerializationSamples {
|
||||
generatorByClassName.put(AuthenticationSuccessEvent.class,
|
||||
(r) -> new AuthenticationSuccessEvent(authentication));
|
||||
generatorByClassName.put(InteractiveAuthenticationSuccessEvent.class,
|
||||
(r) -> new InteractiveAuthenticationSuccessEvent(authentication, Authentication.class));
|
||||
(r) -> new InteractiveAuthenticationSuccessEvent(authentication, AuthenticationFilter.class));
|
||||
generatorByClassName.put(LogoutSuccessEvent.class, (r) -> new LogoutSuccessEvent(authentication));
|
||||
generatorByClassName.put(JaasAuthenticationFailedEvent.class,
|
||||
(r) -> new JaasAuthenticationFailedEvent(authentication, new RuntimeException("message")));
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-1
@@ -305,7 +305,7 @@ final class UniqueSecurityAnnotationScanner<A extends Annotation> extends Abstra
|
||||
}
|
||||
for (int i = 0; i < rootParameterTypes.length; i++) {
|
||||
Class<?> resolvedParameterType = ResolvableType.forMethodParameter(candidateMethod, i, sourceDeclaringClass)
|
||||
.resolve();
|
||||
.toClass();
|
||||
if (rootParameterTypes[i] != resolvedParameterType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+32
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -326,6 +327,14 @@ public class UniqueSecurityAnnotationScannerTests {
|
||||
.isThrownBy(() -> this.parameterScanner.scan(parameter));
|
||||
}
|
||||
|
||||
// gh-17898
|
||||
@Test
|
||||
void scanWhenAnnotationOnParameterizedUndeclaredMethodAndThenLocates() throws Exception {
|
||||
Method method = ClassUtils.getMethod(GenericInterfaceImpl.class, "processOneAndTwo", Long.class, Object.class);
|
||||
PreAuthorize pre = this.scanner.scan(method, method.getDeclaringClass());
|
||||
assertThat(pre).isNotNull();
|
||||
}
|
||||
|
||||
interface UserService {
|
||||
|
||||
void add(@CustomParameterAnnotation("one") String user);
|
||||
@@ -764,4 +773,27 @@ public class UniqueSecurityAnnotationScannerTests {
|
||||
|
||||
}
|
||||
|
||||
interface GenericInterface<A, B> {
|
||||
|
||||
@PreAuthorize("hasAuthority('thirtythree')")
|
||||
void processOneAndTwo(A value1, B value2);
|
||||
|
||||
}
|
||||
|
||||
abstract static class GenericAbstractSuperclass<C> implements GenericInterface<Long, C> {
|
||||
|
||||
@Override
|
||||
public void processOneAndTwo(Long value1, C value2) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GenericInterfaceImpl extends GenericAbstractSuperclass<String> {
|
||||
|
||||
// The compiler does not require us to declare a concrete
|
||||
// processOneAndTwo(Long, String) method, and we intentionally
|
||||
// do not declare one here.
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -546,6 +546,14 @@ open class BankService {
|
||||
The result is that the above method will only return the `Account` if its `owner` attribute matches the logged-in user's `name`.
|
||||
If not, Spring Security will throw an `AccessDeniedException` and return a 403 status code.
|
||||
|
||||
[NOTE]
|
||||
=====
|
||||
Note that `@PostAuthorize` is not recommended for classes that perform database writes since that typically means that a database change was made before the security invariants were checked.
|
||||
A common example of doing this is if you have `@Transactional` and `@PostAuthorize` on the same method.
|
||||
Instead, read the value first, using `@PostAuthorize` on the read, and then perform the database write, should that read is authorized.
|
||||
If you must do something like this, you can <<changing-the-order, ensure that `@EnableTransactionManagement` comes before `@EnableMethodSecurity`>>.
|
||||
=====
|
||||
|
||||
[[use-prefilter]]
|
||||
=== Filtering Method Parameters with `@PreFilter`
|
||||
|
||||
@@ -1797,39 +1805,7 @@ As already noted, there is a Spring AOP method interceptor for each annotation,
|
||||
|
||||
Namely, the `@PreFilter` method interceptor's order is 100, ``@PreAuthorize``'s is 200, and so on.
|
||||
|
||||
The reason this is important to note is that there are other AOP-based annotations like `@EnableTransactionManagement` that have an order of `Integer.MAX_VALUE`.
|
||||
In other words, they are located at the end of the advisor chain by default.
|
||||
|
||||
At times, it can be valuable to have other advice execute before Spring Security.
|
||||
For example, if you have a method annotated with `@Transactional` and `@PostAuthorize`, you might want the transaction to still be open when `@PostAuthorize` runs so that an `AccessDeniedException` will cause a rollback.
|
||||
|
||||
To get `@EnableTransactionManagement` to open a transaction before method authorization advice runs, you can set ``@EnableTransactionManagement``'s order like so:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableTransactionManagement(order = 0)
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableTransactionManagement(order = 0)
|
||||
----
|
||||
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<tx:annotation-driven ref="txManager" order="0"/>
|
||||
----
|
||||
======
|
||||
|
||||
Since the earliest method interceptor (`@PreFilter`) is set to an order of 100, a setting of zero means that the transaction advice will run before all Spring Security advice.
|
||||
You can use the `offset` parameter on `@EnableMethodSecurity` to move all interceptors en masse to provide their advice earlier or later in a method invocation.
|
||||
|
||||
[[authorization-expressions]]
|
||||
== Expressing Authorization with SpEL
|
||||
|
||||
@@ -4,36 +4,7 @@
|
||||
This section demonstrates how to use Spring Security's Test support to test method-based security.
|
||||
We first introduce a `MessageService` that requires the user to be authenticated to be able to access it:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class HelloMessageService implements MessageService {
|
||||
|
||||
@PreAuthorize("authenticated")
|
||||
public String getMessage() {
|
||||
Authentication authentication = SecurityContextHolder.getContext()
|
||||
.getAuthentication();
|
||||
return "Hello " + authentication;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class HelloMessageService : MessageService {
|
||||
@PreAuthorize("authenticated")
|
||||
fun getMessage(): String {
|
||||
val authentication: Authentication = SecurityContextHolder.getContext().authentication
|
||||
return "Hello $authentication"
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./HelloMessageService[tag=authenticated,indent=0]
|
||||
|
||||
The result of `getMessage` is a `String` that says "`Hello`" to the current Spring Security `Authentication`.
|
||||
The following listing shows example output:
|
||||
@@ -48,30 +19,8 @@ Hello org.springframework.security.authentication.UsernamePasswordAuthentication
|
||||
|
||||
Before we can use the Spring Security test support, we must perform some setup:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class) // <1>
|
||||
@ContextConfiguration // <2>
|
||||
public class WithMockUserTests {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
include-code::./WithMockUserTests[tag=setup,indent=0]
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
class WithMockUserTests {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `@ExtendWith` instructs the spring-test module that it should create an `ApplicationContext`. For additional information, refer to the {spring-framework-reference-url}testing.html#testcontext-junit-jupiter-extension[Spring reference].
|
||||
<2> `@ContextConfiguration` instructs the spring-test the configuration to use to create the `ApplicationContext`. Since no configuration is specified, the default configuration locations will be tried. This is no different than using the existing Spring Test support. For additional information, refer to the {spring-framework-reference-url}testing.html#spring-testing-annotation-contextconfiguration[Spring Reference].
|
||||
|
||||
@@ -87,28 +36,7 @@ If you need only Spring Security related support, you can replace `@ContextConfi
|
||||
Remember, we added the `@PreAuthorize` annotation to our `HelloMessageService`, so it requires an authenticated user to invoke it.
|
||||
If we run the tests, we expect the following test will pass:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test(expected = AuthenticationCredentialsNotFoundException.class)
|
||||
public void getMessageUnauthenticated() {
|
||||
messageService.getMessage();
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test(expected = AuthenticationCredentialsNotFoundException::class)
|
||||
fun getMessageUnauthenticated() {
|
||||
messageService.getMessage()
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserSampleTests[tag=snippet,indent=0]
|
||||
|
||||
[[test-method-withmockuser]]
|
||||
== @WithMockUser
|
||||
@@ -117,32 +45,7 @@ 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".
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser
|
||||
public void getMessageWithMockUser() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser
|
||||
fun getMessageWithMockUser() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserTests[tag=mock-user,indent=0]
|
||||
|
||||
Specifically the following is true:
|
||||
|
||||
@@ -157,168 +60,28 @@ The preceding example is handy, because it lets us use a lot of defaults.
|
||||
What if we wanted to run the test with a different username?
|
||||
The following test would run with a username of `customUser` (again, the user does not need to actually exist):
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser("customUsername")
|
||||
public void getMessageWithMockUserCustomUsername() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser("customUsername")
|
||||
fun getMessageWithMockUserCustomUsername() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserTests[tag=custom-user,indent=0]
|
||||
|
||||
We can also easily customize the roles.
|
||||
For example, the following test is invoked with a username of `admin` and roles of `ROLE_USER` and `ROLE_ADMIN`.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"})
|
||||
public void getMessageWithMockUserCustomUser() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser(username="admin",roles=["USER","ADMIN"])
|
||||
fun getMessageWithMockUserCustomUser() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserTests[tag=custom-roles,indent=0]
|
||||
|
||||
If we do not want the value to automatically be prefixed with `ROLE_` we can use the `authorities` attribute.
|
||||
For example, the following test is invoked with a username of `admin` and the `USER` and `ADMIN` authorities.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
|
||||
public void getMessageWithMockUserCustomAuthorities() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithMockUser(username = "admin", authorities = ["ADMIN", "USER"])
|
||||
fun getMessageWithMockUserCustomUsername() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserTests[tag=custom-authorities,indent=0]
|
||||
|
||||
It can be a bit tedious to place the annotation on every test method.
|
||||
Instead, we can place the annotation at the class level. Then every test uses the specified user.
|
||||
The following example runs every test with a user whose username is `admin`, whose password is `password`, and who has the `ROLE_USER` and `ROLE_ADMIN` roles:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"})
|
||||
public class WithMockUserTests {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username="admin",roles=["USER","ADMIN"])
|
||||
class WithMockUserTests {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserClassTests[tag=snippet,indent=0]
|
||||
|
||||
If you use JUnit 5's `@Nested` test support, you can also place the annotation on the enclosing class to apply to all nested classes.
|
||||
The following example runs every test with a user whose username is `admin`, whose password is `password`, and who has the `ROLE_USER` and `ROLE_ADMIN` roles for both test methods.
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"})
|
||||
public class WithMockUserTests {
|
||||
|
||||
@Nested
|
||||
public class TestSuite1 {
|
||||
// ... all test methods use admin user
|
||||
}
|
||||
|
||||
@Nested
|
||||
public class TestSuite2 {
|
||||
// ... all test methods use admin user
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserNestedTests[tag=snippet,indent=0]
|
||||
|
||||
By default, the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
|
||||
This is the equivalent of happening before JUnit's `@Before`.
|
||||
@@ -337,55 +100,7 @@ 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.
|
||||
The following example runs `withMockUser1` and `withMockUser2` by using <<test-method-withmockuser,@WithMockUser>> and `anonymous` as an anonymous user:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WithMockUser
|
||||
public class WithUserClassLevelAuthenticationTests {
|
||||
|
||||
@Test
|
||||
public void withMockUser1() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMockUser2() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithAnonymousUser
|
||||
public void anonymous() throws Exception {
|
||||
// override default to run as anonymous user
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WithMockUser
|
||||
class WithUserClassLevelAuthenticationTests {
|
||||
@Test
|
||||
fun withMockUser1() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withMockUser2() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithAnonymousUser
|
||||
fun anonymous() {
|
||||
// override default to run as anonymous user
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithUserClassLevelAuthenticationTests[tag=snippet,indent=0]
|
||||
|
||||
By default, the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
|
||||
This is the equivalent of happening before JUnit's `@Before`.
|
||||
@@ -410,92 +125,17 @@ That is exactly what `@WithUserDetails` does.
|
||||
|
||||
Assuming we have a `UserDetailsService` exposed as a bean, the following test is invoked with an `Authentication` of type `UsernamePasswordAuthenticationToken` and a principal that is returned from the `UserDetailsService` with the username of `user`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails
|
||||
public void getMessageWithUserDetails() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails
|
||||
fun getMessageWithUserDetails() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithUserDetailsTests[tag=user-details,indent=0]
|
||||
|
||||
We can also customize the username used to lookup the user from our `UserDetailsService`.
|
||||
For example, this test can be run with a principal that is returned from the `UserDetailsService` with the username of `customUsername`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails("customUsername")
|
||||
public void getMessageWithUserDetailsCustomUsername() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails("customUsername")
|
||||
fun getMessageWithUserDetailsCustomUsername() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithUserDetailsTests[tag=user-details-custom-username,indent=0]
|
||||
|
||||
We can also provide an explicit bean name to look up the `UserDetailsService`.
|
||||
The following test looks up the username of `customUsername` by using the `UserDetailsService` with a bean name of `myUserDetailsService`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
|
||||
public void getMessageWithUserDetailsServiceBeanName() {
|
||||
String message = messageService.getMessage();
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
|
||||
fun getMessageWithUserDetailsServiceBeanName() {
|
||||
val message: String = messageService.getMessage()
|
||||
// ...
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithCustomUserDetailsTests[tag=custom-user-details-service,indent=0]
|
||||
|
||||
As we did with `@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.
|
||||
@@ -519,128 +159,21 @@ We 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`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
|
||||
public @interface WithMockCustomUser {
|
||||
|
||||
String username() default "rob";
|
||||
|
||||
String name() default "Rob Winch";
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class)
|
||||
annotation class WithMockCustomUser(val username: String = "rob", val name: String = "Rob Winch")
|
||||
----
|
||||
======
|
||||
include-code::./WithMockCustomUser[tag=snippet,indent=0]
|
||||
|
||||
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 that we specify a `SecurityContextFactory` to create a new `SecurityContext`, given our `@WithMockCustomUser` annotation.
|
||||
The following listing shows our `WithMockCustomUserSecurityContextFactory` implementation:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class WithMockCustomUserSecurityContextFactory
|
||||
implements WithSecurityContextFactory<WithMockCustomUser> {
|
||||
@Override
|
||||
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
|
||||
CustomUserDetails principal =
|
||||
new CustomUserDetails(customUser.name(), customUser.username());
|
||||
Authentication auth =
|
||||
UsernamePasswordAuthenticationToken.authenticated(principal, "password", principal.getAuthorities());
|
||||
context.setAuthentication(auth);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithMockCustomUserSecurityContextFactory[tag=snippet,indent=0]
|
||||
|
||||
We can now annotate a test class or a test method with our new annotation and Spring Security's `WithSecurityContextTestExecutionListener` to 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`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
final class WithUserDetailsSecurityContextFactory
|
||||
implements WithSecurityContextFactory<WithUserDetails> {
|
||||
|
||||
private UserDetailsService userDetailsService;
|
||||
|
||||
@Autowired
|
||||
public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
public SecurityContext createSecurityContext(WithUserDetails withUser) {
|
||||
String username = withUser.value();
|
||||
Assert.hasLength(username, "value() must be non-empty String");
|
||||
UserDetails principal = userDetailsService.loadUserByUsername(username);
|
||||
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(principal, principal.getPassword(), principal.getAuthorities());
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
context.setAuthentication(authentication);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
include-code::./WithUserDetailsSecurityContextFactory[tag=snippet,indent=0]
|
||||
|
||||
By default, the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event.
|
||||
This is the equivalent of happening before JUnit's `@Before`.
|
||||
@@ -658,46 +191,12 @@ 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 you have many tests related to an administrative user with a username of `admin` and roles of `ROLE_USER` and `ROLE_ADMIN`, you have to write:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@WithMockUser(username="admin",roles={"USER","ADMIN"})
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@WithMockUser(username="admin",roles=["USER","ADMIN"])
|
||||
----
|
||||
======
|
||||
include-code::./WithMockUserTests[tag=snippet,indent=0]
|
||||
|
||||
Rather than repeating this everywhere, we can use a meta annotation.
|
||||
For example, we could create a meta annotation named `WithMockAdmin`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
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
|
||||
----
|
||||
======
|
||||
include-code::./WithMockAdmin[tag=snippet,indent=0]
|
||||
|
||||
Now we can use `@WithMockAdmin` in the same way as the more verbose `@WithMockUser`.
|
||||
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethod;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
/**
|
||||
* A message service for demonstrating test support for method-based security.
|
||||
*/
|
||||
// tag::authenticated[]
|
||||
public class HelloMessageService implements MessageService {
|
||||
|
||||
@Override
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public String getMessage() {
|
||||
Authentication authentication = SecurityContextHolder.getContext()
|
||||
.getAuthentication();
|
||||
return "Hello " + authentication;
|
||||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public String getJsrMessage() {
|
||||
Authentication authentication = SecurityContextHolder.getContext()
|
||||
.getAuthentication();
|
||||
return "Hello JSR " + authentication;
|
||||
}
|
||||
}
|
||||
// end::authenticated[]
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethod;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
class HelloServiceTests {
|
||||
|
||||
@Autowired
|
||||
MessageService messageService;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
UsernamePasswordAuthenticationToken user = UsernamePasswordAuthenticationToken.authenticated("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContextHolder.getContext().setAuthentication(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
void helloServiceTest() {
|
||||
assertThat(messageService.getMessage())
|
||||
.contains("user")
|
||||
.contains("ROLE_USER");
|
||||
}
|
||||
|
||||
@EnableMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
MessageService messageService() {
|
||||
return new HelloMessageService();
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodmetaannotations;
|
||||
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
// tag::snippet[]
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@WithMockUser(value="rob",roles={"USER","ADMIN"})
|
||||
public @interface WithMockAdmin { }
|
||||
// end::snippet[]
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodmetaannotations;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.security.docs.servlet.test.testmethod.HelloMessageService;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
public class WithMockAdminTests {
|
||||
|
||||
@Autowired
|
||||
MessageService messageService;
|
||||
|
||||
@Test
|
||||
@WithMockAdmin
|
||||
void getMessageWithMockUserAdminRoles() {
|
||||
String message = messageService.getMessage();
|
||||
assertThat(message)
|
||||
.contains("rob")
|
||||
.contains("ROLE_ADMIN")
|
||||
.contains("ROLE_USER");
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
MessageService messageService() {
|
||||
return new HelloMessageService();
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodmetaannotations;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.security.docs.servlet.test.testmethod.HelloMessageService;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
public class WithMockUserTests {
|
||||
|
||||
@Autowired
|
||||
MessageService messageService;
|
||||
|
||||
@Test
|
||||
// tag::snippet[]
|
||||
@WithMockUser(username = "admin", roles = {"USER", "ADMIN"})
|
||||
// end::snippet[]
|
||||
void getMessageWithMockUserAdminRoles() {
|
||||
String message = messageService.getMessage();
|
||||
assertThat(message)
|
||||
.contains("admin")
|
||||
.contains("ROLE_ADMIN")
|
||||
.contains("ROLE_USER");
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
MessageService messageService() {
|
||||
return new HelloMessageService();
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodsetup;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.docs.servlet.test.testmethod.HelloMessageService;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
class WithMockUserSampleTests {
|
||||
|
||||
@Autowired
|
||||
MessageService messageService;
|
||||
|
||||
// tag::snippet[]
|
||||
@Test
|
||||
void getMessageUnauthenticated() {
|
||||
assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class)
|
||||
.isThrownBy(() -> messageService.getMessage());
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
MessageService messageService() {
|
||||
return new HelloMessageService();
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodsetup;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
// tag::setup[]
|
||||
@ExtendWith(SpringExtension.class) // <1>
|
||||
@ContextConfiguration // <2>
|
||||
class WithMockUserTests {
|
||||
// ...
|
||||
}
|
||||
// end::setup[]
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodwithanonymoususer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.security.test.context.support.WithAnonymousUser;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
// tag::snippet[]
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@WithMockUser
|
||||
class WithUserClassLevelAuthenticationTests {
|
||||
|
||||
@Test
|
||||
void withMockUser1() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void withMockUser2() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithAnonymousUser
|
||||
void anonymous() throws Exception {
|
||||
// override default to run as anonymous user
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodwithmockuser;
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
// tag::snippet[]
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username = "admin", roles = {"USER", "ADMIN"})
|
||||
class WithMockUserClassTests {
|
||||
// ...
|
||||
}
|
||||
// end::snippet[]
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodwithmockuser;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
// tag::snippet[]
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username = "admin", roles = {"USER", "ADMIN"})
|
||||
class WithMockUserNestedTests {
|
||||
|
||||
@Nested
|
||||
class TestSuite1 {
|
||||
// ... all test methods use admin user
|
||||
}
|
||||
|
||||
@Nested
|
||||
class TestSuite2 {
|
||||
// ... all test methods use admin user
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodwithmockuser;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.security.docs.servlet.test.testmethod.HelloMessageService;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
class WithMockUserTests {
|
||||
|
||||
@Autowired
|
||||
MessageService messageService;
|
||||
|
||||
// tag::mock-user[]
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getMessageWithMockUser() {
|
||||
String message = messageService.getMessage();
|
||||
assertThat(message).contains("user");
|
||||
}
|
||||
// end::mock-user[]
|
||||
|
||||
// tag::custom-user[]
|
||||
@Test
|
||||
@WithMockUser("customUser")
|
||||
void getMessageWithMockUserCustomUsername() {
|
||||
String message = messageService.getMessage();
|
||||
assertThat(message).contains("customUser");
|
||||
}
|
||||
// end::custom-user[]
|
||||
|
||||
// tag::custom-roles[]
|
||||
@Test
|
||||
@WithMockUser(username = "admin", roles = {"USER", "ADMIN"})
|
||||
void getMessageWithMockUserCustomRoles() {
|
||||
String message = messageService.getMessage();
|
||||
assertThat(message)
|
||||
.contains("admin")
|
||||
.contains("ROLE_ADMIN")
|
||||
.contains("ROLE_USER");
|
||||
}
|
||||
// end::custom-roles[]
|
||||
|
||||
// tag::custom-authorities[]
|
||||
@Test
|
||||
@WithMockUser(username = "admin", authorities = {"ADMIN", "USER"})
|
||||
public void getMessageWithMockUserCustomAuthorities() {
|
||||
String message = messageService.getMessage();
|
||||
assertThat(message)
|
||||
.contains("admin")
|
||||
.contains("ADMIN")
|
||||
.contains("USER")
|
||||
.doesNotContain("ROLE_");
|
||||
}
|
||||
// end::custom-authorities[]
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
MessageService messageService() {
|
||||
return new HelloMessageService();
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodwithsecuritycontext;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class CustomUserDetails implements UserDetails {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String username;
|
||||
|
||||
private final Collection<? extends GrantedAuthority> authorities;
|
||||
|
||||
public CustomUserDetails(String name, String username) {
|
||||
this.name = name;
|
||||
this.username = username;
|
||||
this.authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return this.authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CustomUserDetails{" + "username='" + this.username + '\'' + '}';
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodwithsecuritycontext;
|
||||
|
||||
import org.springframework.security.test.context.support.WithSecurityContext;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
// tag::snippet[]
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
|
||||
public @interface WithMockCustomUser {
|
||||
|
||||
String username() default "rob";
|
||||
|
||||
String name() default "Rob Winch";
|
||||
}
|
||||
// end::snippet[]
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodwithsecuritycontext;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.test.context.support.WithSecurityContextFactory;
|
||||
|
||||
// tag::snippet[]
|
||||
public class WithMockCustomUserSecurityContextFactory
|
||||
implements WithSecurityContextFactory<WithMockCustomUser> {
|
||||
|
||||
@Override
|
||||
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
CustomUserDetails principal = new CustomUserDetails(customUser.name(), customUser.username());
|
||||
Authentication auth = UsernamePasswordAuthenticationToken.authenticated(principal, "password",
|
||||
principal.getAuthorities());
|
||||
context.setAuthentication(auth);
|
||||
return context;
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodwithsecuritycontext;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.test.context.support.WithSecurityContextFactory;
|
||||
import org.springframework.security.test.context.support.WithUserDetails;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
// tag::snippet[]
|
||||
final class WithUserDetailsSecurityContextFactory
|
||||
implements WithSecurityContextFactory<WithUserDetails> {
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@Autowired
|
||||
public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
public SecurityContext createSecurityContext(WithUserDetails withUser) {
|
||||
String username = withUser.value();
|
||||
Assert.hasLength(username, "value() must be non-empty String");
|
||||
UserDetails principal = userDetailsService.loadUserByUsername(username);
|
||||
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(principal, principal.getPassword(), principal.getAuthorities());
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
context.setAuthentication(authentication);
|
||||
return context;
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodwithuserdetails;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.docs.servlet.test.testmethod.HelloMessageService;
|
||||
import org.springframework.security.docs.servlet.test.testmethodwithsecuritycontext.CustomUserDetails;
|
||||
import org.springframework.security.test.context.support.WithUserDetails;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
class WithCustomUserDetailsTests {
|
||||
|
||||
@Autowired
|
||||
MessageService messageService;
|
||||
|
||||
// tag::custom-user-details-service[]
|
||||
@Test
|
||||
@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService")
|
||||
void getMessageWithUserDetailsServiceBeanName() {
|
||||
String message = messageService.getMessage();
|
||||
assertThat(message).contains("customUsername");
|
||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
assertThat(principal).isInstanceOf(CustomUserDetails.class);
|
||||
}
|
||||
// end::custom-user-details-service[]
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
UserDetailsService myUserDetailsService() {
|
||||
return new CustomUserDetailsService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessageService messageService() {
|
||||
return new HelloMessageService();
|
||||
}
|
||||
}
|
||||
|
||||
static class CustomUserDetailsService implements UserDetailsService {
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
|
||||
return new CustomUserDetails("name", username);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.docs.servlet.test.testmethodwithuserdetails;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.core.MessageService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.docs.servlet.test.testmethod.HelloMessageService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.test.context.support.WithUserDetails;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
class WithUserDetailsTests {
|
||||
|
||||
@Autowired
|
||||
MessageService messageService;
|
||||
|
||||
// tag::user-details[]
|
||||
@Test
|
||||
@WithUserDetails
|
||||
void getMessageWithUserDetails() {
|
||||
String message = messageService.getMessage();
|
||||
assertThat(message).contains("user");
|
||||
}
|
||||
// end::user-details[]
|
||||
|
||||
// tag::user-details-custom-username[]
|
||||
@Test
|
||||
@WithUserDetails("customUsername")
|
||||
void getMessageWithUserDetailsCustomUsername() {
|
||||
String message = messageService.getMessage();
|
||||
assertThat(message).contains("customUsername");
|
||||
}
|
||||
// end::user-details-custom-username[]
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
UserDetails user1 = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.build();
|
||||
UserDetails customUser = User.withDefaultPasswordEncoder()
|
||||
.username("customUsername")
|
||||
.password("password")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user1, customUser);
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessageService messageService() {
|
||||
return new HelloMessageService();
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethod
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.security.config.core.MessageService
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
|
||||
/**
|
||||
* A message service for demonstrating test support for method-based security.
|
||||
*/
|
||||
// tag::authenticated[]
|
||||
class HelloMessageService : MessageService {
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
override fun getMessage(): String {
|
||||
val authentication: Authentication = SecurityContextHolder.getContext().authentication
|
||||
return "Hello $authentication"
|
||||
}
|
||||
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
override fun getJsrMessage(): String {
|
||||
val authentication = SecurityContextHolder.getContext().authentication
|
||||
return "Hello JSR $authentication"
|
||||
}
|
||||
}
|
||||
// end::authenticated[]
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethod
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.config.core.MessageService
|
||||
import org.springframework.security.core.authority.AuthorityUtils
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration
|
||||
class HelloServiceTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var messageService: MessageService
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
val user = UsernamePasswordAuthenticationToken.authenticated(
|
||||
"user",
|
||||
"password",
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER")
|
||||
)
|
||||
SecurityContextHolder.getContext().authentication = user
|
||||
}
|
||||
|
||||
@Test
|
||||
fun helloServiceTest() {
|
||||
assertThat(messageService.message)
|
||||
.contains("user")
|
||||
.contains("ROLE_USER")
|
||||
}
|
||||
|
||||
@EnableMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
|
||||
@Configuration
|
||||
open class Config {
|
||||
@Bean
|
||||
open fun messageService(): MessageService {
|
||||
return HelloMessageService()
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodmetaannotations
|
||||
|
||||
import org.springframework.security.test.context.support.WithMockUser
|
||||
|
||||
// tag::snippet[]
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@WithMockUser(value = "rob", roles = ["USER", "ADMIN"])
|
||||
annotation class WithMockAdmin
|
||||
// end::snippet[]
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodmetaannotations
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.config.core.MessageService
|
||||
import org.springframework.security.kt.docs.servlet.test.testmethod.HelloMessageService
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration
|
||||
class WithMockAdminTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var messageService: MessageService
|
||||
|
||||
@Test
|
||||
@WithMockAdmin
|
||||
fun getMessageWithMockUserAdminRoles() {
|
||||
val message = messageService.message
|
||||
assertThat(message)
|
||||
.contains("rob")
|
||||
.contains("ROLE_ADMIN")
|
||||
.contains("ROLE_USER")
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
open class Config {
|
||||
@Bean
|
||||
open fun messageService(): MessageService {
|
||||
return HelloMessageService()
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodmetaannotations
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.config.core.MessageService
|
||||
import org.springframework.security.kt.docs.servlet.test.testmethod.HelloMessageService
|
||||
import org.springframework.security.test.context.support.WithMockUser
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration
|
||||
class WithMockUserTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var messageService: MessageService
|
||||
|
||||
@Test
|
||||
// tag::snippet[]
|
||||
@WithMockUser(username = "admin", roles = ["USER", "ADMIN"])
|
||||
// end::snippet[]
|
||||
fun getMessageWithMockUserAdminRoles() {
|
||||
val message = messageService.message
|
||||
assertThat(message)
|
||||
.contains("admin")
|
||||
.contains("ROLE_ADMIN")
|
||||
.contains("ROLE_USER")
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
open class Config {
|
||||
@Bean
|
||||
open fun messageService(): MessageService {
|
||||
return HelloMessageService()
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodsetup
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.config.core.MessageService
|
||||
import org.springframework.security.kt.docs.servlet.test.testmethod.HelloMessageService
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration
|
||||
class WithMockUserSampleTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var messageService: MessageService
|
||||
|
||||
// tag::snippet[]
|
||||
@Test
|
||||
fun getMessageUnauthenticated() {
|
||||
assertThatExceptionOfType(AuthenticationCredentialsNotFoundException::class.java)
|
||||
.isThrownBy { messageService.getMessage() }
|
||||
}
|
||||
// end::snippet[]
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
open class Config {
|
||||
@Bean
|
||||
open fun messageService(): MessageService {
|
||||
return HelloMessageService()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodsetup
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
// tag::setup[]
|
||||
@ExtendWith(SpringExtension::class) // <1>
|
||||
@ContextConfiguration // <2>
|
||||
class WithMockUserTests {
|
||||
}
|
||||
// end::setup[]
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodwithanonymoususer
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.security.test.context.support.WithAnonymousUser
|
||||
import org.springframework.security.test.context.support.WithMockUser
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
|
||||
// tag::snippet[]
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@WithMockUser
|
||||
class WithUserClassLevelAuthenticationTests {
|
||||
|
||||
@Test
|
||||
fun withMockUser1() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withMockUser2() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithAnonymousUser
|
||||
fun anonymous() {
|
||||
// override default to run as anonymous user
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodwithmockuser
|
||||
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.security.test.context.support.WithMockUser
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
// tag::snippet[]
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username = "admin", roles = ["USER", "ADMIN"])
|
||||
class WithMockUserClassTests {
|
||||
// ...
|
||||
}
|
||||
// end::snippet[]
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodwithmockuser
|
||||
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.security.test.context.support.WithMockUser
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
// tag::snippet[]
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration
|
||||
@WithMockUser(username = "admin", roles = ["USER", "ADMIN"])
|
||||
class WithMockUserNestedTests {
|
||||
|
||||
@Nested
|
||||
inner class TestSuite1 {
|
||||
// ... all test methods use admin user
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class TestSuite2 {
|
||||
// ... all test methods use admin user
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodwithmockuser
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.config.core.MessageService
|
||||
import org.springframework.security.kt.docs.servlet.test.testmethod.HelloMessageService
|
||||
import org.springframework.security.test.context.support.WithMockUser
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration
|
||||
class WithMockUserTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var messageService: MessageService
|
||||
|
||||
// tag::mock-user[]
|
||||
@Test
|
||||
@WithMockUser
|
||||
fun getMessageWithMockUser() {
|
||||
val message = messageService.message
|
||||
assertThat(message).contains("user")
|
||||
}
|
||||
// end::mock-user[]
|
||||
|
||||
// tag::custom-user[]
|
||||
@Test
|
||||
@WithMockUser("customUser")
|
||||
fun getMessageWithMockUserCustomUsername() {
|
||||
val message = messageService.message
|
||||
assertThat(message).contains("customUser")
|
||||
}
|
||||
// end::custom-user[]
|
||||
|
||||
// tag::custom-roles[]
|
||||
@Test
|
||||
@WithMockUser(username = "admin", roles = ["USER", "ADMIN"])
|
||||
fun getMessageWithMockUserCustomRoles() {
|
||||
val message = messageService.message
|
||||
assertThat(message)
|
||||
.contains("admin")
|
||||
.contains("ROLE_ADMIN")
|
||||
.contains("ROLE_USER")
|
||||
}
|
||||
// end::custom-roles[]
|
||||
|
||||
// tag::custom-authorities[]
|
||||
@Test
|
||||
@WithMockUser(username = "admin", authorities = ["ADMIN", "USER"])
|
||||
fun getMessageWithMockUserCustomAuthorities() {
|
||||
val message = messageService.message
|
||||
assertThat(message)
|
||||
.contains("admin")
|
||||
.contains("ADMIN")
|
||||
.contains("USER")
|
||||
.doesNotContain("ROLE_")
|
||||
}
|
||||
// end::custom-authorities[]
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
open class Config {
|
||||
@Bean
|
||||
open fun messageService(): MessageService {
|
||||
return HelloMessageService()
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodwithsecuritycontext
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.AuthorityUtils
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
|
||||
class CustomUserDetails(
|
||||
name: String,
|
||||
username: String,
|
||||
authorities: MutableCollection<GrantedAuthority> = AuthorityUtils.createAuthorityList("ROLE_USER")) : UserDetails {
|
||||
|
||||
override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
|
||||
return authorities
|
||||
}
|
||||
|
||||
override fun getPassword(): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getUsername(): String {
|
||||
return username
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodwithsecuritycontext
|
||||
|
||||
import org.springframework.security.test.context.support.WithSecurityContext
|
||||
|
||||
// tag::snippet[]
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class)
|
||||
annotation class WithMockCustomUser(val username: String = "rob", val name: String = "Rob Winch")
|
||||
// end::snippet[]
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodwithsecuritycontext;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.context.SecurityContext
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.test.context.support.WithSecurityContextFactory
|
||||
|
||||
// tag::snippet[]
|
||||
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
|
||||
}
|
||||
}
|
||||
// end::snippet[]
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodwithsecuritycontext
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.context.SecurityContext
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.test.context.support.WithSecurityContextFactory
|
||||
import org.springframework.security.test.context.support.WithUserDetails
|
||||
import org.springframework.util.Assert
|
||||
|
||||
// tag::snippet[]
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
// end::snippet[]
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodwithuserdetails
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.config.core.MessageService
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||
import org.springframework.security.docs.servlet.test.testmethodwithsecuritycontext.CustomUserDetails
|
||||
import org.springframework.security.kt.docs.servlet.test.testmethod.HelloMessageService
|
||||
import org.springframework.security.test.context.support.WithUserDetails
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration
|
||||
class WithCustomUserDetailsTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var messageService: MessageService
|
||||
|
||||
// tag::custom-user-details-service[]
|
||||
@Test
|
||||
@WithUserDetails(value = "customUsername", userDetailsServiceBeanName = "myUserDetailsService")
|
||||
fun getMessageWithUserDetailsServiceBeanName() {
|
||||
val message: String = messageService.getMessage()
|
||||
assertThat(message).contains("customUsername");
|
||||
val principal = SecurityContextHolder.getContext().authentication.principal
|
||||
assertThat(principal).isInstanceOf(CustomUserDetails::class.java)
|
||||
}
|
||||
// end::custom-user-details-service[]
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
open class Config {
|
||||
|
||||
@Bean
|
||||
open fun myUserDetailsService(): UserDetailsService {
|
||||
return CustomUserDetailsService()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun messageService(): MessageService {
|
||||
return HelloMessageService()
|
||||
}
|
||||
}
|
||||
|
||||
open class CustomUserDetailsService : UserDetailsService {
|
||||
|
||||
@Throws(UsernameNotFoundException::class)
|
||||
override fun loadUserByUsername(username: String): UserDetails {
|
||||
return CustomUserDetails("name", username)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.kt.docs.servlet.test.testmethodwithuserdetails
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
|
||||
import org.springframework.security.config.core.MessageService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.kt.docs.servlet.test.testmethod.HelloMessageService
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager
|
||||
import org.springframework.security.test.context.support.WithUserDetails
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@ContextConfiguration
|
||||
class WithUserDetailsTests {
|
||||
|
||||
@Autowired
|
||||
lateinit var messageService: MessageService
|
||||
|
||||
// tag::user-details[]
|
||||
@Test
|
||||
@WithUserDetails
|
||||
fun getMessageWithUserDetails() {
|
||||
val message: String = messageService.message
|
||||
assertThat(message).contains("user")
|
||||
}
|
||||
// end::user-details[]
|
||||
|
||||
// tag::user-details-custom-username[]
|
||||
@Test
|
||||
@WithUserDetails("customUsername")
|
||||
fun getMessageWithUserDetailsCustomUsername() {
|
||||
val message: String = messageService.message
|
||||
assertThat(message).contains("customUsername")
|
||||
}
|
||||
// end::user-details-custom-username[]
|
||||
|
||||
@EnableMethodSecurity
|
||||
@Configuration
|
||||
open class Config {
|
||||
|
||||
@Bean
|
||||
open fun userDetailsService(): UserDetailsService {
|
||||
val user1 = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.build();
|
||||
val customUser = User.withDefaultPasswordEncoder()
|
||||
.username("customUsername")
|
||||
.password("password")
|
||||
.build();
|
||||
return InMemoryUserDetailsManager(user1, customUser);
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun messageService(): MessageService {
|
||||
return HelloMessageService()
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
springBootVersion=3.3.3
|
||||
version=6.5.3
|
||||
version=6.5.5
|
||||
samplesBranch=6.5.x
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -14,14 +14,14 @@ org-jetbrains-kotlinx = "1.10.2"
|
||||
org-mockito = "5.17.0"
|
||||
org-opensaml = "4.3.2"
|
||||
org-opensaml5 = "5.1.2"
|
||||
org-springframework = "6.2.10"
|
||||
org-springframework = "6.2.11"
|
||||
|
||||
[libraries]
|
||||
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.18"
|
||||
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.18.4.1"
|
||||
com-google-inject-guice = "com.google.inject:guice:3.0"
|
||||
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
|
||||
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.37.3"
|
||||
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.37.4"
|
||||
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:9.43.6"
|
||||
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
|
||||
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
|
||||
@@ -29,7 +29,7 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11"
|
||||
com-unboundid-unboundid-ldapsdk7 = "com.unboundid:unboundid-ldapsdk:7.0.1"
|
||||
commons-collections = "commons-collections:commons-collections:3.2.2"
|
||||
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.10"
|
||||
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.11"
|
||||
io-mockk = "io.mockk:mockk:1.14.5"
|
||||
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.19"
|
||||
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
|
||||
@@ -46,7 +46,7 @@ jakarta-servlet-jsp-jakarta-servlet-jsp-api = "jakarta.servlet.jsp:jakarta.servl
|
||||
jakarta-servlet-jsp-jstl-jakarta-servlet-jsp-jstl-api = "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.2"
|
||||
jakarta-websocket-jakarta-websocket-api = { module = "jakarta.websocket:jakarta.websocket-api", version.ref = "jakarta-websocket" }
|
||||
jakarta-websocket-jakarta-websocket-client-api = { module = "jakarta.websocket:jakarta.websocket-client-api", version.ref = "jakarta-websocket" }
|
||||
jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4.0.2"
|
||||
jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4.0.4"
|
||||
ldapsdk = "ldapsdk:ldapsdk:4.1"
|
||||
net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0"
|
||||
org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.11.1"
|
||||
@@ -65,13 +65,13 @@ org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4
|
||||
io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.13.1"
|
||||
org-aspectj-aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "org-aspectj" }
|
||||
org-aspectj-aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "org-aspectj" }
|
||||
org-assertj-assertj-core = "org.assertj:assertj-core:3.27.3"
|
||||
org-assertj-assertj-core = "org.assertj:assertj-core:3.27.4"
|
||||
org-bouncycastle-bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "org-bouncycastle" }
|
||||
org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "org-bouncycastle" }
|
||||
org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" }
|
||||
org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" }
|
||||
org-hamcrest = "org.hamcrest:hamcrest:2.2"
|
||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.23.Final"
|
||||
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:6.6.29.Final"
|
||||
org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
|
||||
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
|
||||
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25"
|
||||
@@ -89,8 +89,8 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup
|
||||
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
|
||||
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
|
||||
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
|
||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.8"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.13"
|
||||
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.1.10"
|
||||
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:3.2.14"
|
||||
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
|
||||
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
|
||||
|
||||
|
||||
+36
-108
@@ -30,9 +30,8 @@ import com.nimbusds.jose.crypto.RSASSASigner;
|
||||
import com.nimbusds.jwt.JWTClaimsSet;
|
||||
import com.nimbusds.jwt.PlainJWT;
|
||||
import net.minidev.json.JSONObject;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
||||
@@ -40,9 +39,12 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.jose.TestKeys;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoders;
|
||||
import org.springframework.security.oauth2.jwt.TestJwts;
|
||||
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver.TrustedIssuerJwtAuthenticationManagerResolver;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -51,17 +53,13 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
/**
|
||||
* Tests for {@link JwtIssuerAuthenticationManagerResolver}
|
||||
*/
|
||||
public class JwtIssuerAuthenticationManagerResolverTests {
|
||||
|
||||
private static final String DEFAULT_RESPONSE_TEMPLATE = "{\n" + " \"issuer\": \"%s\", \n"
|
||||
+ " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n" + "}";
|
||||
|
||||
private static final String JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"n\":\"3FlqJr5TRskIQIgdE3Dd7D9lboWdcTUT8a-fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRvc5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4_1tfRgG6ii4Uhxh6iI8qNMJQX-fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2kJdJ_ZIV-WW4noDdzpKqHcwmB8FsrumlVY_DNVvUSDIipiq9PbP4H99TXN1o746oRaNa07rq1hoCgMSSy-85SagCoxlmyE-D-of9SsMY8Ol9t0rdzpobBuhyJ_o5dfvjKw\"}]}";
|
||||
|
||||
private String jwt = jwt("iss", "trusted");
|
||||
|
||||
private String evil = jwt("iss", "\"");
|
||||
@@ -70,31 +68,22 @@ public class JwtIssuerAuthenticationManagerResolverTests {
|
||||
|
||||
@Test
|
||||
public void resolveWhenUsingFromTrustedIssuersThenReturnsAuthenticationManager() throws Exception {
|
||||
try (MockWebServer server = new MockWebServer()) {
|
||||
server.start();
|
||||
String issuer = server.url("").toString();
|
||||
// @formatter:off
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)
|
||||
));
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET)
|
||||
);
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET)
|
||||
);
|
||||
// @formatter:on
|
||||
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
|
||||
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers(issuer);
|
||||
Authentication token = withBearerToken(jws.serialize());
|
||||
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null);
|
||||
assertThat(authenticationManager).isNotNull();
|
||||
String issuer = "https://idp.example";
|
||||
|
||||
// @formatter:on
|
||||
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
|
||||
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers(issuer);
|
||||
Authentication token = withBearerToken(jws.serialize());
|
||||
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null);
|
||||
assertThat(authenticationManager).isNotNull();
|
||||
JwtDecoder decoder = mock(JwtDecoder.class);
|
||||
Jwt jwt = TestJwts.user();
|
||||
given(decoder.decode(token.getName())).willReturn(jwt);
|
||||
try (MockedStatic<JwtDecoders> jwtDecoders = mockStatic(JwtDecoders.class)) {
|
||||
given(JwtDecoders.fromIssuerLocation(issuer)).willReturn(decoder);
|
||||
Authentication authentication = authenticationManager.authenticate(token);
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
}
|
||||
@@ -102,29 +91,20 @@ public class JwtIssuerAuthenticationManagerResolverTests {
|
||||
|
||||
@Test
|
||||
public void resolveWhenUsingFromTrustedIssuersPredicateThenReturnsAuthenticationManager() throws Exception {
|
||||
try (MockWebServer server = new MockWebServer()) {
|
||||
server.start();
|
||||
String issuer = server.url("").toString();
|
||||
// @formatter:off
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)
|
||||
));
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET)
|
||||
);
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET)
|
||||
);
|
||||
// @formatter:on
|
||||
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
|
||||
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers(issuer::equals);
|
||||
Authentication token = withBearerToken(jws.serialize());
|
||||
String issuer = "https://idp.example";
|
||||
|
||||
// @formatter:on
|
||||
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
|
||||
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers(issuer::equals);
|
||||
Authentication token = withBearerToken(jws.serialize());
|
||||
JwtDecoder decoder = mock(JwtDecoder.class);
|
||||
Jwt jwt = TestJwts.user();
|
||||
given(decoder.decode(token.getName())).willReturn(jwt);
|
||||
try (MockedStatic<JwtDecoders> jwtDecoders = mockStatic(JwtDecoders.class)) {
|
||||
given(JwtDecoders.fromIssuerLocation(issuer)).willReturn(decoder);
|
||||
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null);
|
||||
assertThat(authenticationManager).isNotNull();
|
||||
Authentication authentication = authenticationManager.authenticate(token);
|
||||
@@ -132,58 +112,6 @@ public class JwtIssuerAuthenticationManagerResolverTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhednUsingTrustedIssuerThenReturnsAuthenticationManager() throws Exception {
|
||||
try (MockWebServer server = new MockWebServer()) {
|
||||
server.start();
|
||||
String issuer = server.url("").toString();
|
||||
// @formatter:off
|
||||
server.enqueue(new MockResponse().setResponseCode(500)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer))
|
||||
);
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer))
|
||||
);
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET)
|
||||
);
|
||||
// @formatter:on
|
||||
JWSObject jws = new JWSObject(new JWSHeader(JWSAlgorithm.RS256),
|
||||
new Payload(new JSONObject(Collections.singletonMap(JwtClaimNames.ISS, issuer))));
|
||||
jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY));
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
.fromTrustedIssuers(issuer);
|
||||
Authentication token = withBearerToken(jws.serialize());
|
||||
AuthenticationManager authenticationManager = authenticationManagerResolver.resolve(null);
|
||||
assertThat(authenticationManager).isNotNull();
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> authenticationManager.authenticate(token));
|
||||
Authentication authentication = authenticationManager.authenticate(token);
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenUsingSameIssuerThenReturnsSameAuthenticationManager() throws Exception {
|
||||
try (MockWebServer server = new MockWebServer()) {
|
||||
String issuer = server.url("").toString();
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(String.format(DEFAULT_RESPONSE_TEMPLATE, issuer, issuer)));
|
||||
server.enqueue(new MockResponse().setResponseCode(200)
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody(JWK_SET));
|
||||
TrustedIssuerJwtAuthenticationManagerResolver resolver = new TrustedIssuerJwtAuthenticationManagerResolver(
|
||||
(iss) -> iss.equals(issuer));
|
||||
AuthenticationManager authenticationManager = resolver.resolve(issuer);
|
||||
AuthenticationManager cachedAuthenticationManager = resolver.resolve(issuer);
|
||||
assertThat(authenticationManager).isSameAs(cachedAuthenticationManager);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveWhenUsingUntrustedIssuerThenException() {
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
|
||||
+12
-1
@@ -46,13 +46,14 @@ import org.springframework.util.StringUtils;
|
||||
* wraps the chain in before and after observations
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @author Nikita Konev
|
||||
* @since 6.0
|
||||
*/
|
||||
public final class ObservationFilterChainDecorator implements FilterChainProxy.FilterChainDecorator {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
|
||||
|
||||
private static final String ATTRIBUTE = ObservationFilterChainDecorator.class + ".observation";
|
||||
static final String ATTRIBUTE = ObservationFilterChainDecorator.class + ".observation";
|
||||
|
||||
static final String UNSECURED_OBSERVATION_NAME = "spring.security.http.unsecured.requests";
|
||||
|
||||
@@ -250,6 +251,16 @@ public final class ObservationFilterChainDecorator implements FilterChainProxy.F
|
||||
private AroundFilterObservation parent(HttpServletRequest request) {
|
||||
FilterChainObservationContext beforeContext = FilterChainObservationContext.before();
|
||||
FilterChainObservationContext afterContext = FilterChainObservationContext.after();
|
||||
|
||||
AroundFilterObservation existingParentObservation = (AroundFilterObservation) request
|
||||
.getAttribute(ATTRIBUTE);
|
||||
if (existingParentObservation != null) {
|
||||
beforeContext
|
||||
.setParentObservation(existingParentObservation.before().getContext().getParentObservation());
|
||||
afterContext
|
||||
.setParentObservation(existingParentObservation.after().getContext().getParentObservation());
|
||||
}
|
||||
|
||||
Observation before = Observation.createNotStarted(this.convention, () -> beforeContext, this.registry);
|
||||
Observation after = Observation.createNotStarted(this.convention, () -> afterContext, this.registry);
|
||||
AroundFilterObservation parent = AroundFilterObservation.create(before, after);
|
||||
|
||||
+6
-3
@@ -49,6 +49,7 @@ import org.springframework.web.util.pattern.PathPatternParser;
|
||||
* </p>
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @author Andrey Litvitski
|
||||
* @since 6.5
|
||||
*/
|
||||
public final class PathPatternRequestMatcher implements RequestMatcher {
|
||||
@@ -200,14 +201,15 @@ public final class PathPatternRequestMatcher implements RequestMatcher {
|
||||
*
|
||||
* <p>
|
||||
* Prefixes should be of the form {@code /my/prefix}, starting with a slash, not
|
||||
* ending in a slash, and not containing and wildcards
|
||||
* ending in a slash, and not containing and wildcards The special value
|
||||
* {@code "/"} may be used to indicate the root context.
|
||||
* @param basePath the path prefix
|
||||
* @return the {@link Builder} for more configuration
|
||||
*/
|
||||
public Builder basePath(String basePath) {
|
||||
Assert.notNull(basePath, "basePath cannot be null");
|
||||
Assert.isTrue(basePath.startsWith("/"), "basePath must start with '/'");
|
||||
Assert.isTrue(!basePath.endsWith("/"), "basePath must not end with a slash");
|
||||
Assert.isTrue("/".equals(basePath) || !basePath.endsWith("/"), "basePath must not end with a slash");
|
||||
Assert.isTrue(!basePath.contains("*"), "basePath must not contain a star");
|
||||
return new Builder(this.parser, basePath);
|
||||
}
|
||||
@@ -282,7 +284,8 @@ public final class PathPatternRequestMatcher implements RequestMatcher {
|
||||
public PathPatternRequestMatcher matcher(@Nullable HttpMethod method, String path) {
|
||||
Assert.notNull(path, "pattern cannot be null");
|
||||
Assert.isTrue(path.startsWith("/"), "pattern must start with a /");
|
||||
PathPattern pathPattern = this.parser.parse(this.basePath + path);
|
||||
String prefix = ("/".equals(this.basePath)) ? "" : this.basePath;
|
||||
PathPattern pathPattern = this.parser.parse(prefix + path);
|
||||
return new PathPatternRequestMatcher(pathPattern,
|
||||
(method != null) ? new HttpMethodRequestMatcher(method) : AnyRequestMatcher.INSTANCE);
|
||||
}
|
||||
|
||||
@@ -314,6 +314,65 @@ public class FilterChainProxyTests {
|
||||
assertFilterChainObservation(contexts.next(), "after", 1);
|
||||
}
|
||||
|
||||
// gh-12610
|
||||
@Test
|
||||
void parentObservationIsTakenIntoAccountDuringDispatchError() throws Exception {
|
||||
ObservationHandler<Observation.Context> handler = mock(ObservationHandler.class);
|
||||
given(handler.supportsContext(any())).willReturn(true);
|
||||
ObservationRegistry registry = ObservationRegistry.create();
|
||||
registry.observationConfig().observationHandler(handler);
|
||||
|
||||
given(this.matcher.matches(any())).willReturn(true);
|
||||
SecurityFilterChain sec = new DefaultSecurityFilterChain(this.matcher, Arrays.asList(this.filter));
|
||||
FilterChainProxy fcp = new FilterChainProxy(sec);
|
||||
fcp.setFilterChainDecorator(new ObservationFilterChainDecorator(registry));
|
||||
Filter initialFilter = ObservationFilterChainDecorator.FilterObservation
|
||||
.create(Observation.createNotStarted("wrap", registry))
|
||||
.wrap(fcp);
|
||||
|
||||
ServletRequest initialRequest = new MockHttpServletRequest("GET", "/");
|
||||
initialFilter.doFilter(initialRequest, new MockHttpServletResponse(), this.chain);
|
||||
|
||||
// simulate request attribute copying in case dispatching to ERROR
|
||||
ObservationFilterChainDecorator.AroundFilterObservation parentObservation = (ObservationFilterChainDecorator.AroundFilterObservation) initialRequest
|
||||
.getAttribute(ObservationFilterChainDecorator.ATTRIBUTE);
|
||||
assertThat(parentObservation).isNotNull();
|
||||
|
||||
// simulate dispatching error-related request
|
||||
Filter errorRelatedFilter = ObservationFilterChainDecorator.FilterObservation
|
||||
.create(Observation.createNotStarted("wrap", registry))
|
||||
.wrap(fcp);
|
||||
ServletRequest errorRelatedRequest = new MockHttpServletRequest("GET", "/error");
|
||||
errorRelatedRequest.setAttribute(ObservationFilterChainDecorator.ATTRIBUTE, parentObservation);
|
||||
errorRelatedFilter.doFilter(errorRelatedRequest, new MockHttpServletResponse(), this.chain);
|
||||
|
||||
ArgumentCaptor<Observation.Context> captor = ArgumentCaptor.forClass(Observation.Context.class);
|
||||
verify(handler, times(8)).onStart(captor.capture());
|
||||
verify(handler, times(8)).onStop(any());
|
||||
List<Observation.Context> contexts = captor.getAllValues();
|
||||
|
||||
Observation.Context initialRequestObservationContextBefore = contexts.get(1);
|
||||
Observation.Context initialRequestObservationContextAfter = contexts.get(3);
|
||||
assertFilterChainObservation(initialRequestObservationContextBefore, "before", 1);
|
||||
assertFilterChainObservation(initialRequestObservationContextAfter, "after", 1);
|
||||
|
||||
assertThat(initialRequestObservationContextBefore.getParentObservation()).isNotNull();
|
||||
assertThat(initialRequestObservationContextBefore.getParentObservation())
|
||||
.isSameAs(initialRequestObservationContextAfter.getParentObservation());
|
||||
|
||||
Observation.Context errorRelatedRequestObservationContextBefore = contexts.get(5);
|
||||
Observation.Context errorRelatedRequestObservationContextAfter = contexts.get(7);
|
||||
assertFilterChainObservation(errorRelatedRequestObservationContextBefore, "before", 1);
|
||||
assertFilterChainObservation(errorRelatedRequestObservationContextAfter, "after", 1);
|
||||
|
||||
assertThat(errorRelatedRequestObservationContextBefore.getParentObservation()).isNotNull();
|
||||
assertThat(errorRelatedRequestObservationContextBefore.getParentObservation())
|
||||
.isSameAs(initialRequestObservationContextBefore.getParentObservation());
|
||||
assertThat(errorRelatedRequestObservationContextAfter.getParentObservation()).isNotNull();
|
||||
assertThat(errorRelatedRequestObservationContextAfter.getParentObservation())
|
||||
.isSameAs(initialRequestObservationContextBefore.getParentObservation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenMultipleFiltersThenObservationRegistryObserves() throws Exception {
|
||||
ObservationHandler<Observation.Context> handler = mock(ObservationHandler.class);
|
||||
|
||||
+8
@@ -137,6 +137,14 @@ public class PathPatternRequestMatcherTests {
|
||||
.isThrownBy(() -> PathPatternRequestMatcher.withDefaults().basePath("/path/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void matcherWhenBasePathIsRootThenNoDoubleSlash() {
|
||||
PathPatternRequestMatcher.Builder builder = PathPatternRequestMatcher.withDefaults().basePath("/");
|
||||
RequestMatcher matcher = builder.matcher(HttpMethod.GET, "/path");
|
||||
MockHttpServletRequest mock = get("/path").servletPath("/path").buildRequest(null);
|
||||
assertThat(matcher.matches(mock)).isTrue();
|
||||
}
|
||||
|
||||
MockHttpServletRequest request(String uri) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
|
||||
ServletRequestPathUtils.parseAndCache(request);
|
||||
|
||||
Reference in New Issue
Block a user