Spring Boot + Angular 2 + JWT

Posted

技术标签:

【中文标题】Spring Boot + Angular 2 + JWT【英文标题】: 【发布时间】:2017-09-13 05:16:07 【问题描述】:

我正在使用 Angular 2 并尝试在 get 方法上发送标头参数。我有以下代码:

let tokenUrl2 = "http://localhost:8080/users";

let headers = new Headers();
headers.append('abc', token);

let options = new RequestOptions( headers: headers );
return this.http.get(tokenUrl2, options);

我收到此错误:

Response for preflight is invalid (redirect)

我也添加了这些参数,但我得到了同样的错误:

headers.append('Content-Type', 'application/json');
headers.append('Authorization':'Bearer ' + token);

谁能告诉我发送标头参数的正确方法是什么。非常感谢:)

编辑 1

网址“http://localhost:8080/users”来自 Spring Boot Web 应用程序(它是一个 REST 服务)。我正在尝试将 Angular 应用程序与 Spring Boot 应用程序进行通信。我的想法是向它发送一个先前从另一个 REST 服务获得的令牌。在第一个 REST 服务中,我得到了一个令牌。这是一个带有 POST 的 REST 服务,它可以工作。然后,我通过 GET 方法将此令牌发送到第二个 REST 服务 (http://localhost:8080/users)。这第二部分不起作用。我尝试将 GET 方法更改为 POST,因为在第一部分使用 POST 方法确实可以工作,但是这个新的更改都不起作用。我仍然收到相同的消息:预检响应无效(重定向)

我有这些问题:

如果我的第一个 REST 服务(使用 POST 实现)可以工作,为什么第二个不工作?这是因为我的请求(我的 Angular 应用程序)没有实现 CORS?

我希望有了这些细节,你可以帮助我解决我的问题。

谢谢!

编辑 2

最初,我认为我只是在从 Angular 应用程序向 REST 服务发送标头参数时出错。但是,我进行了调查,发现我的问题涉及更多组件。我讲述我的组件:

我有一个 Angular 应用程序需要使用 Spring Boot 应用程序中的 REST 服务。每个 REST 服务都需要身份验证,为此我使用 JWT。

首先。我的 Angular 应用程序使用经过身份验证的 REST 服务。如果这样做了。 REST 服务返回一个令牌。

第二。有了这个令牌,Angular 应用程序可以使用另一个受 Spring Security 保护的 REST 服务。

我的错误发生在第二步。我不能消费其他服务。我有一个从 OncePerRequestFilter 扩展的自定义过滤器,甚至没有被调用。在 Angular 应用程序中,我收到一条我之前报告过的消息:

预检响应无效(重定向)

正如我在之前的编辑中所说的那样。我不理解,因为第一个 REST 服务被调用但第二个没有。我也不明白为什么我的自定义过滤器没有被调用。我认为我从 Angular 应用程序中调用错误。

我的代码:

自定义过滤器:

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter 

    private final Log logger = LogFactory.getLog(this.getClass());

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Value("$jwt.header")
    private String tokenHeader;

    static final String ORIGIN = "Origin";

    @Override
    //@CrossOrigin(origins = "*")
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException 
        // TODO Auto-generated method stub
        logger.info("checking authentication für user ");

        String authToken = request.getHeader(this.tokenHeader);

        // authToken.startsWith("Bearer ")
        // String authToken = header.substring(7);
        String username = jwtTokenUtil.getUsernameFromToken(authToken);

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) 

            // It is not compelling necessary to load the use details from the database. You could also store the information
            // in the token and read it from it. It's up to you ;)
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

            // For simple validation it is completely sufficient to just check the token integrity. You don't have to call
            // the database compellingly. Again it's up to you ;)
            if (jwtTokenUtil.validateToken(authToken, userDetails)) 
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                logger.info("authenticated user " + username + ", setting security context");
                SecurityContextHolder.getContext().setAuthentication(authentication);
            
        

        filterChain.doFilter(request, response);
    

Angular 控制器和服务:

import  Component, OnInit  from '@angular/core';
import  LoginService  from './login.service';

interface TokenJson 
    token: string;



