获取 JWT 后,带有 Spring Boot 后端的 Angular 在现有路由上返回 404

Posted

技术标签:

【中文标题】获取 JWT 后,带有 Spring Boot 后端的 Angular 在现有路由上返回 404【英文标题】:Angular with spring boot backend returning 404 on existing route after getting JWT 【发布时间】:2019-01-19 12:07:36 【问题描述】:

我正在尝试开发一个简单的 Web 应用程序,其中 Angular 作为前端,Spring Boot 作为后端。我的飞行前请求成功并且我收到了一个有效的令牌,但是当我尝试对路由发出后续请求时,我得到一个 404。当我尝试卷曲 url 时,我得到一个 500,并显示消息“缺少或无效的授权标头”在我看来,这似乎是说该路线确实存在并且正在监听,但还有其他问题。

首先是打字稿。这是我的login.component.ts

import  Component  from "@angular/core";
import  Observable  from "rxjs/index";
import  LoginService  from "../services/login.service";

@Component(
    selector: "login",
    templateUrl: "./login.component.html"
)

export class Login 
    private model = "username": "", "password": "";
    private currentUserName;

    constructor (private loginService: LoginService) 
        this.currentUserName = localStorage.getItem("currentUserName");
    

    public onSubmit() 
        console.log("submitting");
        this.loginService.sendCredential(this.model).subscribe(
            data => 
                console.log(data);
                localStorage.setItem("token", data);
                console.log("Setting token");
                this.loginService.sendToken(localStorage.getItem("token")).subscribe(
                    data => 
                        this.currentUserName = this.model.username;
                        localStorage.setItem("currentUserName", this.model.username);
                        this.model.username = "";
                        this.model.password = "";
                    ,
                    error => console.log(error)

                );
            ,
            error => 
                console.log("oh no");
                console.log(error)
            
        );
    

然后是我的LoginService

import  Injectable  from "@angular/core";
import  HttpClient  from '@angular/common/http';
import  HttpHeaders  from '@angular/common/http';
import  Observable  from "rxjs/index";

@Injectable()
export class LoginService 
    token: string;
    constructor (private http: HttpClient) 

    public sendCredential(model): Observable<String> 
        const tokenUrlPreFlight = "http://localhost:8080/users/login/";
        const httpOptions:  = 
            headers: new HttpHeaders(
                'ContentType': 'application/json'
            )
        ;
        return this.http.post<String>(tokenUrlPreFlight, model, httpOptions);
    

    public sendToken(token) 
        const tokenUrlPostFlight = "http://localhost:8080/rest/users/";
        console.log("Bearer " + token);

        const httpOptions:  = 
            headers: new HttpHeaders(
                'Authorization': 'Bearer ' + token
            )
        ;

        return this.http.get(tokenUrlPostFlight, httpOptions);
    

    public logout(): void 
        localStorage.setItem("token", "");
        localStorage.setItem("currentUserName", "");
        alert("You just logged out");
    

    public checkLogin(): boolean 
        if(localStorage.getItem("currentUserName") != null && localStorage.getItem("currentUserName") != "" && localStorage.getItem("token") != null && localStorage.getItem("token") != "") 
            console.log(localStorage.getItem("currentUserName"));
            console.log(localStorage.getItem("token"));
            return true;
        

        return false;
    

现在是java。首先是我的切入点:

import com.acb.app.configuration.JwtFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class App 

    @Bean
    public FilterRegistrationBean jwtFilter() 
        final FilterRegistrationBean<JwtFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new JwtFilter());
        filterRegistrationBean.addUrlPatterns("/rest/*");
        return filterRegistrationBean;
    

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

我的UserController.java

