Reputation: 26858
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.
Upvotes: 5
Views: 7507
Reputation: 26858
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.
Upvotes: 5