Skip to content

Commit

Permalink
Extract authorities from permissions claim (#40)
Browse files Browse the repository at this point in the history
* Extract authorities from permissions claim

* Apply suggestions from code review

Co-Authored-By: Luciano Balmaceda <[email protected]>

Co-authored-by: Luciano Balmaceda <[email protected]>
  • Loading branch information
jimmyjames and lbalmaceda authored Jan 30, 2020
1 parent 5ddeea5 commit fd3236e
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 24 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,6 @@ gradle-app.setting
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties

*.classpath
*.project
*.settings/
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ implementation 'com.auth0:auth0-spring-security-api:1.2.6'

## Usage

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

```java
@EnableWebSecurity
Expand All @@ -46,7 +46,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
```

or for `HS256` signed JWTs
or for `HS256` signed JWTs:

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

Then using Spring Security `HttpSecurity` you can specify which paths requires authentication
Then using Spring Security `HttpSecurity` you can specify which paths requires authentication:

```java
http.authorizeRequests()
.antMatchers("/api/**").fullyAuthenticated();
```

and you can even specify that the JWT should have a single or several scopes
To restrict access based on the presence of a specific scope or permission claim, you can use the `hasAuthority` method.
Scope and permissions claim values are prefixed with `SCOPE_` and `PERMISSION_`, respectively.

To require a specific scope (`read:users` in the example below):

```java
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/users/**").hasAuthority("read:users");
.antMatchers(HttpMethod.GET, "/api/users/**").hasAuthority("SCOPE_read:users");
```

To require a specific permission (`admin` in the example below):

```java
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/admin/**").hasAuthority("PERMISSION_admin");
```

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

Expand All @@ -93,7 +102,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.forRS256("YOUR_API_AUDIENCE", "YOUR_API_ISSUER")
.configure(http)
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/users/**").hasAuthority("read:users");
.antMatchers(HttpMethod.GET, "/api/users/**").hasAuthority("SCOPE_read:users")
.antMatchers(HttpMethod.GET, "/api/admin/**").hasAuthority("PERMISSION_admin");
}
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.*;

public class AuthenticationJsonWebToken implements Authentication, JwtAuthentication {

private final static String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
private final static String PERMISSION_AUTHORITY_PREFIX = "PERMISSION_";

private final DecodedJWT decoded;
private boolean authenticated;

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

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
String scope = decoded.getClaim("scope").asString();
if (scope == null || scope.trim().isEmpty()) {
return new ArrayList<>();
}
final String[] scopes = scope.split(" ");
List<SimpleGrantedAuthority> authorities = new ArrayList<>(scopes.length);
for (String value : scopes) {
authorities.add(new SimpleGrantedAuthority(value));
}
List<SimpleGrantedAuthority> scopes = getScopeAuthorities();
List<SimpleGrantedAuthority> permissions = getPermissionAuthorities();

List<GrantedAuthority> authorities = new ArrayList<>();
authorities.addAll(scopes);
authorities.addAll(permissions);

return authorities;
}

Expand Down Expand Up @@ -83,4 +82,34 @@ public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentExce
public String getName() {
return decoded.getSubject();
}

private List<SimpleGrantedAuthority> getScopeAuthorities() {
String scope = decoded.getClaim("scope").asString();
if (scope == null || scope.trim().isEmpty()) {
return Collections.emptyList();
}
final String[] scopes = scope.split(" ");
List<SimpleGrantedAuthority> authorities = new ArrayList<>(scopes.length * 2);
for (String value : scopes) {
// For backwards-compatibility, create authority without scope prefix
authorities.add(new SimpleGrantedAuthority(value));
authorities.add(new SimpleGrantedAuthority(SCOPE_AUTHORITY_PREFIX + value));
}
return authorities;
}

private List<SimpleGrantedAuthority> getPermissionAuthorities() {
String[] permissions = decoded.getClaim("permissions").asArray(String.class);

if (permissions == null || permissions.length == 0) {
return Collections.emptyList();
}

List<SimpleGrantedAuthority> authorities = new ArrayList<>(permissions.length);
for (String value : permissions) {
authorities.add(new SimpleGrantedAuthority(PERMISSION_AUTHORITY_PREFIX + value));
}

return authorities;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.spring.security.api.authentication.AuthenticationJsonWebToken;
import org.hamcrest.Matchers;
import org.hamcrest.collection.IsCollectionWithSize;
import org.hamcrest.collection.IsEmptyCollection;
Expand All @@ -15,6 +14,7 @@
import org.springframework.security.core.GrantedAuthority;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

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

AuthenticationJsonWebToken auth = new AuthenticationJsonWebToken(token, verifier);
assertThat(auth, is(notNullValue()));

Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
assertThat(authorities, is(notNullValue()));
assertThat(authorities, is(IsCollectionWithSize.hasSize(4)));
assertThat(authorities, containsInAnyOrder(
hasProperty("authority", is("auth0")),
hasProperty("authority", is("auth10")),
hasProperty("authority", is("SCOPE_auth0")),
hasProperty("authority", is("SCOPE_auth10"))
));
}

@Test
public void shouldGetEmptyAuthoritiesOnEmptyPermissionsClaim() throws Exception {
String token = JWT.create()
.withArrayClaim("permissions", new String[]{})
.sign(hmacAlgorithm);

AuthenticationJsonWebToken auth = new AuthenticationJsonWebToken(token, verifier);
assertThat(auth, is(notNullValue()));
assertThat(auth.getAuthorities(), is(notNullValue()));
assertThat(auth.getAuthorities(), is(IsEmptyCollection.empty()));
}

@Test
public void shouldGetPermissionsAsAuthorities() throws Exception {
String[] permissionsClaim = {"read:permission", "write:permission"};
String token = JWT.create()
.withArrayClaim("permissions", permissionsClaim)
.sign(hmacAlgorithm);

AuthenticationJsonWebToken auth = new AuthenticationJsonWebToken(token, verifier);
assertThat(auth, is(notNullValue()));
assertThat(auth.getAuthorities(), is(notNullValue()));
assertThat(auth.getAuthorities(), is(IsCollectionWithSize.hasSize(2)));

ArrayList<GrantedAuthority> authorities = new ArrayList<>(auth.getAuthorities());
assertThat(authorities.get(0), is(notNullValue()));
assertThat(authorities.get(0).getAuthority(), is("auth0"));
assertThat(authorities.get(1), is(notNullValue()));
assertThat(authorities.get(1).getAuthority(), is("auth10"));
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
assertThat(authorities, IsCollectionWithSize.hasSize(2));
assertThat(authorities, containsInAnyOrder(
hasProperty("authority", is("PERMISSION_" + permissionsClaim[0])),
hasProperty("authority", is("PERMISSION_" + permissionsClaim[1]))
));
}
}

0 comments on commit fd3236e

Please sign in to comment.