我想通过从Azure AD接收的承载令牌允许在RestApi Sping Boot 3应用程序中访问资源。
POM:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-active-directory</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
我在Azure门户上创建了一个应用程序(通过一些Azure手册),并且我能够通过 Postman 接收访问令牌。
这是我的Sping Boot 属性:
当我试图通过 Postman 访问资源时,包括收到的访问令牌,我得到一个登录信息:
我是否需要一些额外的spring安全配置来允许我访问此资源,或者存在其他问题?
我尝试了以下几个,但没有任何变化。
从SpringBoot 2.1.3迁移到3.0.7。使用旧的解决方案,我能够通过访问令牌正确授权:
public class AuzreAuthFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(AADAuthenticationFilter.class);
private static final String CURRENT_USER_PRINCIPAL = "CURRENT_USER_PRINCIPAL";
private static final String CURRENT_USER_PRINCIPAL_GRAPHAPI_TOKEN = "CURRENT_USER_PRINCIPAL_GRAPHAPI_TOKEN";
private static final String TOKEN_HEADER = "Authorization";
private static final String TOKEN_TYPE = "Bearer ";
private AADAuthenticationProperties aadAuthProps;
private ServiceEndpointsProperties serviceEndpointsProps;
private AzureUserPrincipalManager principalManager;
public AuzreAuthFilter(AADAuthenticationProperties aadAuthProps,
ServiceEndpointsProperties serviceEndpointsProps,
ResourceRetriever resourceRetriever) {
this.aadAuthProps = aadAuthProps;
this.serviceEndpointsProps = serviceEndpointsProps;
this.principalManager = new AzureUserPrincipalManager(serviceEndpointsProps, aadAuthProps, resourceRetriever);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader(TOKEN_HEADER);
if (authHeader != null && authHeader.startsWith(TOKEN_TYPE)) {
try {
final String idToken = authHeader.replace(TOKEN_TYPE, "");
UserPrincipal principal = (UserPrincipal) request
.getSession().getAttribute(CURRENT_USER_PRINCIPAL);
final ClientCredential credential =
new ClientCredential(aadAuthProps.getClientId(), aadAuthProps.getClientSecret());
final AzureADGraphClient client =
new AzureADGraphClient(credential, aadAuthProps, serviceEndpointsProps);
if (principal == null) {
principal = principalManager.buildUserPrincipal(idToken);
request.getSession().setAttribute(CURRENT_USER_PRINCIPAL, principal);
request.getSession().setAttribute(CURRENT_USER_PRINCIPAL_GRAPHAPI_TOKEN, idToken);
}
final Authentication authentication = new PreAuthenticatedAuthenticationToken(
principal, null, client.convertGroupsToGrantedAuthorities(principal.getUserGroups()));
authentication.setAuthenticated(true);
log.info("Request token verification success. {}", authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (ParseException | BadJOSEException | JOSEException ex) {
log.error("Failed to initialize UserPrincipal.", ex);
throw new ServletException(ex);
}
}
filterChain.doFilter(request, response);
}
}
public class AzureUserPrincipalManager {
private final JWKSource<SecurityContext> keySource;
/**
* Creates a new {@link UserPrincipalManager} with a predefined {@link JWKSource}.
* <p>
* This is helpful in cases the JWK is not a remote JWKSet or for unit testing.
*
* @param keySource - {@link JWKSource} containing at least one key
*/
public AzureUserPrincipalManager(JWKSource<SecurityContext> keySource) {
this.keySource = keySource;
}
/**
* Create a new {@link UserPrincipalManager} based of the {@link ServiceEndpoints#getAadKeyDiscoveryUri()} and
* {@link AADAuthenticationProperties#getEnvironment()}.
*
* @param serviceEndpointsProps - used to retrieve the JWKS URL
* @param aadAuthProps - used to retrieve the environment.
* @param resourceRetriever - configures the {@link RemoteJWKSet} call.
*/
public AzureUserPrincipalManager(ServiceEndpointsProperties serviceEndpointsProps,
AADAuthenticationProperties aadAuthProps,
ResourceRetriever resourceRetriever) {
try {
keySource = new RemoteJWKSet<>(new URL(serviceEndpointsProps
.getServiceEndpoints(aadAuthProps.getEnvironment()).getAadKeyDiscoveryUri()), resourceRetriever);
} catch (MalformedURLException e) {
log.error("Failed to parse active directory key discovery uri.", e);
throw new IllegalStateException("Failed to parse active directory key discovery uri.", e);
}
}
public UserPrincipal buildUserPrincipal(String idToken) throws ParseException, JOSEException, BadJOSEException {
final JWSObject jwsObject = JWSObject.parse(idToken);
final ConfigurableJWTProcessor<SecurityContext> validator =
getAadJwtTokenValidator(jwsObject.getHeader().getAlgorithm());
final JWTClaimsSet jwtClaimsSet = validator.process(idToken, null);
final JWTClaimsSetVerifier<SecurityContext> verifier = validator.getJWTClaimsSetVerifier();
verifier.verify(jwtClaimsSet, null);
return new UserPrincipal(jwsObject, jwtClaimsSet);
}
private ConfigurableJWTProcessor<SecurityContext> getAadJwtTokenValidator(JWSAlgorithm jwsAlgorithm) {
final ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
final JWSKeySelector<SecurityContext> keySelector =
new JWSVerificationKeySelector<>(jwsAlgorithm, keySource);
jwtProcessor.setJWSKeySelector(keySelector);
jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<SecurityContext>() {
@Override
public void verify(JWTClaimsSet claimsSet, SecurityContext ctx) throws BadJWTException {
super.verify(claimsSet, ctx);
final String issuer = claimsSet.getIssuer();
if (issuer == null || !issuer.contains("https://sts.windows.net/")
&& !issuer.contains("https://login.microsoftonline.com/")) {
throw new BadJWTException("Invalid token issuer");
}
}
});
return jwtProcessor;
}
}
2条答案
按热度按时间zbq4xfa01#
除了配置的依赖项和属性之外,您还必须配置Spring Security:
8iwquhpp2#
访问令牌可以授权对OAuth2资源服务器的请求(而不是对使用会话保护的OAuth2客户端的请求)=>您应该依赖于
spring-boot-starter-oauth2-resource-server
而不是spring-boot-starter-oauth2-client
,并且具有spring.security.oauth2.resource-server
属性而不是spring.security.oauth2.client
属性。您可以参考official doc以获得详尽的配置选项,并参考my tutorials以获得完整的示例。