Skip to content

Commit 0fcc0de

Browse files
author
nigel.zheng
committed
refactor: get UserAuthentication directly from WithMockUserSecurityContextFactory
1 parent f6976a9 commit 0fcc0de

File tree

5 files changed

+163
-60
lines changed

5 files changed

+163
-60
lines changed

src/main/java/com/github/ahunigel/test/security/WithClaims.java

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
package com.github.ahunigel.test.security;
22

3-
import org.springframework.beans.BeanUtils;
4-
import org.springframework.core.annotation.AnnotationUtils;
53
import org.springframework.security.authentication.AbstractAuthenticationToken;
64
import org.springframework.security.core.Authentication;
75
import org.springframework.security.core.context.SecurityContext;
8-
import org.springframework.security.test.context.support.WithMockUser;
6+
import org.springframework.security.core.context.SecurityContextHolder;
97
import org.springframework.security.test.context.support.WithSecurityContext;
108
import org.springframework.security.test.context.support.WithSecurityContextFactory;
119

1210
import java.lang.annotation.*;
1311
import java.util.Arrays;
12+
import java.util.HashMap;
1413
import java.util.Map;
1514
import java.util.stream.Collectors;
1615

1716
/**
1817
* Created by Nigel Zheng on 8/3/2018.
18+
* <p>
19+
* Attach claims as map to current authentication details
1920
*/
2021
@Target({ElementType.METHOD, ElementType.TYPE})
2122
@Retention(RetentionPolicy.RUNTIME)
@@ -26,36 +27,41 @@
2627
/**
2728
* Return the contained {@link Claim} annotations.
2829
*
29-
* @return the claim
30+
* @return the claims
3031
*/
3132
Claim[] value();
3233

33-
WithMockUser user() default @WithMockUser();
34+
/**
35+
* key-value paired string array, redundant value will be ignored
36+
* <p>
37+
* would merge with #value() if key is absent
38+
*
39+
* @return
40+
*/
41+
String[] claims() default {};
3442

