为什么 CORS 配置为允许任何访问,登录时仍然出现 401 Unauthorized 错误?

问题描述 投票:0回答:1

经过几天的尝试解决以下问题后,我对以下问题感到不知所措。

我构建这个项目的上下文是React/Axios/Spring Boot 3/Spring Security 6。

当我尝试登录项目的受保护视图时,出现 401 CORS 未经授权的错误。这些是相关部分:

Login.js 文件:

const validateUserExists = async () => { 
       
       
       
       const loginData = {
           username: state.username,
           password: state.password
       }
       
       console.log("Login Data is, ", loginData)
       
       // check username for existence
       
        try {
            console.log("Sending data...")
            const response = await userServices.getStaffByEmailAddress(loginData);
            const res = response.data
            console.log("Res data is, ", response.data)
            
            if (response.data == "Login successful!") {

                        console.log("Response is, ", resSID)
        
                // here the process will determine the role of the user 
                const respSID = await userServices.getSIDByStaffEmail(state.username)
                const resSID = respSID.data
                 
               
                    console.log("Staff id for getting email is: ", resSID)
               
                    // get rid from staff id in the staff_roles table
                    const respRID = await userServices.getRIDfromSID(resSID)
                    const resRID = respRID.data
               
                    console.log("Role id based on staff id is: ", resRID)
               
                    // get role name from the rid
                    const respRole = await userServices.getRoleByRID(resRID)
                    const role = respRole.data
               
                    console.log("Role name based on RID is: ", role) 

                                // ...ommitted code for further filtering based on role and session management

                                navigate("/ShowUsers")
                     }

                     } catch (error) {
            
            console.log("Error is, " , error)
            console.log("Error status: ", error.res.status)
            console.log("Error data: ", error.res.data)
        
        }
   }

return (
      <div className="form">
      <form onSubmit={handleSubmit} >
        
        <div className="form-row-login">
        <div className="title">Sign-In Below:</div>
        <label htmlFor="username">Username: </label>
        <input 
            type="text" 
            id="username" 
            name="username"
            placeholder="Enter your company email" 
            value={state.username} 
            onChange={(e) => setState({username: e.target.value})}
        />
          
        
        <label htmlFor="password">Password: </label>
        <input 
            type="password" 
            id="password" 
            name="password"
            placeholder="Enter your password" 
            value={state.password}
            onChange={(e) => setState({password: e.target.value})}
        />
        
        <button className="button-container" type="submit" value="Login">Log In</button>
        <button 
            className="button-container" 
            type="submit" 
            value="ForgotPassword"
            onClick={(e) => handleForgotPassword(e)}>Forgot Password?</button>
        
      </div>
      
      { isLoginPending && <div class="form-row-login">Please wait...</div> }
      { isLoggedIn && <div class="form-row-login">Success.</div> }
      { loginError && <div class="form-row-login">{loginError.message}</div> }
       
      </form>
      
      
    </div>
   );
                      

用于保存用户名和密码的LoginData模型:

package com.example.demo.model;

public class LoginData {
    
    private String username;
    private String password;

    
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    
    public LoginData(String username, String password) {
        super();
        this.username = username;
        this.password = password;
    }
    @Override
    public String toString() {
        return "LoginData [username=" + username + ", password=" + password + "]";
    }
    public LoginData() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    

}

Axios 通过 service.js:

         getStaffByEmailAddress(loginData) {
        
        try {
            
            console.log("LoginData at axios is, ", loginData)
            
            return axios.post('http://myappforms:8080/myapp/login/get-staff-by-email',                       loginData, {
                  headers: {
                    'Access-Control-Allow-Origin': '*',
                    'Content-Type': 'application/json; charset=UTF-8',
                    'Access-Control-Allow-Headers':'*',
                    "Access-Control-Allow-Methods": "GET,PUT,POST,DELETE,PATCH,OPTIONS"
                }})
            
        } catch (error) {
            
            console.log("Error is: ", error)
            
        }
            
    }

StaffController.java 文件:


@RestController
@CrossOrigin(origins = "*")
@RequestMapping("/login")
public class StaffController {

// ... other endpoints

       @PostMapping("/get-staff-by-email")
    public ResponseEntity<String> getStaffByEmailAddress(@RequestBody LoginData loginData,                   HttpServletRequest request) {
        System.out.println(loginData.getPassword());
        System.out.println(loginData.getUsername());

        if (staffService.authenticateUser(loginData.getUsername(), loginData.getPassword())) {
            
            return ResponseEntity.ok("Login successful!");
            

        } else {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Login failed");
        }
    }

}

StaffService.java:

请注意,我在这里使用 BCrypt 对提交的密码进行编码,并将其与已存储在数据库中的散列版本进行匹配。

