英文:
Spring security default login not working when disabling CORS error
问题
以下是翻译好的内容:
我有一个基于Spring Boot和Angular 9构建的应用程序。我遇到了“请求的资源上没有'Access-Control-Allow-Origin'标头”的错误。所以,我修复了这个错误,但是现在当我停止Spring Boot应用程序,然后再次运行以测试任何API或其他页面时,我就没有默认的登录界面来输入由Spring Security提供的用户名和密码。因此,这使得我的Spring Security无法工作。我认为在我禁用CORS策略时出现了一些问题。
我的Spring Security配置如下:
WebConfig.java
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
}
WebSecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService customUserDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
protected CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/books").permitAll()
.antMatchers("/api/v1/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.and().csrf().disable();
}
}
BasicAuthController.java
@RequestMapping("/api/v1")
@RestController
@CrossOrigin(origins = "http://localhost:4200")
public class BasicAuthController {
@GetMapping(path = "/basicauth")
public AuthenticationBean basicauth() {
System.out.println("hitted here");
return new AuthenticationBean("You are authenticated");
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.15.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>in.ashwin</groupId>
<artifactId>onlinebookstore</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>onlinebookstore</name>
<description>Angular 8 and spring boot fullstack</description>
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
英文:
I have an application building on spring boot and angualar9. I got the No 'Access-Control-Allow-Origin' header is present on the requested resource
error.So,I fixed this error but,now when I stop the spring boot application and again run to test any api or any other pages then,I don't get default login screen to enter username and password provided by spring security.So,this made my spring security not working.I think there is some problem when i disabled CORS policy.
My spring secutiry config is:
Webconfig.java
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
}
WebSecutiryConfig.java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService customUserDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
protected CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/books").permitAll()
.antMatchers("/api/v1/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.and().csrf().disable();
}
}
BasicAuthController.java
@RequestMapping("/api/v1")
@RestController
@CrossOrigin(origins ="http://localhost:4200")
public class BasicAuthController {
@GetMapping(path = "/basicauth")
public AuthenticationBean basicauth() {
System.out.println("hitted here");
return new AuthenticationBean("You are authenticated");
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.15.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>in.ashwin</groupId>
<artifactId>onlinebookstore</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>onlinebookstore</name>
<description>Angular 8 and spring boot fullstack</description>
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
答案1
得分: 1
你应该在配置方法中为登录页面添加已登录数据和使用 .autenticated()
的路径规范。permitAll()
告诉 Spring 无论用户是否已登录都不重要。
如果需要的话,你还可以使用 .loginPage("custom_url")
添加自定义登录路径。
编辑
如果你使用 Angular,我建议使用如下的过滤器,我将使用 JWT 身份验证作为示例,但你也可以使用其他方式。
JWT 过滤器:负责验证已登录用户的身份验证。
@Component
public class JwtFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserService userService;
@Autowired
public JwtFilter( JwtUtil jwtUtil, UserService userService) {
this.jwtUtil = jwtUtil;
this.userService = userService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
/// 用户数据应该是某些唯一标识符。可以加密
String userData = null;
String jwtToken = null;
// JWT Token 的形式为 "Bearer token"。移除 Bearer 单词,获取
// 仅 Token
if (requestTokenHeader != null) {
if (requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
userData= jwtTokenUtil.getDataFromToken(jwtToken);
} catch (Exception e) {
logger.error("JwtRequestFilter.doFilterInternal 无法获取 JWT Token", e);
}
}
// 获取到令牌后进行验证。
if (userData!= null) {
Optional<User> optionalUserObject = userService.findByData(userData);
if (optionalUser.isEmpty()){
throw new UsernameNotFoundException("未找到用户。");
}
User actualUser = optionalUserObject.get();
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
actualUser,null,actualUser.getRoles());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 在上下文中设置身份验证后,我们指定
// 当前用户已经通过验证。这样它就可以成功地通过
// Spring Security 配置。
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
然后,你应该在 configure
方法中定义需要身份验证的 URL。
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 对于这个示例,我们不需要 CSRF
httpSecurity.csrf().disable()
// 不对此请求进行身份验证
.authorizeRequests().antMatchers("/login", "/register").permitAll().
// 所有其他请求都需要经过身份验证
anyRequest().authenticated().and() exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and();
// 添加过滤器以在每个请求中验证令牌
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.cors();
}
像你已经完成的那样添加跨源资源共享 (CORS) 配置。
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint,
Serializable {
private static final long serialVersionUID = -7078141869840704968L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未经授权");
}
}
希望我没有漏掉任何内容,这对你有帮助。
英文:
You should add to your configure method specification of login page for the logged in data and paths with .autenticated()
.
permitAll()
is telling spring you dont care if the user is logged in or not.
You can also add a custom login path if you would like with .loginPage("custom_url")
Edit
if you are using angular i would suggest using a filter as flowing i will use JWT authentication but you can use every other way.
JWt fliter: responsible for validating logged in users authentication.
@Component
public class JwtFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserService userService;
@Autowired
public JwtFilter( JwtUtil jwtUtil, UserService userService) {
this.jwtUtil = jwtUtil;
this.userService = userService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
/// user data should be some unique identifier. may be encrypted
String userData = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null) {
if (requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
userData= jwtTokenUtil.getDataFromToken(jwtToken);
} catch (Exception e) {
logger.error("JwtRequestFilter.doFilterInternal Unable to get JWT Token", e);
}
}
// Once we get the token validate it.
if (userData!= null) {
Optional<User> optionalUserObject = userService.findByData(userData);
if (optionalUser.isEmpty()){
throw new UsernameNotFoundException("User not found.");
}
User actualUser = optionalUserObject.get();
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
actualUser,null,actualUser.getRoles());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the
// Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
then you should define the urls that requires authentication with the configure
method
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/login", "/register").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and() exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and();
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.cors();
}
add cors configuration as you already done.
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint,
Serializable {
private static final long serialVersionUID = -7078141869840704968L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
hope i didn't forgot any Thing and it will work for you.
答案2
得分: 0
将 @CrossOrigin(origins ="http://localhost:4200") 替换为 @CrossOrigin("*")
重新启动服务并测试
英文:
Replace @CrossOrigin(origins ="http://localhost:4200") to @CrossOrigin("*")
Restart service and test
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论