3543
class WithClaimsSecurityContextFactory implements WithSecurityContextFactory<WithClaims> {
3644
@Override
3745
public SecurityContext createSecurityContext(WithClaims annotation) {
38-
WithSecurityContext withSecurityContext = AnnotationUtils
39-
.findAnnotation(annotation.user().getClass(), WithSecurityContext.class);
40-
WithSecurityContextFactory factory = createFactory(withSecurityContext);
41-
SecurityContext context = factory.createSecurityContext(annotation.user());
46+
SecurityContext context = SecurityContextHolder.getContext();
4247
Authentication authentication = context.getAuthentication();
43-
Map<String, String> claims = Arrays.stream(annotation.value()).collect(Collectors.toMap(Claim::name, Claim::value));
44-
((AbstractAuthenticationToken) authentication).setDetails(claims);
48+
if (authentication != null && authentication instanceof AbstractAuthenticationToken) {
49+
Map<String, String> claims = Arrays.stream(annotation.value()).collect(Collectors.toMap(Claim::name, Claim::value));
50+
Map<String, String> stringMap = toMap(annotation.claims());
51+
stringMap.entrySet().stream().forEach(entry -> claims.putIfAbsent(entry.getKey(), entry.getValue()));
52+
if (!claims.isEmpty()) {
53+
((AbstractAuthenticationToken) authentication).setDetails(claims);
54+
}
55+
}
4556
return context;
4657
}
4758

48-
private WithSecurityContextFactory<? extends Annotation> createFactory(
49-
WithSecurityContext withSecurityContext) {
50-
Class<? extends WithSecurityContextFactory<? extends Annotation>> clazz = withSecurityContext
51-
.factory();
52-
try {
53-
return BeanUtils.instantiateClass(clazz);
54-
} catch (IllegalStateException e) {
55-
return BeanUtils.instantiateClass(clazz);
56-
} catch (Exception e) {
57-
throw new RuntimeException(e);
59+
private Map<String, String> toMap(String[] claims) {
60+
final Map<String, String> map = new HashMap<>();
61+
for (int i = 0; i + 1 < claims.length; i += 2) {
62+
map.put(claims[i], claims[i + 1]);
5863
}
64+
return map;
5965
}
6066
}
6167

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.github.ahunigel.test.security;
2+
3+
import com.github.ahunigel.test.security.util.MockUserUtils;
4+
import org.springframework.core.annotation.AliasFor;
5+
import org.springframework.security.authentication.AbstractAuthenticationToken;
6+
import org.springframework.security.core.Authentication;
7+
import org.springframework.security.core.context.SecurityContext;
8+
import org.springframework.security.test.context.support.WithMockUser;
9+
import org.springframework.security.test.context.support.WithSecurityContext;
10+
import org.springframework.security.test.context.support.WithSecurityContextFactory;
11+
12+
import java.lang.annotation.*;
13+
import java.util.Arrays;
14+
import java.util.Map;
15+
import java.util.stream.Collectors;
16+
17+
/**
18+
* Created by Nigel Zheng on 8/3/2018.
19+
* <p>
20+
* Emulate running with a mocked user,
21+
* attach claims as map to mocked authentication details
22+
*/
23+
@Target({ElementType.METHOD, ElementType.TYPE})
24+
@Retention(RetentionPolicy.RUNTIME)
25+
@Inherited
26+
@Documented
27+
@WithSecurityContext(factory = WithMockUserAndClaims.WithMockUserWithClaimsSecurityContextFactory.class)
28+
public @interface WithMockUserAndClaims {
29+
/**
30+
* Return the contained {@link Claim} annotations.
31+
*
32+
* @return the claims
33+
*/
34+
@AliasFor("claims")
35+
Claim[] value();
36+
37+
@AliasFor("value")
38+
Claim[] claims();
39+
40+
WithMockUser user() default @WithMockUser();
41+
42+
class WithMockUserWithClaimsSecurityContextFactory implements WithSecurityContextFactory<WithMockUserAndClaims> {
43+
@Override
44+
public SecurityContext createSecurityContext(WithMockUserAndClaims annotation) {
45+
SecurityContext context = MockUserUtils.getSecurityContext(annotation.user());
46+
Authentication authentication = context.getAuthentication();
47+
Map<String, String> claims = Arrays.stream(annotation.value()).collect(Collectors.toMap(Claim::name, Claim::value));
48+
if (!claims.isEmpty() && authentication instanceof AbstractAuthenticationToken) {
49+
((AbstractAuthenticationToken) authentication).setDetails(claims);
50+
}
51+
return context;
52+
}
53+
54+
}
55+
56+
}

src/main/java/com/github/ahunigel/test/security/oauth2/WithMockOAuth2Client.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
/**
1919
* Created by Nigel Zheng on 8/3/2018.
20+
* <p>
21+
* Emulate running with a mocked oauth2 client
2022
*/
2123
@Retention(RetentionPolicy.RUNTIME)
2224
@WithSecurityContext(factory = WithMockOAuth2Client.WithMockOAuth2ClientSecurityContextFactory.class)
Lines changed: 10 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package com.github.ahunigel.test.security.oauth2;
22

33
import com.github.ahunigel.test.security.Claim;
4-
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
5-
import org.springframework.security.core.GrantedAuthority;
6-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
4+
import com.github.ahunigel.test.security.util.MockUserUtils;
75
import org.springframework.security.core.context.SecurityContext;
86
import org.springframework.security.core.context.SecurityContextHolder;
9-
import org.springframework.security.core.userdetails.User;
107
import org.springframework.security.oauth2.provider.OAuth2Authentication;
118
import org.springframework.security.test.context.support.WithMockUser;
129
import org.springframework.security.test.context.support.WithSecurityContext;
@@ -15,14 +12,14 @@
1512
import java.lang.annotation.Retention;
1613
import java.lang.annotation.RetentionPolicy;
1714
import java.util.Arrays;
18-
import java.util.HashMap;
1915
import java.util.Map;
20-
import java.util.Set;
2116
import java.util.stream.Collectors;
22-
import java.util.stream.Stream;
2317

2418
/**
2519
* Created by Nigel Zheng on 8/3/2018.
20+
* <p>
21+
* Emulate running with a mocked oauth2 client on behalf of user,
22+
* attach claims as map to mocked oauth2 authentication details
2623
*/
2724
@Retention(RetentionPolicy.RUNTIME)
2825
@WithSecurityContext(factory = WithMockOAuth2User.WithMockOAuth2UserSecurityContextFactory.class)
@@ -31,40 +28,21 @@
3128

3229
WithMockUser user() default @WithMockUser();
3330

31+
/**
32+
* Return the contained {@link Claim} annotations.
33+
*
34+
* @return the claims
35+
*/
3436
Claim[] claims();
3537

3638
class WithMockOAuth2UserSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2User> {
3739

38-
/**
39-
* Sadly, #WithMockUserSecurityContextFactory is not public,
40-
* so re-implement mock user authentication creation
41-
*
42-
* @param user
43-
* @return an Authentication with provided user details
44-
*/
45-
public static UsernamePasswordAuthenticationToken getUserAuthentication(final WithMockUser user) {
46-
final String principal = user.username().isEmpty() ? user.value() : user.username();
47-
48-
final Stream<String> grants = user.authorities().length == 0 ?
49-
Stream.of(user.roles()).map(r -> "ROLE_" + r) :
50-
Stream.of(user.authorities());
51-
52-
final Set<? extends GrantedAuthority> userAuthorities = grants
53-
.map(auth -> new SimpleGrantedAuthority(auth))
54-
.collect(Collectors.toSet());
55-
56-
return new UsernamePasswordAuthenticationToken(
57-
new User(principal, user.password(), userAuthorities),
58-
principal + ":" + user.password(),
59-
userAuthorities);
60-
}
61-
6240
@Override
6341
public SecurityContext createSecurityContext(final WithMockOAuth2User annotation) {
6442
final SecurityContext ctx = SecurityContextHolder.createEmptyContext();
6543
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(
6644
WithMockOAuth2Client.WithMockOAuth2ClientSecurityContextFactory.getOAuth2Request(annotation.client()),
67-
getUserAuthentication(annotation.user()));
45+
MockUserUtils.getAuthentication(annotation.user()));
6846
Map<String, String> claims = Arrays.stream(annotation.claims()).collect(Collectors.toMap(Claim::name, Claim::value));
6947
if (!claims.isEmpty()) {
7048
oAuth2Authentication.setDetails(claims);
@@ -74,12 +52,5 @@ public SecurityContext createSecurityContext(final WithMockOAuth2User annotation
7452
return ctx;
7553
}
7654

77-
private Map<String, Object> toMap(String[] claims) {
78-
final Map<String, Object> map = new HashMap<>();
79-
for (int i = 0; i + 1 < claims.length; i += 2) {
80-
map.put(claims[i], claims[i + 1]);
81-
}
82-
return map;
83-
}
8455
}
8556
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.github.ahunigel.test.security.util;
2+
3+
import org.springframework.beans.BeanUtils;
4+
import org.springframework.core.annotation.AnnotationUtils;
5+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
6+
import org.springframework.security.core.Authentication;
7+
import org.springframework.security.core.GrantedAuthority;
8+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
9+
import org.springframework.security.core.context.SecurityContext;
10+
import org.springframework.security.core.userdetails.User;
11+
import org.springframework.security.test.context.support.WithMockUser;
12+
import org.springframework.security.test.context.support.WithSecurityContext;
13+
import org.springframework.security.test.context.support.WithSecurityContextFactory;
14+
15+
import java.lang.annotation.Annotation;
16+
import java.util.Set;
17+
import java.util.stream.Collectors;
18+
import java.util.stream.Stream;
19+
20+
/**
21+
* Created by Nigel Zheng on 8/3/2018.
22+
*/
23+
public class MockUserUtils {
24+
public static SecurityContext getSecurityContext(WithMockUser withMockUser) {
25+
WithSecurityContext withSecurityContext = AnnotationUtils
26+
.findAnnotation(withMockUser.getClass(), WithSecurityContext.class);
27+
WithSecurityContextFactory factory = createFactory(withSecurityContext);
28+
return factory.createSecurityContext(withMockUser);
29+
}
30+
31+
public static Authentication getAuthentication(WithMockUser withMockUser) {
32+
return getSecurityContext(withMockUser).getAuthentication();
33+
}
34+
35+
private static WithSecurityContextFactory<? extends Annotation> createFactory(
36+
WithSecurityContext withSecurityContext) {
37+
Class<? extends WithSecurityContextFactory<? extends Annotation>> clazz = withSecurityContext
38+
.factory();
39+
try {
40+
return BeanUtils.instantiateClass(clazz);
41+
} catch (Exception e) {
42+
throw new RuntimeException(e);
43+
}
44+
}
45+
46+
/**
47+
* User authentication creation
48+
*
49+
* @param user
50+
* @return an Authentication with provided user details
51+
*/
52+
public static UsernamePasswordAuthenticationToken createUserAuthentication(final WithMockUser user) {
53+
final String principal = user.username().isEmpty() ? user.value() : user.username();
54+
55+
final Stream<String> grants = user.authorities().length == 0 ?
56+
Stream.of(user.roles()).map(r -> "ROLE_" + r) :
57+
Stream.of(user.authorities());
58+
59+
final Set<? extends GrantedAuthority> userAuthorities = grants
60+
.map(auth -> new SimpleGrantedAuthority(auth))
61+
.collect(Collectors.toSet());
62+
63+
return new UsernamePasswordAuthenticationToken(
64+
new User(principal, user.password(), userAuthorities),
65+
principal + ":" + user.password(),
66+
userAuthorities);
67+
}
68+
}

0 commit comments

Comments
 (0)