Skip to content
This repository was archived by the owner on Apr 24, 2024. It is now read-only.

Commit 7b2f5c7

Browse files
Merge pull request #37 from FrogDevelopment/feature/extended_authentication
Feature/extended authentication
2 parents 5aaf242 + 2602957 commit 7b2f5c7

File tree

9 files changed

+148
-43
lines changed

9 files changed

+148
-43
lines changed

build.gradle

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ sourceCompatibility = JavaVersion.VERSION_11
1616
targetCompatibility = JavaVersion.VERSION_11
1717

1818
repositories {
19-
mavenLocal()
20-
mavenCentral()
2119
jcenter()
2220
}
2321

@@ -38,7 +36,8 @@ dependencies {
3836
implementation 'javax.servlet:javax.servlet-api:4.0.1'
3937
implementation 'javax.xml.bind:jaxb-api:2.3.1'
4038

41-
compileOnly 'org.jetbrains:annotations:20.0.0'
39+
compileOnly 'org.jetbrains:annotations:20.1.0'
40+
compileOnly 'org.springframework.security:spring-security-test'
4241

4342
testImplementation 'org.springframework.boot:spring-boot-starter-test'
4443
testImplementation 'org.springframework.security:spring-security-test'
@@ -101,24 +100,24 @@ artifacts {
101100

102101
// To specify a license in the pom:
103102
install {
104-
repositories.mavenInstaller {
105-
pom.project {
106-
licenses {
107-
license {
108-
name 'The Apache Software License, Version 2.0'
109-
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
110-
distribution 'repo'
103+
repositories.mavenInstaller {
104+
pom.project {
105+
licenses {
106+
license {
107+
name 'The Apache Software License, Version 2.0'
108+
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
109+
distribution 'repo'
110+
}
111+
}
111112
}
112-
}
113113
}
114-
}
115114
}
116115

117116
jar {
118117
enabled true
119118
}
120119

121120
wrapper {
122-
gradleVersion = "6.4"
121+
gradleVersion = "6.6.1"
123122
distributionType = Wrapper.DistributionType.ALL
124123
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-all.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,58 @@
11
package fr.frogdevelopment.jwt;
22

3+
import static java.util.Collections.emptyList;
4+
import static java.util.stream.Collectors.toUnmodifiableList;
5+
import static java.util.stream.Collectors.toUnmodifiableMap;
6+
7+
import io.jsonwebtoken.Claims;
38
import java.util.Collection;
4-
import org.springframework.security.authentication.AbstractAuthenticationToken;
9+
import java.util.List;
10+
import java.util.Map;
11+
import java.util.Map.Entry;
12+
import lombok.Getter;
13+
import lombok.Setter;
14+
import org.springframework.security.core.Authentication;
15+
import org.springframework.security.core.GrantedAuthority;
516
import org.springframework.security.core.authority.SimpleGrantedAuthority;
617

7-
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
18+
public class JwtAuthenticationToken implements Authentication {
19+
20+
public static final String AUTHORITIES_KEY = "authorities";
821

22+
@Getter
923
private final String principal;
24+
@Getter
25+
private final String name;
26+
@Getter
27+
private final Collection<GrantedAuthority> authorities;
28+
@Getter
29+
private final Map<String, Object> details;
30+
@Getter
31+
@Setter
32+
private boolean authenticated;
1033

11-
JwtAuthenticationToken(String principal, Collection<SimpleGrantedAuthority> authorities) {
12-
super(authorities);
13-
this.principal = principal;
14-
super.setAuthenticated(true);
34+
public JwtAuthenticationToken(Claims claims) {
35+
this.principal = claims.getSubject();
36+
this.name = claims.getSubject();
37+
//noinspection unchecked
38+
this.authorities = ((List<String>) claims.getOrDefault(AUTHORITIES_KEY, emptyList()))
39+
.stream()
40+
.map(SimpleGrantedAuthority::new)
41+
.collect(toUnmodifiableList());
42+
this.details = claims.entrySet()
43+
.stream()
44+
.filter(entry -> !AUTHORITIES_KEY.equals(entry.getKey()))
45+
.collect(toUnmodifiableMap(Entry::getKey, Entry::getValue));
46+
this.authenticated = true;
1547
}
1648

17-
@Override
18-
public String getCredentials() {
19-
return null;
49+
public Object getDetail(String key) {
50+
return details.get(key);
2051
}
2152

2253
@Override
23-
public String getPrincipal() {
24-
return principal;
54+
public String getCredentials() {
55+
return null;
2556
}
2657

2758
}

src/main/java/fr/frogdevelopment/jwt/JwtProcessTokenFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ private void resolveTokenAndSetAuthenticationOnSpringSecurityContext(@NonNull Ht
3434
try {
3535
log.debug("Resolve token and set authentication on Spring Security Context for request {}",
3636
request.getRequestURL());
37-
Authentication authentication = resolveTokenToAuthentication.call(request);
37+
var authentication = resolveTokenToAuthentication.call(request);
3838

3939
if (authentication != null) {
4040
SecurityContextHolder.getContext().setAuthentication(authentication);
Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
package fr.frogdevelopment.jwt;
22

3-
import io.jsonwebtoken.Claims;
4-
import java.util.List;
5-
import java.util.stream.Collectors;
63
import javax.servlet.http.HttpServletRequest;
74
import org.jetbrains.annotations.NotNull;
85
import org.jetbrains.annotations.Nullable;
9-
import org.springframework.security.core.Authentication;
10-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
116

127
class ResolveTokenToAuthentication {
138

14-
static final String AUTHORITIES_KEY = "authorities";
15-
169
private final ResolveClaimsFromToken resolveClaimsFromToken;
1710
private final RetrieveTokenFromRequest retrieveTokenFromRequest;
1811

@@ -22,22 +15,16 @@ class ResolveTokenToAuthentication {
2215
this.retrieveTokenFromRequest = retrieveTokenFromRequest;
2316
}
2417

25-
@Nullable Authentication call(@NotNull HttpServletRequest request) {
18+
@Nullable
19+
JwtAuthenticationToken call(@NotNull HttpServletRequest request) {
2620
var token = retrieveTokenFromRequest.call(request);
2721
if (token == null) {
2822
return null;
2923
}
3024

3125
var claims = resolveClaimsFromToken.call(token);
3226

33-
return new JwtAuthenticationToken(claims.getSubject(), resolveAuthorities(claims));
27+
return new JwtAuthenticationToken(claims);
3428
}
3529

36-
private List<SimpleGrantedAuthority> resolveAuthorities(@NotNull Claims claims) {
37-
//noinspection unchecked
38-
return ((List<String>) claims.get(AUTHORITIES_KEY, List.class))
39-
.stream()
40-
.map(SimpleGrantedAuthority::new)
41-
.collect(Collectors.toList());
42-
}
4330
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package fr.frogdevelopment.jwt.test.context.support;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target({ElementType.METHOD, ElementType.TYPE})
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface Claim {
11+
12+
String name() default "";
13+
14+
String value() default "";
15+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package fr.frogdevelopment.jwt.test.context.support;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Inherited;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
import org.springframework.core.annotation.AliasFor;
10+
import org.springframework.security.test.context.support.TestExecutionEvent;
11+
import org.springframework.security.test.context.support.WithSecurityContext;
12+
13+
@Target({ElementType.METHOD, ElementType.TYPE})
14+
@Retention(RetentionPolicy.RUNTIME)
15+
@Inherited
16+
@Documented
17+
@WithSecurityContext(factory = WithMockJwtUserSecurityContextFactory.class)
18+
public @interface WithMockJwtUser {
19+
20+
String username() default "user";
21+
22+
String[] roles() default { "USER" };
23+
24+
Claim[] claims() default {};
25+
26+
@AliasFor(annotation = WithSecurityContext.class)
27+
TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD;
28+
29+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package fr.frogdevelopment.jwt.test.context.support;
2+
3+
import static fr.frogdevelopment.jwt.JwtAuthenticationToken.AUTHORITIES_KEY;
4+
5+
import fr.frogdevelopment.jwt.JwtAuthenticationToken;
6+
import io.jsonwebtoken.impl.DefaultClaims;
7+
import java.util.ArrayList;
8+
import org.springframework.security.core.context.SecurityContext;
9+
import org.springframework.security.core.context.SecurityContextHolder;
10+
import org.springframework.security.test.context.support.WithSecurityContextFactory;
11+
12+
public final class WithMockJwtUserSecurityContextFactory implements WithSecurityContextFactory<WithMockJwtUser> {
13+
14+
@Override
15+
public SecurityContext createSecurityContext(WithMockJwtUser withUser) {
16+
if (withUser.username() == null || withUser.username().strip().length() == 0) {
17+
throw new IllegalArgumentException("Username required");
18+
}
19+
20+
var claims = new DefaultClaims();
21+
claims.setSubject(withUser.username());
22+
23+
var authorities = new ArrayList<String>();
24+
for (String role : withUser.roles()) {
25+
if (!role.startsWith("ROLE_")) {
26+
role = "ROLE_" + role;
27+
}
28+
authorities.add(role);
29+
}
30+
claims.put(AUTHORITIES_KEY, authorities);
31+
32+
for (var claim : withUser.claims()) {
33+
claims.put(claim.name(), claim.value());
34+
}
35+
36+
var context = SecurityContextHolder.createEmptyContext();
37+
context.setAuthentication(new JwtAuthenticationToken(claims));
38+
return context;
39+
}
40+
}

src/test/java/fr/frogdevelopment/jwt/ResolveTokenToAuthenticationTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package fr.frogdevelopment.jwt;
22

3-
import static fr.frogdevelopment.jwt.ResolveTokenToAuthentication.AUTHORITIES_KEY;
3+
import static fr.frogdevelopment.jwt.JwtAuthenticationToken.AUTHORITIES_KEY;
44
import static org.junit.jupiter.api.Assertions.assertEquals;
55
import static org.junit.jupiter.api.Assertions.assertNotNull;
66
import static org.junit.jupiter.api.Assertions.assertNull;
7+
import static org.junit.jupiter.api.Assertions.assertTrue;
78
import static org.mockito.ArgumentMatchers.anyString;
89
import static org.mockito.BDDMockito.given;
910

@@ -48,6 +49,9 @@ void createAuthentication_should_return_authentication() {
4849
// then
4950
assertNotNull(authentication);
5051
assertEquals(USERNAME, authentication.getPrincipal());
52+
assertEquals(USERNAME, authentication.getName());
53+
assertNotNull(authentication.getDetails());
54+
assertTrue(authentication.isAuthenticated());
5155
assertNull(authentication.getCredentials());
5256
var authorities = ROLES.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
5357
assertEquals(authentication.getAuthorities(), authorities);

0 commit comments

Comments
 (0)