Skip to content

Commit fd3236e

Browse files
Extract authorities from permissions claim (#40)
* Extract authorities from permissions claim * Apply suggestions from code review Co-Authored-By: Luciano Balmaceda <balmacedaluciano@gmail.com> Co-authored-by: Luciano Balmaceda <balmacedaluciano@gmail.com>
1 parent 5ddeea5 commit fd3236e

4 files changed

Lines changed: 100 additions & 24 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,6 @@ gradle-app.setting
8888
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
8989
# gradle/wrapper/gradle-wrapper.properties
9090

91+
*.classpath
92+
*.project
93+
*.settings/

README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ implementation 'com.auth0:auth0-spring-security-api:1.2.6'
3030

3131
## Usage
3232

33-
Inside a `WebSecurityConfigurerAdapter` you can configure your API to only accept `RS256` signed JWTs
33+
Inside a `WebSecurityConfigurerAdapter` you can configure your API to only accept `RS256` signed JWTs:
3434

3535
```java
3636
@EnableWebSecurity
@@ -46,7 +46,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
4646
}
4747
```
4848

49-
or for `HS256` signed JWTs
49+
or for `HS256` signed JWTs:
5050

5151
```java
5252
@EnableWebSecurity
@@ -65,20 +65,29 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
6565
> If you need further customization (like a leeway for JWT verification) use the `JwtWebSecurityConfigurer` signatures which accept a `JwtAuthenticationProvider`.
6666
6767

68-
Then using Spring Security `HttpSecurity` you can specify which paths requires authentication
68+
Then using Spring Security `HttpSecurity` you can specify which paths requires authentication:
6969

7070
```java
7171
http.authorizeRequests()
7272
.antMatchers("/api/**").fullyAuthenticated();
7373
```
7474

75-
and you can even specify that the JWT should have a single or several scopes
75+
To restrict access based on the presence of a specific scope or permission claim, you can use the `hasAuthority` method.
76+
Scope and permissions claim values are prefixed with `SCOPE_` and `PERMISSION_`, respectively.
77+
78+
To require a specific scope (`read:users` in the example below):
7679

7780
```java
7881
http.authorizeRequests()
79-
.antMatchers(HttpMethod.GET, "/api/users/**").hasAuthority("read:users");
82+
.antMatchers(HttpMethod.GET, "/api/users/**").hasAuthority("SCOPE_read:users");
8083
```
8184

85+
To require a specific permission (`admin` in the example below):
86+
87+
```java
88+
http.authorizeRequests()
89+
.antMatchers(HttpMethod.GET, "/api/admin/**").hasAuthority("PERMISSION_admin");
90+
```
8291

8392
`JwtWebSecurityConfigurer#configure(HttpSecurity)` also returns `HttpSecurity` so you can do the following:
8493

@@ -93,7 +102,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
93102
.forRS256("YOUR_API_AUDIENCE", "YOUR_API_ISSUER")
94103
.configure(http)
95104
.authorizeRequests()
96-
.antMatchers(HttpMethod.GET, "/api/users/**").hasAuthority("read:users");
105+
.antMatchers(HttpMethod.GET, "/api/users/**").hasAuthority("SCOPE_read:users")
106+
.antMatchers(HttpMethod.GET, "/api/admin/**").hasAuthority("PERMISSION_admin");
97107
}
98108
}
99109
```

lib/src/main/java/com/auth0/spring/security/api/authentication/AuthenticationJsonWebToken.java

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
import org.springframework.security.core.GrantedAuthority;
99
import org.springframework.security.core.authority.SimpleGrantedAuthority;
1010

11-
import java.util.ArrayList;
12-
import java.util.Collection;
13-
import java.util.List;
11+
import java.util.*;
1412

1513
public class AuthenticationJsonWebToken implements Authentication, JwtAuthentication {
1614

15+
private final static String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
16+
private final static String PERMISSION_AUTHORITY_PREFIX = "PERMISSION_";
17+
1718
private final DecodedJWT decoded;
1819
private boolean authenticated;
1920

@@ -39,15 +40,13 @@ public Authentication verify(JWTVerifier verifier) throws JWTVerificationExcepti
3940

4041
@Override
4142
public Collection<? extends GrantedAuthority> getAuthorities() {
42-
String scope = decoded.getClaim("scope").asString();
43-
if (scope == null || scope.trim().isEmpty()) {
44-
return new ArrayList<>();
45-
}
46-
final String[] scopes = scope.split(" ");
47-
List<SimpleGrantedAuthority> authorities = new ArrayList<>(scopes.length);
48-
for (String value : scopes) {
49-
authorities.add(new SimpleGrantedAuthority(value));
50-
}
43+
List<SimpleGrantedAuthority> scopes = getScopeAuthorities();
44+
List<SimpleGrantedAuthority> permissions = getPermissionAuthorities();
45+
46+
List<GrantedAuthority> authorities = new ArrayList<>();
47+
authorities.addAll(scopes);
48+
authorities.addAll(permissions);
49+
5150
return authorities;
5251
}
5352

@@ -83,4 +82,34 @@ public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentExce
8382
public String getName() {
8483
return decoded.getSubject();
8584
}
85+
86+
private List<SimpleGrantedAuthority> getScopeAuthorities() {
87+
String scope = decoded.getClaim("scope").asString();
88+
if (scope == null || scope.trim().isEmpty()) {
89+
return Collections.emptyList();
90+
}
91+
final String[] scopes = scope.split(" ");
92+
List<SimpleGrantedAuthority> authorities = new ArrayList<>(scopes.length * 2);
93+
for (String value : scopes) {
94+
// For backwards-compatibility, create authority without scope prefix
95+
authorities.add(new SimpleGrantedAuthority(value));
96+
authorities.add(new SimpleGrantedAuthority(SCOPE_AUTHORITY_PREFIX + value));
97+
}
98+
return authorities;
99+
}
100+
101+
private List<SimpleGrantedAuthority> getPermissionAuthorities() {
102+
String[] permissions = decoded.getClaim("permissions").asArray(String.class);
103+
104+
if (permissions == null || permissions.length == 0) {
105+
return Collections.emptyList();
106+
}
107+
108+
List<SimpleGrantedAuthority> authorities = new ArrayList<>(permissions.length);
109+
for (String value : permissions) {
110+
authorities.add(new SimpleGrantedAuthority(PERMISSION_AUTHORITY_PREFIX + value));
111+
}
112+
113+
return authorities;
114+
}
86115
}

lib/src/test/java/com/auth0/spring/security/api/authentication/AuthenticationJsonWebTokenTest.java

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.auth0.jwt.JWTVerifier;
55
import com.auth0.jwt.algorithms.Algorithm;
66
import com.auth0.jwt.interfaces.DecodedJWT;
7-
import com.auth0.spring.security.api.authentication.AuthenticationJsonWebToken;
87
import org.hamcrest.Matchers;
98
import org.hamcrest.collection.IsCollectionWithSize;
109
import org.hamcrest.collection.IsEmptyCollection;
@@ -15,6 +14,7 @@
1514
import org.springframework.security.core.GrantedAuthority;
1615

1716
import java.util.ArrayList;
17+
import java.util.Collection;
1818
import java.util.Collections;
1919
import java.util.Map;
2020

@@ -206,15 +206,49 @@ public void shouldGetScopeAsAuthorities() throws Exception {
206206
.withClaim("scope", "auth0 auth10")
207207
.sign(hmacAlgorithm);
208208

209+
AuthenticationJsonWebToken auth = new AuthenticationJsonWebToken(token, verifier);
210+
assertThat(auth, is(notNullValue()));
211+
212+
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
213+
assertThat(authorities, is(notNullValue()));
214+
assertThat(authorities, is(IsCollectionWithSize.hasSize(4)));
215+
assertThat(authorities, containsInAnyOrder(
216+
hasProperty("authority", is("auth0")),
217+
hasProperty("authority", is("auth10")),
218+
hasProperty("authority", is("SCOPE_auth0")),
219+
hasProperty("authority", is("SCOPE_auth10"))
220+
));
221+
}
222+
223+
@Test
224+
public void shouldGetEmptyAuthoritiesOnEmptyPermissionsClaim() throws Exception {
225+
String token = JWT.create()
226+
.withArrayClaim("permissions", new String[]{})
227+
.sign(hmacAlgorithm);
228+
229+
AuthenticationJsonWebToken auth = new AuthenticationJsonWebToken(token, verifier);
230+
assertThat(auth, is(notNullValue()));
231+
assertThat(auth.getAuthorities(), is(notNullValue()));
232+
assertThat(auth.getAuthorities(), is(IsEmptyCollection.empty()));
233+
}
234+
235+
@Test
236+
public void shouldGetPermissionsAsAuthorities() throws Exception {
237+
String[] permissionsClaim = {"read:permission", "write:permission"};
238+
String token = JWT.create()
239+
.withArrayClaim("permissions", permissionsClaim)
240+
.sign(hmacAlgorithm);
241+
209242
AuthenticationJsonWebToken auth = new AuthenticationJsonWebToken(token, verifier);
210243
assertThat(auth, is(notNullValue()));
211244
assertThat(auth.getAuthorities(), is(notNullValue()));
212245
assertThat(auth.getAuthorities(), is(IsCollectionWithSize.hasSize(2)));
213246

214-
ArrayList<GrantedAuthority> authorities = new ArrayList<>(auth.getAuthorities());
215-
assertThat(authorities.get(0), is(notNullValue()));
216-
assertThat(authorities.get(0).getAuthority(), is("auth0"));
217-
assertThat(authorities.get(1), is(notNullValue()));
218-
assertThat(authorities.get(1).getAuthority(), is("auth10"));
247+
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
248+
assertThat(authorities, IsCollectionWithSize.hasSize(2));
249+
assertThat(authorities, containsInAnyOrder(
250+
hasProperty("authority", is("PERMISSION_" + permissionsClaim[0])),
251+
hasProperty("authority", is("PERMISSION_" + permissionsClaim[1]))
252+
));
219253
}
220254
}

0 commit comments

Comments
 (0)