public Boolean authenticateUser(String username, String password) {

               @Autowired
           StaffRepository staffRepo;
    
           @Autowired
           private PasswordEncoder passwordEncoder;
        
        Staff staff = staffRepo.findByUsername(username);
        
        System.out.println(username);
        System.out.println(password);
        System.out.println("Staff is: " + staff);
        System.out.println(staff.getUsername());
        System.out.println(staff.getStaffpass());
        
        String encryptedPass = passwordEncoder.encode(password);
        
        if (staff != null && passwordEncoder.matches(encryptedPass, staff.getStaffpass())) {
            return true;
        }
        
        return false;
    }

WebSecurity配置文件:

package com.example.demo;

import java.util.Arrays;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.CorsRegistry;

//@ComponentScan(basePackages = "come.example.demo")
@Configuration (proxyBeanMethods = false)
@EnableWebSecurity
@EnableMethodSecurity
public class WebSecurityConfig {
    
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    
        
        //XorCsrfTokenRequestAttributeHandler xor = new XorCsrfTokenRequestAttributeHandler();
        //String hasIpAddress = "127.0.0.1";
        
        
    return http
        .cors(cors -> cors.disable())
        .csrf(AbstractHttpConfigurer::disable)
        .sessionManagement((session) ->
            session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        )
        .authorizeHttpRequests(authorize -> {
            //try {
                authorize
                        .requestMatchers("/myapp/").permitAll()
                        .requestMatchers("/public/**").permitAll()
                        //.requestMatchers("/login/**").permitAll()
                        //.requestMatchers("/login/get-staff-by-email").permitAll()
                        .anyRequest().authenticated();
                       // .and()
                       // .httpBasic();
            //} catch (Exception e) {
            //  // TODO Auto-generated catch block
            //  e.printStackTrace();
            //}
        }
                
                
        )
        .httpBasic(Customizer.withDefaults())
        .build();   
        
        
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    /*
     @Bean
       
    public void addCorsMappings(CorsRegistry registry) {
        // Allow all origin to call your api
        registry.addMapping("/**");
    }
            */
    
    
      @Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList("/**"));
    configuration.setAllowedMethods(Arrays.asList("GET","POST"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}


}

我尝试过的解决方案包括以下内容:

  1. WebSecurityConfig 文件内的专用 CORS 配置。
  2. 专门配置的 .requestMatchers 路径允许向其发出所有请求。
  3. 从 CORS 配置中完全删除登录路径
  4. @CrossOrigin 注释在控制器文件级别具有不受限制的起源。
  5. 尝试在相关方法上应用@CrossOrigin没有效果。
  6. LoginData模型精确到提交的对象
  7. 使用 POST 而不是 GET 请求作为良好安全实践的一点
  8. JSON.Stringify 用于 React 中的 loginData 对象,我用它来有效地提交新客户端。
  9. 配置文件中注释掉了一个用于添加 CORS 映射的 bean,但此时项目无法编译。
  10. 讽刺的是,CORS 和 CSRF 在配置中被禁用,而浏览器仍然阻止它。
  11. 访问标头在对端点的 Axios 请求中设置。
  12. 我曾想过密码加密是原因,但它必须就在它所在的地方; BCrypt 可以,但出于安全原因不应该在前端实现。

CORS 配置取自文档网站:参见此处

这是浏览器中的错误反馈:

响应标头:

OPTIONS //login/get-staff-by-email HTTP/1.1
Host: myappforms:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: access-control-allow-origin,content-type
Referer: http://localhost:8080/
Origin: http://localhost:8080
Connection: keep-alive

请求标头:

HTTP/1.1 401 
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
WWW-Authenticate: Basic realm="Realm"
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 721
Date: Tue, 09 Jan 2024 16:29:37 GMT
Keep-Alive: timeout=20
Connection: keep-alive

服务器反馈(Tomcat 10.1.17):

在这里,不安全的端点(例如公众中任何人都可以选择的位置/程序)可以正常工作,假设在配置文件中的“/public”requestMatchers规范下。

 No active profile set, falling back to 1 default profile: "default"
