You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular调用SpringBoot接口:POST请求401,GET正常,Postman均正常

问题:Angular POST请求到Spring Boot微服务返回401,GET请求正常

使用Okta官方Angular示例项目测试与Spring Boot应用集成,搭建了带Spring Security的微服务。通过Postman调用接口时GET和POST均正常,但Angular发起POST请求返回401 Unauthorized,服务器日志显示认证为匿名身份;将控制器方法改为GET后问题解决,疑惑POST请求失败的原因。

相关信息

服务器日志

2024-01-07 19:19:18.499 DEBUG 10476 --- [nio-8040-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.4g.account.controller.AccountController#getOrders()
2024-01-07 19:19:18.511 DEBUG 10476 --- [nio-8040-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-01-07 19:19:18.520 DEBUG 10476 --- [nio-8040-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Failed to authorize filter invocation [POST /account/orders] with attributes [authenticated]
2024-01-07 19:19:18.524 DEBUG 10476 --- [nio-8040-exec-1] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2024-01-07 19:19:18.524 DEBUG 10476 --- [nio-8040-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request

Angular组件代码(profile.component.ts)

import {HttpClient} from '@angular/common/http';
import { Component, Inject, OnInit } from '@angular/core';
import { IDToken, OktaAuth } from '@okta/okta-auth-js';
import { OKTA_AUTH } from '@okta/okta-angular';

@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
  orders!: { name: string; value: unknown }[];

  constructor(@Inject(OKTA_AUTH) public oktaAuth: OktaAuth,private http: HttpClient) {
  }

  async ngOnInit() {
    await this.getOrders();
  }
  async getOrders() {
    const accessToken: AccessToken = await this.oktaAuth.tokenManager.get('accessToken') as AccessToken;
    const apiUrl = 'http://localhost:8090/account/orders';
    const headers = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${accessToken.accessToken}`
    };
    return this.http.post(apiUrl, { headers }).subscribe(data => {
      this.orders = Object.entries(data).map(entry => ({ name: entry[0], value: entry[1] }));
  })
}
}

Spring Security配置(SecurityConfiguration.java)

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    Environment environment;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and().oauth2ResourceServer()
                .authenticationManagerResolver(new JwtIssuerAuthenticationManagerResolver("https://xxxx.okta.com/oauth2/default"));
        http.cors();
    }

}

Spring控制器代码(AccountController.java)

@RestController
@CrossOrigin(origins = {"http://localhost:4200"})
@RequestMapping("/account")
public class AccountController {
    @Autowired
    private SecurityContextAccessor securityContextAccessor;
    @Autowired
    private OrderService orderService;
    @PostMapping(value = "/orders")
    public @ResponseBody ResponseEntity<String> getOrders() {
        try{
            return new ResponseEntity<>(orderService.getOrders(securityContextAccessor.getJwtToken()), HttpStatus.OK);
        }catch(AccountException ae){
            return new ResponseEntity<>("", HttpStatus.FORBIDDEN);
        }
    }
}
问题原因与解决办法

核心原因

Angular的HttpClient.post()方法参数传递错误:该方法的第二个参数是请求体数据,第三个参数才是包含headers的配置对象。当前代码把headers当成请求体传递,导致实际请求中根本没有携带Authorization头,Spring Security因此识别为匿名身份,返回401。

修正后的Angular代码

调整http.post的参数顺序,将headers放在配置参数中:

async getOrders() {
    const accessToken: AccessToken = await this.oktaAuth.tokenManager.get('accessToken') as AccessToken;
    const apiUrl = 'http://localhost:8090/account/orders';
    const headers = {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${accessToken.accessToken}`
    };
    // 第二个参数传空请求体(接口无需参数),第三个参数传headers配置
    return this.http.post(apiUrl, {}, { headers }).subscribe(data => {
      this.orders = Object.entries(data).map(entry => ({ name: entry[0], value: entry[1] }));
  })
}

补充说明

  • GET请求正常是因为HttpClient.get()的第二个参数就是配置对象,之前的调用方式正确,能正常携带Authorization头。
  • Postman调用正常是因为手动正确设置了请求头,不存在参数传递错误的问题。
  • 控制器方法用@PostMapping但方法名为getOrders,语义上不合理,建议根据实际功能调整(如改为createOrders),但这不是导致401的直接原因。

内容的提问来源于stack exchange,提问作者softechie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 15:23:13