import com.acb.app.model.User;
import io.jsonwebtoken.*;
import com.acb.maki.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.ServletException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@RestController
@RequestMapping("/users")
public class UserController 

    @Autowired
    private UserService userService;

    @GetMapping("")
    public List<User> userIndex() 
        return userService.getAllUsers();
    

    @GetMapping("username")
    public Optional<User> getUserByUsername(@RequestBody String username) 
        return userService.findByUsername(username);
    

    @PostMapping("login")
    public String login(@RequestBody Map<String, String> json) throws ServletException 
        if(json.get("username") == null || json.get("password") == null) 
            throw new ServletException("Please fill in username and password");
        

        final String username = json.get("username");
        final String password = json.get("password");

        Optional<User> optionalUser = userService.findByUsername(username);
        if(!optionalUser.isPresent()) 
            throw new ServletException("Username not found");
        

        User user = optionalUser.get();

        if(!password.equals(user.getPassword())) 
            throw new ServletException("Invalid login. Please check username and password");
        
        final String response = Jwts.builder().setSubject(username).claim("roles", "user").setIssuedAt(new Date()).signWith(SignatureAlgorithm.HS256, "secretKey").compact();
        final String jsonResponse = String.format("\"%s\"", response);

        System.out.println(jsonResponse);
        return jsonResponse;
    

    @PostMapping(value="/register")
    public User register(@RequestBody User user) 
        return userService.save(user);
    

最后但并非最不重要的是我的JwtFilter.java

import io.jsonwebtoken.*;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;



public class JwtFilter extends GenericFilterBean 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException 
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final String authHeader = request.getHeader("Authorization");
        if ("OPTIONS".equals(request.getMethod())) 
            response.setStatus(HttpServletResponse.SC_OK);
            filterChain.doFilter(servletRequest, servletResponse);
         else 
            if (authHeader == null || !authHeader.startsWith("Bearer ")) 
                throw new ServletException("Missing or invalid authorization header");
            

            final String token = authHeader.substring(7);

            try 
                final JwtParser jwtParser = Jwts.parser();
                final Jws<Claims> claimsJws = jwtParser.setSigningKey("secretKey").parseClaimsJws(token);
                final Claims claims = claimsJws.getBody();
                request.setAttribute("claims", claims);
             catch (final SignatureException e) 
                throw new ServletException("Invalid token.");
            
        

        filterChain.doFilter(servletRequest, servletResponse);
    

我非常清楚比较纯文本密码等方面的不良做法。我只是想让它暂时起作用。令牌已正确生成并返回。令牌也正确设置为本地存储,但在向/rest/users 路由发出获取请求时,会返回 404。 java端没有错误。

【问题讨论】:

您正在向 /rest/users 发送请求。您的服务映射到 /users。就这么简单。 @JBNizet 是的,我可以看到,但是由于潜在的跨站点脚本,将我的服务更改为映射到 /rest/users/ 会导致请求被阻止。因此,当我的过滤器对 /rest/* 模式有效时,我不确定如何保护我的其他路线 请注意,如果您只是将 Angular-CLI 的 ng serve 配置为 Spring boot 后端的反向代理,则不必与 CORS 混淆。这也将允许您从源代码中删除所有硬编码的http://localhost:8080 绝对!那是我的清单上的东西,但是我目前正在尝试遵循一个稍微过时的教程,只是想对弹簧和角度有一点感觉。在不久的将来,整个事情将不得不进行一些升级。感谢您的帮助 【参考方案1】:

正如我所料,我确实是个白痴。正如上面提到的用户,我的服务映射到/users/,而我需要的是受保护的版本/rest/users。因此,在我的 UserController 旁边,我创建了 UserResource.java,如下所示:

import com.acb.app.model.User;
import com.acb.app.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/rest")
public class UserResource 

    @Autowired
    private UserService userService;

    @RequestMapping("/users")
    public List<User> findAllUsers() 
        return userService.getAllUsers();
    

这利用了 JwtFilter 并允许我保护我的路线。希望这对其他人有帮助。

【讨论】:

以上是关于获取 JWT 后,带有 Spring Boot 后端的 Angular 在现有路由上返回 404的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JWT 在 Spring Boot 中获取 Refresh Token

Spring Boot JWT Authentication:登录和注销后触发一个方法

如何在 Spring Boot OAuth2 的 SecurityContextHolder 中获取 JWT 令牌?

独立资源服务器(Spring Boot 2 + OAuth + JWT)在 Spring-boot 从 1.2.x 升级到 2.x 后给出 UsernameNotFoundException

在 Spring Boot 中成功身份验证后未生成 JWT 令牌

带有 spring-boot 和 spring-security 的 JWT