@Component(
    selector: 'login',
    templateUrl: 'login.component.html',
    styleUrls: ['login.scss'],
    providers: [LoginService]
)
export class LoginComponent implements OnInit 
    private model = 'username':'****', 'password':'****';
    private currentToken:string ;
    private tokenJson: TokenJson;

    // constructor
    constructor(private _loginService: LoginService) 

    

    // on-init
    ngOnInit() 
      debugger;
      this._loginService.sendCredential(this.model).subscribe(
         data => 
                   debugger;
                   //localStorage.setItem("token", JSON.parse(JSON.stringify(data)).token);

                  // this.currentToken = JSON.parse(JSON.stringify(data))._body;
                   this.tokenJson = JSON.parse(JSON.stringify(data))._body;

                   this.currentToken = JSON.parse(JSON.parse(JSON.stringify(data))._body).token;
                   localStorage.setItem("token",  this.currentToken);
                   this._loginService.sendToken(localStorage.getItem("token")).subscribe(
                     data => 
                               //this.currentUserName=this.model.username;
                               //localStorage.setItem("currentUserName", this.model.username);
                               debugger;
                               this.model.username='';
                               this.model.password='';
                             ,
                     error => 
                       debugger;
                       console.log(error)
                     
                   );
                 ,
         error => 
           debugger;
           console.log(error)
         
       );
    




import Injectable from "@angular/core";
import Http, Headers, Response, RequestOptions from '@angular/http';
import Observable     from 'rxjs/Observable';


@Injectable()
export class LoginService 
  token: string;

  constructor (private http: Http) 

  sendCredential(model) 
    debugger;

    let tokenUrl1 = "http://localhost:8080/auth";
    let headers = new Headers();
    headers.append('Content-Type', 'application/json');
    return this.http.post(tokenUrl1, JSON.stringify(model), headers: headers);
  

  sendToken(token) 
    debugger;
    let tokenUrl2 = "http://localhost:8080/users";
    console.log('Bearer '+token);


    let getHeaders2 = new Headers();
    getHeaders2.append('Authorization', token);

    let headers = new Headers();

    headers.append('authorization', token);



    return this.http.post(tokenUrl2, , options);
  




解决方案

我发现了问题。针对 OPTIONS 类型的请求收到的错误消息。我只是避免了这种类型的请求。我在安全配置类中添加了以下配置:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    //
    @Override
    public void configure(WebSecurity web) throws Exception 
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    
    //


PD:我从here指导

【问题讨论】:

【参考方案1】:
 Response for preflight is invalid (redirect)

这听起来更像是您的服务器没有正确处理飞行前请求。

您在使用 CORS 吗? (即您是否使用ng serve 运行您的 Angular 应用程序,但尝试访问不同端口/机器上的服务器?)。

因为,根据您遇到的错误,我认为您的后端服务器未设置为正确处理 CORS。

【讨论】:

我使用命令“npm start”运行我的应用程序。你认为这是问题所在? 您很可能遇到了 CORS 问题。基本上,您从一台服务器为您的浏览器应用程序提供服务,但试图从该应用程序向另一台服务器发出 http 请求。出于安全原因,浏览器不允许这样做,除非其他服务器通过在其响应中设置适当的标头来明确允许它。谷歌“CORS”阅读一下。我假设您可以控制该其他服务器来配置 CORS。如果您没有该控制权,则必须调查在“npm start”服务器上设置代理 我忘记告诉我我的 Angular 应用程序使用了 Spring Boot 应用程序。 URL 是我的 spring boot 应用程序的 get 方法。我更改为 POST 但它仍然无法正常工作。我在第一篇文章中详细解释了 mot 这个答案引导我找到了最终的解决方案。【参考方案2】:
    let requestOptions = new RequestOptions(
        headers: new Headers(
            'Content-Type': 'application/json'
        ),
        method: RequestMethod.Post,
        body: 
    );

    this.http.request("http://localhost:8080/users", requestOptions)
        .map((response: Response) => response.json())

【讨论】:

为什么参数“method”是:RequestMethod.Post?为什么是 POST?它不应该是 GET 吗? 改成RequestMethod.Get 我收到此错误消息:找不到名称“RequestMethod” 需要从@angular/httpangular.io/docs/ts/latest/api/http/index/…导入 我已经实施了您的更改,但我仍然收到相同的消息:预检响应无效(重定向)。我认为该解决方案是另一种方式。可能是@snorkpete 的方式

以上是关于Spring Boot + Angular 2 + JWT的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2 + CORS + 带有 Spring Security 的 Spring Boot Rest Controller

如何使用spring boot和angular 2登录

maven - Spring Boot/Angular 2/4 项目战构建

Spring Boot + Angular 2 Heroku 部署

Spring Boot 和 Angular 2 的 URL 处理问题

Angular 基本身份验证 Spring Boot