2
votes

I am using Spring Boot 2.2.0 with azure-active-directory-b2c-spring-boot-starter 2.2.0. I managed to secure a Thymeleaf web page with that (following their tutorial). Now, I want to have a REST API that is secured in the same way, as the actual application will be a mobile app that does REST calls to my Spring Boot backend.

I already figured out how to get a token with the password grant flow using:

POST https://<my-tenant-id>.b2clogin.com/<my-tenant-id.onmicrosoft.com/oauth2/v2.0/token?p=B2C_1_<my-custom-policy>

(with username and password as parameters)

So a mobile app could use that call. But how should I configure my Spring Boot app so that using Authorization: Bearer <access-token> on API calls works? What dependencies/starters do I need and how should I configure things?

UPDATE:

I tried adding:

<dependency>
  <groupId>org.springframework.security.oauth</groupId>
  <artifactId>spring-security-oauth2</artifactId>
  <version>2.3.7.RELEASE</version>
</dependency>

With:

@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class OAuth2ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("my-azure-b2c-test");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/**")
            .authenticated();
    }
}

But when I do a request on my Spring Boot app, I get a 401 with "invalid_token" error.

1
Have you tried anything?Michael
You have to check the JWT token that is contained in the incoming request header in your Spring Boot app (for every incoming request).ieggel
@ieggel Can you explain in detail how this should be done?Wim Deblauwe
@Michael I updated the question with what I have triedWim Deblauwe
@WimDeblauwe I have no experience with Azure, but what i mentioned before was how it has to be done in general. In my opinion the library you are using can already handle the JWT verification for you and integrate it into the spring security context. There seem to be some examples here: github.com/microsoft/azure-spring-boot/tree/master/…ieggel

1 Answers

3
votes

Solution seems to be quite simple once you know it.

First, add the following dependencies:

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

Next specify the spring.security.oauth2.resourceserver.jwt.jwk-set-uri property in your application.properties file.

To know this value, do a GET (using cURL or other tool) on https://<my-tenant-id>.b2clogin.com/<my-tenant-id>.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_<my-custom-policy>

This will return a JSON body with jwks_uri value. Take that value and put it in your application.properties file.

Now create this Java class in the project:

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/**")
            .authenticated()
            .and()
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
    }
}

If you now have controller like this:

@RestController
public class ApiController {

    @GetMapping("/api/test")
    public String apiTest(@AuthenticationPrincipal Principal principal) {
        return "test " + principal;
    }
}

You will see that the principal is not-null if you do a GET on api/test with a correct Authorization header (and it is of type org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken)

The only thing that is unfortunate is that the Principal has no authorities, something I still need to figure out why.