使用spring security angular和rest API重新加载页面给我错误401

Posted

技术标签:

【中文标题】使用spring security angular和rest API重新加载页面给我错误401【英文标题】:reloading page give me error 401 using spring security angular and rest API 【发布时间】:2020-06-22 04:32:12 【问题描述】:

我使用 Spring Boot、Spring Security 和 Angular 客户端进行了相对简单的设置。 所以我使用rest api在客户端显示数据库,就像数据库网站一样

所以现在登录正在工作,当我登录时,我可以一切都没有问题。但是当我重新加载页面时,我失去了对其余 api 的授权,我无法再收到信息,并出现错误 401。

这是在登录过程之后

General
Request URL: http://localhost:8080/mindnessBdd/api/v1/films
Request Method: GET
Status Code: 200 
Remote Address: [::1]:8080
Referrer Policy: no-referrer-when-downgrade

Request header
GET /mindnessBdd/api/v1/films HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: application/json, text/plain, */*
Sec-Fetch-Dest: empty
Authorization: Basic bWluZG5lc3M6QFJhcGhhNzY2MjAh
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Content-Type: application/json
Origin: http://localhost:4200
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Referer: http://localhost:4200/films
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7

Responce header
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:4200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/json
Date: Tue, 10 Mar 2020 09:32:31 GMT
Expires: 0
Pragma: no-cache
Transfer-Encoding: chunked
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

重新加载后

General
Request URL: http://localhost:8080/mindnessBdd/api/v1/films
Request Method: GET
Status Code: 401 
Remote Address: [::1]:8080
Referrer Policy: no-referrer-when-downgrade

Response Headers
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:4200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 0
Date: Tue, 10 Mar 2020 09:37:25 GMT
Expires: 0
Pragma: no-cache
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
WWW-Authenticate: Basic realm="Realm"
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

Request Header
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Authorization: Basic dW5kZWZpbmVkOnVuZGVmaW5lZA==
Connection: keep-alive
Content-Type: application/json
Host: localhost:8080
Origin: http://localhost:4200
Referer: http://localhost:4200/films
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36

不知道它是否有帮助,但这是我的身份验证配置

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.cors().and().csrf().disable()
                .authorizeRequests()
                .antMatchers("/", "/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .httpBasic()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().formLogin().disable();;

    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() 
        final CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(ImmutableList.of("http://localhost:4200"));
        configuration.setAllowedMethods(ImmutableList.of("*"));
        configuration.setAllowCredentials(true);
        configuration.setMaxAge((long) 3600);
        configuration.setAllowedHeaders(ImmutableList.of("*"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    


我认为我对 Spring Security 缺乏了解是错误的,谁能看到我做错了什么?

提前致谢!

** 编辑 登录组件:

Skip to content
Search or jump to…

Pull requests
Issues
Marketplace
Explore

@mindness 
mindness
/
WebAppFront
1
00
 Code Issues 0 Pull requests 0 Actions Projects 0 Wiki Security Insights Settings
WebAppFront/src/app/login/login.component.ts / 
 raphael lefrancois login / auth / logout
c3fe322 4 days ago
Executable File  39 lines (33 sloc)  1 KB

Code navigation is available!
Navigate your code with ease. Click on function and method calls to jump to their definitions or references in the same repository. Learn more

You're using code navigation to jump to definitions or references.
Learn more or give us feedback
import  Component, OnInit  from '@angular/core';
import  Router, ActivatedRoute  from '@angular/router';
import  AuthenticationService  from './auth.service';

@Component(
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
)
export class LoginComponent implements OnInit 

  username: string;
  password: string;
  errorMessage = 'Invalid Credentials';
  successMessage: string;
  invalidLogin = false;
  loginSuccess = false;

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private authenticationService: AuthenticationService)    

  ngOnInit() 
  

  handleLogin() 
    this.authenticationService.authenticationService(this.username, this.password).subscribe((result) => 
      this.invalidLogin = false;
      this.loginSuccess = true;
      this.successMessage = 'Login Successful.';
      this.router.navigate(['/films']);
    , () => 
      this.invalidLogin = true;
      this.loginSuccess = false;
    );
  




身份验证服务

import  HttpClient  from '@angular/common/http';
import  Injectable  from '@angular/core';
import  map  from 'rxjs/operators';

@Injectable(
  providedIn: 'root'
)
export class AuthenticationService 

  // BASE_PATH: 'http://localhost:8080'
  USER_NAME_SESSION_ATTRIBUTE_NAME = 'authenticatedUser'

  public username: string;
  public password: string;

  constructor(private http: HttpClient) 

  

  authenticationService(username: string, password: string) 
    return this.http.get(`http://localhost:8080/mindnessBdd/api/v1/basicauth`,
       headers:  authorization: this.createBasicAuthToken(username, password)  ).pipe(map((res) => 
      this.username = username;
      this.password = password;
      this.registerSuccessfulLogin(username, password);
    ));
  

  createBasicAuthToken(username: string, password: string) 
    return 'Basic ' + window.btoa(username + ':' + password);
  

  registerSuccessfulLogin(username, password) 
    sessionStorage.setItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME, username);
  

  logout() 
    sessionStorage.removeItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME);
    this.username = null;
    this.password = null;
  

  isUserLoggedIn() 
    const user = sessionStorage.getItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME)
    if (user === null)  return false; 
    return true;
  

  getLoggedInUserName() 
    const user = sessionStorage.getItem(this.USER_NAME_SESSION_ATTRIBUTE_NAME)
    if (user === null)  return ''; 
    return user;
  

http拦截器

import  HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders  from '@angular/common/http';
import  Injectable  from '@angular/core';
import  Observable  from 'rxjs';
import  AuthenticationService  from './auth.service';

@Injectable()
export class HttpInterceptorService implements HttpInterceptor 

  constructor(private authenticationService: AuthenticationService)  

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
    if (this.authenticationService.isUserLoggedIn() && req.url.indexOf('basicauth') === -1) 
      const authReq = req.clone(
        headers: new HttpHeaders(
          'Content-Type': 'application/json',
          Authorization: `Basic $window.btoa(this.authenticationService.username + ':' + this.authenticationService.password)`
        )
      );
      return next.handle(authReq);
     else 
      return next.handle(req);
    
  

【问题讨论】:

【参考方案1】:

这是一个非常有趣的问题。

如果您仔细检查两个请求标头(登录后的一个和重新加载后的一个),您会找到原因。

登录后的授权Base64编码为:

Authorization: Basic bWluZG5lc3M6QFJhcGhhNzY2MjAh

重载后的是:

Authorization: Basic dW5kZWZpbmVkOnVuZGVmaW5lZA==

现在,如果您将第二次授权解码为纯文本,则为:

undefined:undefined

这就是你得到 401 的原因,因为 undefined undefined 不是有效的用户和密码。

我猜你从你的 javascript 变量中获得了用户名和密码,当你重新加载你的页面时它是未定义的......


更新: 在阅读了您的前端代码之后,您似乎将用户名保存到了会话存储中,并且当您重新加载页面时,它假定用户已登录,因为它在会话存储中找到了用户名。因此它尝试再次发送令牌,但是未设置身份验证服务中的用户名和密码。您可以将用户名和令牌保存到会话存储 (cookie) 中。并且当重新加载时,它可以从存储中获取令牌,而不是。为了安全起见,请设置 HttpOnly 标志。

【讨论】:

感谢您的回答,但我不明白为什么在实施 Spring Security 之前我没有这个重载问题,并且我没有更改系统以对其余 api 进行身份验证您有什么建议吗解决这个问题? 在你将Spring security引入你的应用之前,你没有认证,甚至没有用户名和密码都可以访问api。现在你有了 Spring security,当你调用 api 时它会检查用户名和密码,但是当你重新加载它时它没有提供用户名和密码,你得到了 401 错误。您还可以在您的问题中分享您的登录ui代码吗?所以我们可以建议如何解决? 我添加了我用来记录的所有内容,如果你看到它为什么不起作用:/ 您好 Mindness,您将用户名保存到会话存储中,当您重新加载页面时,它会检查用户是否已登录,因此它会尝试再次发送令牌,但是用户名和密码在身份验证服务中未设置。您可以将用户名和令牌保存到会话存储中。而重载时,它可以从存储中获取令牌,而不是 嘿,鲍勃,非常感谢你的回答,现在它在安全方面工作它看起来有点糟糕,不是吗?我在会话存储中添加了用户名和密码,所以我可以取回令牌,不确定这样做是否真的安全

以上是关于使用spring security angular和rest API重新加载页面给我错误401的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 Angular 和 Spring Security 设置身份验证标头

Spring Security和Angular 5

如何将凭据从 Angular 前端传递到 Spring 后端(Spring Security 的基本身份验证)

Angular 2 Spring Security CSRF 令牌

Spring Security + Angular JS + Spring MVC Rest

Angular , Spring security 一个 MYSQL 基本登录