经过几天的尝试解决以下问题后,我对以下问题感到不知所措。
我构建这个项目的上下文是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;
}
}
我尝试过的解决方案包括以下内容:
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:
这是后端的身份验证问题,相同的散列密码无法匹配。
身份验证的问题就在这里,在 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 未经授权的错误。