You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security认证后登录页持续重载问题排查

问题描述

Spring Boot Web项目集成Spring Security后,访问允许页面、受限页面均正常,但提交登录后登录页持续重载。怀疑认证过程存在问题,查看日志未获取有效线索,failureUrl("/login?error=true")也未触发报错。代码哪里出现问题了?


pom依赖

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</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-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>  
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity6</artifactId>
    </dependency>

Security配置文件

@Configuration
@EnableWebSecurity
public class WebSecurity {
    @Autowired
    UserDetailsService userDetailsService;
 
    @Autowired 
    BCryptPasswordEncoder passwordEncoder;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeHttpRequests((authorize) ->
                        authorize.requestMatchers("/register/**").permitAll()
                                .requestMatchers("/index").permitAll()
                                .requestMatchers("/brand","/shop").hasRole("ADMIN")
                ).formLogin(
                        form -> form
                                .loginPage("/login")
                               // .loginProcessingUrl("/login")
                                .defaultSuccessUrl("/index")
                                .failureUrl("/login?error=true")
                                .permitAll()
                ).logout(
                        logout -> logout
                                .logoutRequestMatcher(new AntPathRequestMatcher("/checkout"))
                                .permitAll()
                );
        return http.build();
    }
 
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().requestMatchers("/image/**", "/js/**","/css/**");
    }
}

Users实体类

public class Users {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int user_id;

private String user_name ="anonymous";
private String password;
private Long phone = 12345L;
private String gender = "male";
private String email;
public String main_image = "1.jpeg";
private Date created_date;

private String main_address;
private boolean enabled;

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(
        name = "users_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
        )
private List<Role> roles = new ArrayList<>();
}

Role实体类

@Entity
@Table(name = "role")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int role_id;
    private String name;

    @ManyToMany(mappedBy="roles")
    private List<Users> users;
      
}

UserDetailServiceImpl实现类

public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private UsersRepository UsersRepository;

@Override
public UserDetails loadUserByUsername(String username) 
        throws UsernameNotFoundException {
    Users user = UsersRepository.getUserByUsername(username);
    
    if (user == null) {
        throw new UsernameNotFoundException("Could not find user");
    }
    else{
        return new org.springframework.security.core.userdetails.User(user.getUser_name(),
                user.getPassword(),
                mapRolesToAuthorities(user.getRoles()));
    }       
}

private Collection <? extends GrantedAuthority> mapRolesToAuthorities(Collection <Role> roles) {
    Collection <? extends GrantedAuthority> mapRoles = roles.stream()
            .map(role -> new SimpleGrantedAuthority(role.getName()))
            .collect(Collectors.toList());
    return mapRoles;
}
}

日志文件内容

Securing POST /shop2023-03-31T19:38:50.289+07:00 DEBUG 12560 --- [nio-8080-exec-5]o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymousSecurityContext
2023-03-31T19:38:50.294+07:00 DEBUG 12560 --- [nio-8080-exec-5] o.s.s.w.s.HttpSessionRequestCache        : Saved request http://localhost:8080/shop?continue to session2023-03-31T19:38:50.298+07:00 DEBUG 12560 --- [nio-8080-exec-5] o.s.s.web.DefaultRedirectStrategy : Redirecting tohttp://localhost:8080/login 
2023-03-31T19:38:50.459+07:00 DEBUG 12560 --- [nio-8080-exec-6] o.s.security.web.FilterChainProxy        : Securing GET /login
2023-03-31T19:38:50.608+07:00 DEBUG 12560 --- [nio-8080-exec-6] o.s.security.web.FilterChainProxy        : Secured GET /login
2023-03-31T19:38:51.094+07:00 DEBUG 12560 --- [nio-8080-exec-6] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext

问题排查与解决方案

1. 登录请求处理路径缺失

注释掉loginProcessingUrl("/login")后,自定义登录页的表单提交请求无法触发Spring Security的认证逻辑,会被当作普通页面请求,导致认证失败后重定向回登录页。恢复该配置:

.formLogin(
        form -> form
                .loginPage("/login")
                .loginProcessingUrl("/login") // 恢复此行
                .defaultSuccessUrl("/index")
                .failureUrl("/login?error=true")
                .permitAll()
)

2. 角色权限前缀不匹配

hasRole("ADMIN")会自动为角色名添加ROLE_前缀,若数据库中角色名存储为ADMIN而非ROLE_ADMIN,会导致权限验证失败。可二选一修改:

  • 数据库中角色name字段改为ROLE_ADMIN;
  • 在mapRolesToAuthorities方法中添加前缀:
private Collection <? extends GrantedAuthority> mapRolesToAuthorities(Collection <Role> roles) {
    return roles.stream()
            .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
            .collect(Collectors.toList());
}

3. 用户启用状态未传入

Users实体的enabled字段未在构建UserDetails时传入,默认会被视为禁用状态。修改loadUserByUsername方法:

return new org.springframework.security.core.userdetails.User(
        user.getUser_name(),
        user.getPassword(),
        user.isEnabled(), // 添加启用状态
        true, // 账户未过期
        true, // 凭证未过期
        true, // 账户未锁定
        mapRolesToAuthorities(user.getRoles())
);

4. Users实体缺少JPA注解

Users类未添加@Entity和@Table注解,JPA无法识别为实体类,导致查询不到用户数据。补充注解:

@Entity
@Table(name = "users")
public class Users {
    // 原有代码
}

5. UserDetailsServiceImpl未被Spring管理

该实现类缺少@Component注解,Spring无法注入UsersRepository,认证时无法查询用户。添加注解:

@Component
public class UserDetailsServiceImpl implements UserDetailsService {
    // 原有代码
}

6. BCryptPasswordEncoder Bean缺失

直接@Autowired但未定义Bean,导致密码编码器无法注入。添加Bean定义:

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

内容的提问来源于stack exchange,提问作者Ni No

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 13:32:03