Spring Boot3+Spring Security6中hasRole("ROLE_USER")失效求助
Spring Boot 3 + Spring Security 6 自定义LoginFilter认证后无法访问Dashboard问题排查
问题描述
我在Spring Boot中创建了LoginFilter,意图在过滤器中创建用户认证对象后重定向到dashboard页面,但遇到以下问题:
- 直接访问
/dashboard时,页面跳转到登录页,尽管已在LoginFilter中创建带ROLE_USER权限的UsernamePasswordAuthenticationToken并放入SecurityContext - 若将
/dashboard的权限规则改为permitAll(),能访问页面但Principal对象为null
使用版本:Spring Boot 3、Spring Security 6
相关代码文件
LoginFilter.java
import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.json.JSONObject; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.RedirectStrategy; import org.springframework.stereotype.Component; import org.springframework.web.filter.GenericFilterBean; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.Date; import java.util.List; @Component public class LoginFilter extends GenericFilterBean { private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,ServletException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER"); List<SimpleGrantedAuthority> updatedAuthorities = new ArrayList<SimpleGrantedAuthority>(); updatedAuthorities.add(authority); authentication = new UsernamePasswordAuthenticationToken("myemail.gamil.com", null, updatedAuthorities); SecurityContextHolder.getContext().setAuthentication(authentication); this.redirectStrategy.sendRedirect((HttpServletRequest) request, (HttpServletResponse) response,"/dashboard"); } }
WebSecurityConfigDashboard.java
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration public class WebSecurityConfigDashboard { @Autowired private LoginFilter loginFilter; @Bean public UserDetailsService userDetailsService1(PasswordEncoder encoder) { UserDetails admin = User.withUsername("user1") .password(encoder.encode("123")) .roles("ROLE_USER") .build(); UserDetails user = User.withUsername("user2") .password(encoder.encode("123")) .roles("ROLE_USER") .build(); return new InMemoryUserDetailsManager(admin, user); } @Bean public SecurityFilterChain securityFilterChain1(HttpSecurity http) throws Exception { http.csrf().disable().authorizeHttpRequests() .requestMatchers("/dashboard*").hasRole("ROLE_USER") .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/dashboard") .failureUrl("/loginError") .and().logout() .invalidateHttpSession(true) .logoutSuccessUrl("/logout") .logoutUrl("/j_spring_security_logout").and() .sessionManagement() .maximumSessions(12) .expiredUrl("/logout"); http.addFilterAfter(loginFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public WebSecurityCustomizer webSecurityCustomizer1() { return (web) -> web.ignoring().requestMatchers("/resources/images/**"); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
LoginController.java
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import java.security.Principal; @Controller public class LoginController { @RequestMapping(value = "/dashboard") public String dashboard(Model model,Principal principal,HttpServletRequest request, HttpServletResponse response) { System.out.println("/dashboard called:------------------------>"); System.out.println("PRINCIPAL:--------------->"+principal.getName()); return "dashboard"; } @RequestMapping(value = "/login", method = RequestMethod.GET) public String login(ModelMap model) { System.out.println("/login called:------------------------>"); return "login"; } @RequestMapping(value = "/logout", method = RequestMethod.GET) public String logout(ModelMap model) { System.out.println("/logout called:------------------------>"); return "logout"; } @RequestMapping(value = "/loginError", method = RequestMethod.GET) public String loginError(ModelMap model) { model.addAttribute("error", "LOGIN FAILED"); return "login"; } @RequestMapping(value = "/error", method = RequestMethod.GET) public String error(ModelMap model,HttpServletRequest request, HttpServletResponse response) { return "error"; } }
问题原因分析
- 过滤器执行顺序错误:你把LoginFilter添加在了
UsernamePasswordAuthenticationFilter之后,但Spring Security的权限校验过滤器(FilterSecurityInterceptor)在UsernamePasswordAuthenticationFilter之后执行。直接访问/dashboard时,权限校验先触发,此时LoginFilter还未执行,无认证信息,因此跳转到登录页。 - 认证对象未持久化到Session:SecurityContext默认绑定当前线程,重定向是新请求,线程不同,之前的认证信息丢失,因此
permitAll()时Principal为null。 - hasRole权限前缀不匹配:
hasRole()会自动添加ROLE_前缀,配置.hasRole("ROLE_USER")实际校验的是ROLE_ROLE_USER权限,和你创建的ROLE_USER不匹配。
解决方案
1. 调整过滤器执行顺序
将LoginFilter放在权限校验过滤器之前,修改配置为:
http.addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class);
2. 持久化SecurityContext到Session
在LoginFilter中设置认证对象后,将其存入HttpSession,确保重定向后能获取到:
// 在LoginFilter的doFilter方法中添加 HttpServletRequest httpRequest = (HttpServletRequest) request; HttpSession session = httpRequest.getSession(); session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
3. 修正hasRole权限配置
hasRole()自动加前缀,因此只需写角色名即可,或者用hasAuthority()(不自动加前缀):
// 方式1:用hasRole,自动补全ROLE_前缀 .requestMatchers("/dashboard*").hasRole("USER") // 方式2:用hasAuthority,和你创建的权限完全匹配 .requestMatchers("/dashboard*").hasAuthority("ROLE_USER")
4. 优化LoginFilter触发逻辑(可选)
当前过滤器会对所有请求执行认证和重定向,需添加触发条件,比如仅在自定义登录接口触发:
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; // 仅当访问自定义登录接口时执行认证 if ("/login/custom".equals(httpRequest.getRequestURI())) { Authentication authentication = new UsernamePasswordAuthenticationToken( "myemail.gamil.com", null, List.of(new SimpleGrantedAuthority("ROLE_USER")) ); SecurityContextHolder.getContext().setAuthentication(authentication); // 存入Session HttpSession session = httpRequest.getSession(); session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); this.redirectStrategy.sendRedirect(httpRequest, (HttpServletResponse) response,"/dashboard"); } else { // 其他请求继续走过滤器链 filterChain.doFilter(request, response); } }
内容的提问来源于stack exchange,提问作者Kunal Bhangale
相关产品推荐
相关产品推荐

