From 62f5d288b02bf78325923cf76331714f9668bd43 Mon Sep 17 00:00:00 2001 From: Andrew Wagner Date: Sun, 25 Oct 2020 21:06:10 -0500 Subject: [PATCH] feat(roles): added support for user roles Updated to encode and decode roles into the JWT and provided fields in the User class for the granted authorities --- .../authentication/JwtTokenProvider.java | 17 ++++++++++++++++- .../authentication/SecurityContextMutator.java | 3 +-- .../exception/NotFoundException.java | 18 ++++++++++++++++++ .../java/com/bts/essentials/model/User.java | 10 +++++++++- .../JwtAuthenticationFilterTest.java | 2 +- .../authentication/JwtTokenProviderTest.java | 17 ++++++++++++++++- .../SecurityContextMutatorTest.java | 12 +----------- .../AbstractSecurityConfigurerAdapterTest.java | 4 ++-- .../service/TestUsersServiceImpl.java | 5 ++++- .../bts/essentials/testutils/DataCreation.java | 11 +++++++---- 10 files changed, 75 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/bts/essentials/exception/NotFoundException.java diff --git a/src/main/java/com/bts/essentials/authentication/JwtTokenProvider.java b/src/main/java/com/bts/essentials/authentication/JwtTokenProvider.java index a8c6e0a..76eb9f5 100644 --- a/src/main/java/com/bts/essentials/authentication/JwtTokenProvider.java +++ b/src/main/java/com/bts/essentials/authentication/JwtTokenProvider.java @@ -4,10 +4,15 @@ import com.bts.essentials.model.User; import io.jsonwebtoken.*; import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Component; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; /** * Created by wagan8r on 8/16/18. @@ -18,6 +23,7 @@ public class JwtTokenProvider { private static final String FIRST_NAME = "first_name"; private static final String LAST_NAME = "last_name"; private static final String IDENTITY_PROVIDER = "identity_provider"; + private static final String ROLES = "roles"; @Value("${essentials.jwt.secret}") private String jwtSecret; @@ -34,6 +40,9 @@ public String getToken(User user) { .claim(FIRST_NAME, user.getFirstName()) .claim(LAST_NAME, user.getLastName()) .claim(IDENTITY_PROVIDER, user.getIdentityProvider()) + .claim(ROLES, user.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.toList())) .setIssuedAt(now) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, jwtSecret) @@ -46,11 +55,17 @@ public User getUser(String jwt) { .setSigningKey(jwtSecret) .parseClaimsJws(jwt) .getBody(); + List authorities = new ArrayList<>(); + Object roles = claims.get(ROLES); + if (roles instanceof List) { + authorities = (List) roles; + } return new User(UUID.fromString(claims.getSubject()), String.valueOf(claims.get(EMAIL)), String.valueOf(claims.get(FIRST_NAME)), String.valueOf(claims.get(LAST_NAME)), - IdentityProvider.valueOf(String.valueOf(claims.get(IDENTITY_PROVIDER)))); + IdentityProvider.valueOf(String.valueOf(claims.get(IDENTITY_PROVIDER))), + authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())); } protected void validateJwt(String jwt) { diff --git a/src/main/java/com/bts/essentials/authentication/SecurityContextMutator.java b/src/main/java/com/bts/essentials/authentication/SecurityContextMutator.java index 6d154f3..5a06e8d 100644 --- a/src/main/java/com/bts/essentials/authentication/SecurityContextMutator.java +++ b/src/main/java/com/bts/essentials/authentication/SecurityContextMutator.java @@ -23,6 +23,7 @@ public void setAuthentication(User user) { UserAuthentication userAuthentication = new UserAuthentication(); userAuthentication.setUserPrincipal(user); userAuthentication.setAuthenticated(true); + userAuthentication.setAuthorities(user.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(userAuthentication); } @@ -36,8 +37,6 @@ public User getAuthenticationUser() { User user = null; if (authentication instanceof UserAuthentication) { user = ((UserAuthentication) authentication).getUserPrincipal(); - } else if (authentication != null) { - throw new IllegalStateException("Invalid Authentication has been set in the security context"); } return user; } diff --git a/src/main/java/com/bts/essentials/exception/NotFoundException.java b/src/main/java/com/bts/essentials/exception/NotFoundException.java new file mode 100644 index 0000000..f1d2638 --- /dev/null +++ b/src/main/java/com/bts/essentials/exception/NotFoundException.java @@ -0,0 +1,18 @@ +package com.bts.essentials.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.NOT_FOUND) +public class NotFoundException extends RuntimeException { + public NotFoundException() { + } + + public NotFoundException(String message) { + super(message); + } + + public NotFoundException(String message, Exception e) { + super(message, e); + } +} diff --git a/src/main/java/com/bts/essentials/model/User.java b/src/main/java/com/bts/essentials/model/User.java index bdd404f..3a4a1d5 100644 --- a/src/main/java/com/bts/essentials/model/User.java +++ b/src/main/java/com/bts/essentials/model/User.java @@ -1,7 +1,10 @@ package com.bts.essentials.model; import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; /** @@ -10,9 +13,14 @@ @Data public class User extends BasicUser { private UUID id; + private List authorities = new ArrayList<>(); - public User(UUID id, String email, String firstName, String lastName, IdentityProvider identityProvider) { + public User() { + } + + public User(UUID id, String email, String firstName, String lastName, IdentityProvider identityProvider, List authorities) { super(email, firstName, lastName, identityProvider); this.id = id; + this.authorities = authorities; } } diff --git a/src/test/java/com/bts/essentials/authentication/JwtAuthenticationFilterTest.java b/src/test/java/com/bts/essentials/authentication/JwtAuthenticationFilterTest.java index 2d53cb1..02360df 100644 --- a/src/test/java/com/bts/essentials/authentication/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/bts/essentials/authentication/JwtAuthenticationFilterTest.java @@ -37,7 +37,7 @@ public void before() { @Test public void doFilterInternal() throws Exception { - User user = createUser(); + User user = createUser(null); String jwt = jwtTokenProvider.getToken(user); HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); HttpServletResponse httpServletResponse = mock(HttpServletResponse.class); diff --git a/src/test/java/com/bts/essentials/authentication/JwtTokenProviderTest.java b/src/test/java/com/bts/essentials/authentication/JwtTokenProviderTest.java index b28c99e..8ee15f3 100644 --- a/src/test/java/com/bts/essentials/authentication/JwtTokenProviderTest.java +++ b/src/test/java/com/bts/essentials/authentication/JwtTokenProviderTest.java @@ -35,7 +35,7 @@ public class JwtTokenProviderTest extends BaseIntegrationTest { @Before public void before() { - user = createUser(); + user = createUser(null); } @Test @@ -53,6 +53,21 @@ public void getUser() { assertEquals(user.getFirstName(), jwtUser.getFirstName()); assertEquals(user.getLastName(), jwtUser.getLastName()); assertEquals(user.getIdentityProvider(), jwtUser.getIdentityProvider()); + assertEquals(user.getAuthorities(), jwtUser.getAuthorities()); + } + + @Test + public void getUserWithRole() { + user = createUser("TEST_ROLE"); + String jwt = jwtTokenProvider.getToken(user); + User jwtUser = jwtTokenProvider.getUser(jwt); + assertEquals(user.getId(), jwtUser.getId()); + assertEquals(user.getEmail(), jwtUser.getEmail()); + assertEquals(user.getFirstName(), jwtUser.getFirstName()); + assertEquals(user.getLastName(), jwtUser.getLastName()); + assertEquals(user.getIdentityProvider(), jwtUser.getIdentityProvider()); + assertEquals(user.getAuthorities(), jwtUser.getAuthorities()); + assertEquals("TEST_ROLE", jwtUser.getAuthorities().get(0).getAuthority()); } @Test diff --git a/src/test/java/com/bts/essentials/authentication/SecurityContextMutatorTest.java b/src/test/java/com/bts/essentials/authentication/SecurityContextMutatorTest.java index 884dba9..255e292 100644 --- a/src/test/java/com/bts/essentials/authentication/SecurityContextMutatorTest.java +++ b/src/test/java/com/bts/essentials/authentication/SecurityContextMutatorTest.java @@ -13,7 +13,6 @@ import static com.bts.essentials.testutils.DataCreation.createUser; import static com.bts.essentials.testutils.DataCreation.createUserAuthentication; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; /** * Created by wagan8r on 10/14/18. @@ -33,7 +32,7 @@ public void before() { @Test public void setAuthentication() { - User user = createUser(); + User user = createUser(null); securityContextMutator.setAuthentication(user); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); User principal = (User) authentication.getPrincipal(); @@ -54,13 +53,4 @@ public void getAuthenticationUserNoAuthentication() { User authenticationUser = securityContextMutator.getAuthenticationUser(); assertEquals(null, authenticationUser); } - - @Test - public void getAuthenticationUserInvalidAuthentication() { - Authentication authentication = mock(Authentication.class); - SecurityContextHolder.getContext().setAuthentication(authentication); - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Invalid Authentication has been set in the security context"); - securityContextMutator.getAuthenticationUser(); - } } diff --git a/src/test/java/com/bts/essentials/authentication/config/AbstractSecurityConfigurerAdapterTest.java b/src/test/java/com/bts/essentials/authentication/config/AbstractSecurityConfigurerAdapterTest.java index 1da198e..5688c7d 100644 --- a/src/test/java/com/bts/essentials/authentication/config/AbstractSecurityConfigurerAdapterTest.java +++ b/src/test/java/com/bts/essentials/authentication/config/AbstractSecurityConfigurerAdapterTest.java @@ -53,7 +53,7 @@ public void permitTestPath() throws Exception { @Test public void filterTestPath() throws Exception { - User user = createUser(); + User user = createUser(null); String jwt = jwtTokenProvider.getToken(user); mockMvc.perform(get("/secured/resource").header(HttpHeaders.AUTHORIZATION, String.format("%s %s", JwtHeaderParser.BEARER, jwt))) .andDo(print()) @@ -72,7 +72,7 @@ public void filterTestPathNoJwt() throws Exception { @Test public void jwtAdviceSetsAuthorizationHeader() throws Exception { - User user = createUser(); + User user = createUser(null); String jwt = jwtTokenProvider.getToken(user); mockMvc.perform(get("/excluded/resource").header(HttpHeaders.AUTHORIZATION, String.format("%s %s", JwtHeaderParser.BEARER, jwt))) .andDo(print()) diff --git a/src/test/java/com/bts/essentials/service/TestUsersServiceImpl.java b/src/test/java/com/bts/essentials/service/TestUsersServiceImpl.java index 1ed677a..906a885 100644 --- a/src/test/java/com/bts/essentials/service/TestUsersServiceImpl.java +++ b/src/test/java/com/bts/essentials/service/TestUsersServiceImpl.java @@ -2,6 +2,8 @@ import com.bts.essentials.model.BasicUser; import com.bts.essentials.model.User; +import com.google.common.collect.ImmutableList; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Service; import java.util.UUID; @@ -11,6 +13,7 @@ class TestUsersServiceImpl implements UsersService { @Override public User getOrCreateUser(BasicUser basicUser) { - return new User(UUID.randomUUID(), basicUser.getEmail(), basicUser.getFirstName(), basicUser.getLastName(), basicUser.getIdentityProvider()); + return new User(UUID.randomUUID(), basicUser.getEmail(), basicUser.getFirstName(), basicUser.getLastName(), + basicUser.getIdentityProvider(), ImmutableList.of(new SimpleGrantedAuthority("TEST_ROLE"))); } } diff --git a/src/test/java/com/bts/essentials/testutils/DataCreation.java b/src/test/java/com/bts/essentials/testutils/DataCreation.java index d683768..7a8d033 100644 --- a/src/test/java/com/bts/essentials/testutils/DataCreation.java +++ b/src/test/java/com/bts/essentials/testutils/DataCreation.java @@ -4,6 +4,8 @@ import com.bts.essentials.model.IdentityProvider; import com.bts.essentials.model.User; import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload; +import com.google.common.collect.ImmutableList; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import java.util.UUID; @@ -14,17 +16,18 @@ public class DataCreation { public static UserAuthentication createUserAuthentication() { UserAuthentication userAuthentication = new UserAuthentication(); - userAuthentication.setUserPrincipal(createUser()); + userAuthentication.setUserPrincipal(createUser(null)); userAuthentication.setAuthenticated(true); return userAuthentication; } - public static User createUser() { - return new User(UUID.randomUUID(), "nerfherder@bts.com", "Han", "Solo", IdentityProvider.GOOGLE); + public static User createUser(String role) { + return new User(UUID.randomUUID(), "nerfherder@bts.com", "Han", "Solo", IdentityProvider.GOOGLE, + role != null && !role.isEmpty() ? ImmutableList.of(new SimpleGrantedAuthority(role)) : ImmutableList.of()); } public static Payload createPayload() { - User user = createUser(); + User user = createUser(null); Payload payload = new Payload(); payload.setEmail(user.getEmail()); payload.set("given_name", user.getFirstName());