2024-01-09T11:05:43.849-05:00  INFO 3500 --- [o-8080-exec-582] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 693 ms
2024-01-09T11:05:43.871-05:00  INFO 3500 --- [o-8080-exec-582] o.s.boot.web.servlet.RegistrationBean    : Filter errorPageFilter was not registered (possibly already registered?)
2024-01-09T11:05:43.871-05:00  INFO 3500 --- [o-8080-exec-582] o.s.boot.web.servlet.RegistrationBean    : Filter springSessionRepositoryFilter was not registered (possibly already registered?)
2024-01-09T11:05:43.871-05:00  INFO 3500 --- [o-8080-exec-582] o.s.boot.web.servlet.RegistrationBean    : Filter springSecurityFilterChain was not registered (possibly already registered?)
2024-01-09T11:05:43.952-05:00  INFO 3500 --- [o-8080-exec-582] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@5f3707ba, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@78954241, org.springframework.security.web.context.SecurityContextHolderFilter@33e0b89a, org.springframework.security.web.header.HeaderWriterFilter@60141a43, org.springframework.security.web.authentication.logout.LogoutFilter@dd00125, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@52e3edea, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5f71c5fc, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@1f770a53, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@6de51fe8, org.springframework.security.web.session.SessionManagementFilter@1a07d2c0, org.springframework.security.web.access.ExceptionTranslationFilter@27c9669c, org.springframework.security.web.access.intercept.AuthorizationFilter@7b5dd70c]
2024-01-09T11:05:44.043-05:00  INFO 3500 --- [o-8080-exec-582] com.example.demo.ServletInitializer      : Started ServletInitializer in 0.942 seconds (process running for 526142.098)
2024-01-09T11:05:56.111-05:00  INFO 3500 --- [o-8080-exec-585] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2024-01-09T11:05:56.191-05:00  INFO 3500 --- [o-8080-exec-585] o.s.web.servlet.DispatcherServlet        : Completed initialization in 80 ms
2024-01-09T11:05:59.281-05:00 DEBUG 3500 --- [o-8080-exec-590] org.hibernate.SQL                        : /* <criteria> */ select l1_0.lid,l1_0.lname,l1_0.lsort from locations l1_0
Hibernate: /* <criteria> */ select l1_0.lid,l1_0.lname,l1_0.lsort from locations l1_0

更新

服务器使用加密密码完成前端的所有请求,但401错误仍然存在。

日志已被编辑为机密信息:

Hibernate: /* <criteria> */ select s1_0.sid,s1_0.resettoken,s1_0.servicecontact,s1_0.sfirstname,s1_0.slastname,s1_0.sphone,s1_0.spid,s1_0.staffpass,s1_0.tokenexpirationdate,s1_0.username from staff s1_0 where s1_0.username=?
[email protected]
password123
2024-01-09T14:01:07.051-05:00 DEBUG 3500 --- [o-8080-exec-620] org.hibernate.SQL                        : select r1_0.sid,r1_1.roleid,r1_1.role from staff_roles r1_0 join roles r1_1 on r1_1.roleid=r1_0.rid where r1_0.sid=?
Hibernate: select r1_0.sid,r1_1.roleid,r1_1.role from staff_roles r1_0 join roles r1_1 on r1_1.roleid=r1_0.rid where r1_0.sid=?
Staff is: Staff [SID=1, spid=1, sfirstname=MyFirstName, slastname=MyLastName, [email protected], sphone=null, staffpass=$2a$12$..HASHEDPASSWORD123HASH, servicecontact=null, resetToken=null, tokenExpirationDate=null, roles=[Roles [roleid=1, role=USER]]]
email@,myapp.com
$2a$12$..HASHEDPASSWORD123HASH

我已经浏览了相关文档,关于SO的一些问题,AI对于这个问题没有多大用处,而且我一般对Spring Security比较陌生,对版本6更是如此,所以它很可能只是下降由于我自己缺乏知识和经验,但我没有想法和方向。任何见解将不胜感激!

更新2:

这是后端的身份验证问题,相同的散列密码无法匹配。

spring-boot authentication axios cors http-status-code-401
1个回答
0
投票

身份验证的问题就在这里,在 StaffService 文件中:

这就是它应该说的-passwordEncoder 将原始提交的密码与存储在数据库中的散列版本进行比较:

if (passwordEncoder.matches(password, staff.getStaffpass())) {
    return true;
}
    

这就是服务器现在报告的内容:

Submitted password is password123
BCrypt password in database is: $2a$10$zJMb0woA4R.rJgvevFMViuBxEyDGNk01XpS25smM6.57cMYx9.KFK
Returned true!

以前:

String encryptedPass = passwordEncoder.encode(password);
    
    System.out.println("Submitted password is " + password);
    System.out.println("Encrypted passwords is " + encryptedPass);
    System.out.println("BCrypt password in database is: " + staff.getStaffpass());
    
    
    if (passwordEncoder.matches(encryptedPass, staff.getStaffpass())) {
        return true;
    }

BCrypt 将使用相同的提交密码创建一个新的哈希值,根据定义,该哈希值与数据库中存储的哈希值不匹配。

结果在服务器日志中可见:

Submitted password is password123
Encrypted passwords is $2a$10$AGB9.oEy6NUvNDkka3.FK.jPpOQ/Hwy09/1cwaESfx07o1giYzhzq
BCrypt password in database is: $2a$10$zJMb0woA4R.rJgvevFMViuBxEyDGNk01XpS25smM6.57cMYx9.KFK
Return is false!

提示 401 未经授权的错误。

© www.soinside.com 2019 - 2024. All rights reserved.