春季安全性:使用CORS Preflights获得401s(尽管http.cors())

时间:2020-05-11 08:14:30

标签: spring-boot kotlin spring-security cors

对于初学者,我想使用基本身份验证来保护我的rest-api的一部分。 当我尝试从React客户端访问端点时,我在预检请求中总是收到401。

我尝试遵循此指南未成功: https://www.baeldung.com/spring-security-cors-preflight

我不确定这是否是问题的一部分,但是只能使用某些自定义的HTTP标头访问另一部分。

我正在使用方法安全性:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = false)
class MethodSecurityConfig : GlobalMethodSecurityConfiguration() {
    override fun customMethodSecurityMetadataSource(): MethodSecurityMetadataSource = SecurityMetadataSource()

    override fun accessDecisionManager(): AccessDecisionManager = super.accessDecisionManager().apply {
        this as AbstractAccessDecisionManager
        decisionVoters.add(PrivilegeVoter())
    }
}

这是我的安全配置:

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
class SecurityConfig : WebSecurityConfigurerAdapter() {
    private val deviceRequestHeaderName: String = "X-DEVICE-ID"    
    private val platformRequestHeaderName: String = "X-PLATFORM-ID"

    @Autowired
    lateinit var users: AppUserRepository

    @Autowired
    lateinit var backendUsers: BackendUserRepository

    @Autowired
    lateinit var roles: RoleRepository

    val authManager by lazy { authenticationManager() }

    private val authProvider by lazy {
        PreAuthenticatedAuthenticationProvider().apply {
            setPreAuthenticatedUserDetailsService {
                val authId = it.principal as UserAuthId
                if (authId.deviceId == null) throw UsernameNotFoundException("No device-id to search for.")
                if (authId.platform == null) throw UsernameNotFoundException("Platform not specified.")
                val platform = try {
                    ApplicationPlatform.valueOf(authId.platform)
                } catch (e: IllegalArgumentException) {
                    throw UsernameNotFoundException("Unknown platform ${authId.platform}.")
                }
                val existingUser = users.findByUserDeviceIdAndPlatform(authId.deviceId, platform)
                if (existingUser != null) return@setPreAuthenticatedUserDetailsService existingUser

                users.save(AppUser(authId.deviceId, platform, roles))
            }
        }
    }

    val passwordEncoder by lazy { BCryptPasswordEncoder() }

    private val deviceIdFilter by lazy {
        HeaderFieldAuthFilter(deviceRequestHeaderName, platformRequestHeaderName).apply {
            setAuthenticationManager(authManager)
        }
    }

    override fun configure(auth: AuthenticationManagerBuilder) = auth {
        authenticationProvider(authProvider)

        val userDetailsService = BackendUserDetailsService(backendUsers)
        userDetailsService(userDetailsService).passwordEncoder(passwordEncoder)
    }

    override fun configure(http: HttpSecurity) = http {
        session {
            sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        }
        exceptionHandling()

        addFilter(deviceIdFilter)
        authorizeRequests().anyRequest().authenticated()
        csrf().disable()
        httpBasic()

        cors().configurationSource { request ->
            CorsConfiguration().apply {
                allowedOrigins = listOf(ALL)
                allowedMethods = listOf(GET, POST, DELETE, PUT, OPTIONS).map { it.name }
                allowedHeaders = listOf(ALL)
                allowCredentials = true
                maxAge = 3600
            }
        }
    }

    @Bean
    fun auditorProvider(): AuditorAware<User> = AuditorAware<User> {
        val authentication = SecurityContextHolder.getContext().authentication
        val user = authentication.run { if (isAuthenticated) principal as? User else null }
        return@AuditorAware Optional.ofNullable(user)
    }
}

2 个答案:

答案 0 :(得分:0)

我可以通过手动从身份验证中排除预检请求来解决。 添加

antMatchers(OPTIONS, "/**").permitAll()

authorizeRequests()配置即可完成。 请注意,Options是对这样导入的HttpMethod枚举值的直接引用

import org.springframework.http.HttpMethod.*

帮助我到达那里的Stackoverflow帖子:

我最初以为,这应该由cors配置处理-显然不是。

答案 1 :(得分:0)

要为单个其余端点启用CORS,您可以使用以下注释它:

int currentPage = 0;
ValueNotifier<int> _changePage = ValueNotifier(currentPage);

// Then in my stateful build I have:

ListView.builder(
    controller: _homeController,
    itemCount: observations.getCategoryCount(
      dictionaryProperties.getSpecificCategory(currentPage)),
    itemBuilder: (BuildContext context, int index) {
      return CheckboxListTile([...omitted code...);
    },
    shrinkWrap: true,
    physics: ClampingScrollPhysics(),
),

// And on a bottom appBar I have the button to click:

ValueListenableBuilder(
  valueListenable: _changePage,
  builder: (BuildContext context, int value, Widget child) {
    return IconButton(
      color: Colors.white,
      icon: Icon(Icons.arrow_forward),
      onPressed: () {
        _homeController.animateTo(
          0.0,
          curve: Curves.easeOut,
          duration: const Duration(milliseconds: 300),
        );
        setState(() {
          currentPage++;
        });
      },
    );
  },
),

要允许所有端点使用CORS,您可以使用如下所示的bean:

@CrossOrigin
相关问题