2
votes

So we have this application that has two parts

  1. Front end ui - using Angular JS
  2. Back end - rest api using Spring boot

Front end is secured using microsoft-adal-angular6 library to authenticate with Azure Active Directory

My question is what is the right way to secure the Back end so only active directory authenticated users can access the API?

1

1 Answers

2
votes

I would suggest to use a jwt token, that is attached to every request to your backend as 'Authorization' header. The token consists of three parts, where one is holding data about the user and one a signature, so you can validate that your token was created by a trusted source. The data part can look something like this:

{
    "iss": "Online JWT Builder",
    "iat": 1580283510,
    "exp": 1611819510,
    "aud": "www.example.com",
    "sub": "[email protected]",
    "GivenName": "Johnny",
    "roles": ["PROJECT_MANAGER", "ADMIN"]
    "scope": "WEBAPP"
}

On the spring side, I would suggest using Spring Security 5 with the latest configuration. You will need those dependencies:

         <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.x.x.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
            <version>5.x.x.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
            <version>5.x.x.RELEASE</version>

Now you can enable security and configure it with a configuration class. Inside you can define which scope the request must have, how to sign the token and with route should be public or secured.


@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
    String jwkSetUri;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .cors().disable()
            .authorizeRequests()
            .antMatchers(("/webapp/**")).hasAuthority("SCOPE_WEBAPP")
            .antMatchers(("/admin/**")).hasRole("ADMIN")
            ...
            .and()
            .oauth2ResourceServer().jwt(jwtConfigurer -> jwtConfigurer.decoder(jwtDecoder())
            .jwtAuthenticationConverter(new CustomJwtAuthenticationConverter()))
            ...
        // @formatter:on
    }

    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
    }
}

I had to use a custom JwtConverter to get the roles from the jwt, but it depends on how you do it, I guess.

public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    private final JwtGrantedAuthoritiesConverter defaultGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();

    public CustomJwtAuthenticationConverter() {
    }

    @Override
    public AbstractAuthenticationToken convert(@NotNull final Jwt jwt) {
        Collection<GrantedAuthority> authorities = Stream
                .concat(defaultGrantedAuthoritiesConverter.convert(jwt).stream(), extractResourceRoles(jwt).stream())
                .collect(Collectors.toSet());
        return new JwtAuthenticationToken(jwt, authorities);
    }

    private static Collection<? extends GrantedAuthority> extractResourceRoles(final Jwt jwt) {
        Collection<String> userRoles = jwt.getClaimAsStringList("roles");
        if (userRoles != null)
            return userRoles
                    .stream()
                    .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                    .collect(Collectors.toSet());
        return Collections.emptySet();
    }
}

This enables you to secure your application on an url basis.

The roles in the jwt, the JwtConverter and the @EnableGlobalMethodSecurity annotation enable you to secure even on a method level.

@Transactional
@PreAuthorize("hasRole('ROLE_PROJECT_MANAGER')")
public Page<Project> findAll(Pageable pageable) {
    return projectRepository.findAll(pageable);
}

Azure Active Directory should support jwt, but I don't have experience with this IDP. What I can't answer is, how you can inject custom claims like roles inside the token and where to get the jwks (Json Web Key Set), which is used to validate the token's signature.