尝试在 Spring 引导中实施 Azure AD 身份验证和授权时出现异常

Getting an exception when tried to implement Azure AD authentication and authorization in Spring Boot

我收到以下错误:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Unsatisfied dependency expressed through method 'setFilterChainProxySecurityConfigurer' parameter 1; nested exception is org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2WebSecurityConfiguration$OAuth2WebSecurityConfigurerAdapter': Unsatisfied dependency expressed through method 'setContentNegotationStrategy' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Unsatisfied dependency expressed through method 'setConfigurers' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration': Unsatisfied dependency expressed through method 'setClientRegistrationRepository' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientRegistrationRepositoryConfiguration': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'spring.security.oauth2.client-org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Client id must not be empty.

即使我在 application.properties 中提供了客户端 ID。

我关注了以下link:https://docs.microsoft.com/en-us/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-active-directory

POM:

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.6.RELEASE</version>
    <relativePath />
    <!-- lookup parent from repository -->
</parent>
<groupId>com.mmm</groupId>
<artifactId>UserMgmt</artifactId>
<version>1.0.3</version>
<packaging>war</packaging>
<name>UserMgmt</name>
<description>User Management</description>
<!-- FIXME change it to the project's website -->
<!-- <url>http://www.example.com</url> -->
<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.microsoft.sqlserver</groupId>
        <artifactId>mssql-jdbc</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
    </dependency>
        <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-dynamodb</artifactId>
        <version>1.11.956</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-lambda</artifactId>
        <version>1.11.207</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.janino</groupId>
        <artifactId>janino</artifactId>
        <version>2.6.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
    </dependency>
    <dependency>     
        <groupId>com.google.code.gson</groupId>     
        <artifactId>gson</artifactId>     
        <version>2.8.6</version>     
        </dependency>   
        <dependency>
        <groupId>com.sun.mail</groupId>
        <artifactId>javax.mail</artifactId>
        <version>1.6.2</version>
    </dependency>
    
    <dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>azure-active-directory-spring-boot-starter</artifactId>
    <version>2.1.6</version>
    </dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>


</dependencies>
<build> 
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
        
    
    <pluginManagement>

        <plugins>
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.1.0</version>
            </plugin>
                <plugin>
                <groupId>org.jboss.as.plugins</groupId>
                <artifactId>jboss-as-maven-plugin</artifactId>
                <version>7.9.Final</version>
                </plugin>
            
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>3.0.2</version>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.1</version>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.6</version>
            </plugin>
            <plugin>
                <artifactId>maven-install-plugin</artifactId>
                <version>2.5.2</version>
            </plugin>
            <plugin>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>2.8.2</version>
            </plugin>
        </plugins>
    </pluginManagement>
</build>
</project>

主线:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class UserApplication extends SpringBootServletInitializer {
private static final Logger LOG = 
LoggerFactory.getLogger(UserApplication.class.getName());


public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);

}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder 
application) {
return application.sources(UserApplication.class);
}

@Configuration
public class DataSourceConfiguration {/*creds*/}

控制器

@RequestMapping(value = "/")
@PreAuthorize("hasRole('ROLE_group1')"+"|| hasRole('ROLE_group2')"+"|| 
hasRole('ROLE_group3')")

APPLICATION.PROPERTIES

azure.activedirectory.tenant-id=xxxxxxxxxxxxxxx
azure.activedirectory.client-id=xxxxxxxxxxxxxxx
azure.activedirectory.client-secret=xxxxxxxxxxxxxx
azure.activedirectory.user-group.allowed-groups=group1,group2,group3

我用

更新了我的 POM
<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>azure-active-directory-spring-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
    <version>2.4.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.4.5</version>
</dependency>

更新POM后的新错误:

 ERROR org.springframework.boot.SpringApplication [main] Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Unsatisfied dependency expressed through method 'setFilterChainProxySecurityConfigurer' parameter 1; nested exception is org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.azure.spring.aad.webapp.AADWebAppConfiguration$DefaultAADWebSecurityConfigurerAdapter': Unsatisfied dependency expressed through method 'setContentNegotationStrategy' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration': Unsatisfied dependency expressed through method 'setConfigurers' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration$OAuth2ClientWebMvcSecurityConfiguration': Unsatisfied dependency expressed through method 'setAuthorizedClientRepository' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'authorizedClientRepository' defined in class path resource [com/azure/spring/aad/webapp/AADWebAppConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository]: Factory method 'authorizedClientRepository' threw exception; nested exception is java.lang.NoClassDefFoundError: org/springframework/security/oauth2/client/OAuth2AuthorizedClientProvider

我已经更新了我的 POM,现在构建正常,但在登录时我收到 AADSTS50011:请求中指定的回复 URL 与配置的回复 URL 不匹配对于应用程序:'18da6914-......................'。我添加了以下重定向 URI:localhost:8080localhost:8080/login/oauth2/code.

由于我对此完全陌生,如有任何帮助,我将不胜感激。

您的代码看起来是正确的。但是由于错误显示 “嵌套异常是 java.lang.IllegalStateException:客户端 ID 不能为空。”,您需要再次检查 application.properties 并确保它是正确的.

并且该示例需要三个依赖项(spring-boot-starter-oauth2-clientspring-boot-starter-webazure-spring-boot-starter-active-directory),您可以尝试使用较新版本更新您的pom。

教程后面有我的代码。

主线:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class SpringBootSampleAdApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootSampleAdApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder 
    application) {
        return application.sources(SpringBootSampleAdApplication.class);
    }

    @Configuration
    public class DataSourceConfiguration {/*creds*/}
}

控制器:

@RestController
public class HelloController {

    @PreAuthorize("hasRole('ROLE_pamela-group1')"+"|| hasRole('ROLE_pamela-group2')")
    @RequestMapping("/test")
    public String helloWorld() {
        return "Hello Users!";
    }
}

application.properties:

azure.activedirectory.tenant-id=xxxxx
azure.activedirectory.client-id=xxxxx
azure.activedirectory.client-secret=xxxxx
azure.activedirectory.user-group.allowed-groups=pamela-group1,pamela-group2

测试结果:

看来主要问题出在您的 Spring 引导版本上。您已指定 2.1.6.RELEASE,但较新的版本可能更合适。

这里有一些观察结果,我将按照建议的修复方法进行跟进。

您添加了 Spring Security 5.4.x 依赖项,但是,这些依赖项用于 Spring Boot 2.4.x.

本教程基于 Spring Boot 2.3.x。 Spring Security 5.3.x 依赖项旨在与 Spring Boot 2.3.x.

一起使用

您得到 NoClassDefFoundError 的原因是 AADWebAppConfiguration 正在尝试查找直到 Spring Boot 2.1 之后才引入的 class。

我认为最好的办法是确保您的依赖项与您正在学习的教程保持一致,或者它们是最新的依赖项。

具体来说,你有这个:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath />
        <!-- lookup parent from repository -->
    </parent>

还有这个:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
    <version>2.4.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.4.5</version>
</dependency>

但它们彼此不兼容。

我建议从本教程使用的 Spring 引导版本开始,以便让一切正常运行。