Spring Boot认证后出现Anonymous Role问题排查与解决求助
Spring Boot + Angular认证后仍识别为匿名角色问题排查
问题描述
Spring Boot + Angular项目中,管理员账号完成登录认证后,系统仍将用户识别为匿名角色,访问受保护接口时返回Full authentication is required to access this resource错误,尝试多种方案未解决。
相关代码与日志
WebSecurityConfig代码
public class WebSecurityConfig { @Autowired UserDetailsServiceImpl userDetailsService; @Autowired private AuthEntryPointJwt unauthorizedHandler; @Bean public AuthTokenFilter authenticationJwtTokenFilter() { return new AuthTokenFilter(); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests().antMatchers("/api2/auth/**").permitAll() .anyRequest().authenticated(); http.authenticationProvider(authenticationProvider()); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }
Auth Controller代码
@CrossOrigin(origins = "http://localhost:4200", maxAge = 3600) @RestController @RequestMapping("/api2/auth") public class AuthController { @Autowired AuthenticationManager authenticationManager; @Autowired UserRepository userRepository; @Autowired RoleRepository roleRepository; @Autowired PasswordEncoder encoder; @Autowired JwtUtils jwtUtils; @PostMapping("/signin") public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(userDetails); List<String> roles = userDetails.getAuthorities().stream() .map(item -> item.getAuthority()) .collect(Collectors.toList()); return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) .body(new UserInfoResponse(userDetails.getId(), userDetails.getUsername(), userDetails.getPassword(), roles)); } @PostMapping("/signup") public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) { if (userRepository.existsByUsername(signUpRequest.getUsername())) { return ResponseEntity.badRequest().body(new MessageResponse("Error: Username is already taken!")); } // Create new user's account User user = new User(signUpRequest.getUsername(), encoder.encode(signUpRequest.getPassword())); Set<String> strRoles = signUpRequest.getRole(); Set<Role> roles = new HashSet<>(); if (strRoles == null) { Role userRole = roleRepository.findByName(ERole.ROLE_USER) .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); roles.add(userRole); } else { strRoles.forEach(role -> { if ("admin".equals(role)) { Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN) .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); roles.add(adminRole); } else if ("user".equals(role)) { Role userRole = roleRepository.findByName(ERole.ROLE_USER) .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); roles.add(userRole); } }); } user.setRoles(roles); userRepository.save(user); return ResponseEntity.ok(new MessageResponse("User registered successfully!")); } @PostMapping("/signout") public ResponseEntity<?> logoutUser() { ResponseCookie cookie = jwtUtils.getCleanJwtCookie(); return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString()) .body(new MessageResponse("You've been signed out!")); } }
请求追踪日志
ERROR 2540 --- c.s.e.security.jwt.AuthEntryPointJwt : Unauthorized error: Full authentication is required to access this resource TRACE 2540 --- o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure] DEBUG 2540 --- s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request TRACE 2540 --- o.s.security.web.FilterChainProxy : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter...] TRACE 2540 --- o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/13) TRACE 2540 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking SecurityContextPersistenceFilter (3/13) DEBUG 2540 --- s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext TRACE 2540 --- o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (4/13) TRACE 2540 --- o.s.security.web.FilterChainProxy : Invoking CorsFilter (5/13) TRACE 2540 --- o.s.security.web.FilterChainProxy : Invoking LogoutFilter (6/13) TRACE 2540 --- o.s.s.w.a.logout.LogoutFilter : Did not match request to Or [Ant [pattern='/logout', GET], Ant [pattern='/logout', POST], Ant [pattern='/logout', PUT], Ant [pattern='/logout', DELETE]] TRACE 2540 --- o.s.security.web.FilterChainProxy : Invoking AuthTokenFilter (7/13) TRACE 2540 --- o.s.security.web.FilterChainProxy : Invoking RequestCacheAwareFilter (8/13) TRACE 2540 --- o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderAwareRequestFilter (9/13) TRACE 2540 --- o.s.security.web.FilterChainProxy : Invoking AnonymousAuthenticationFilter (10/13) TRACE 2540 ---[ o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]] TRACE 2540 --- o.s.security.web.FilterChainProxy : Invoking SessionManagementFilter (11/13) TRACE 2540 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking ExceptionTranslationFilter (12/13) TRACE 2540 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Invoking FilterSecurityInterceptor (13/13) TRACE 2540 --- [nio-8080-exec-2] edFilterInvocationSecurityMetadataSource : Did not match request to Ant [pattern='/api2/auth/**'] - [permitAll] (1/2) TRACE 2540 ---o.s.s.w.a.i.FilterSecurityInterceptor : Did not re-authenticate AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]] before authorizing TRACE 2540 --- o.s.s.w.a.expression.WebExpressionVoter : Voted to deny authorization TRACE 2540 --- o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [GET /product/slider] with attributes [authenticated] using AffirmativeBased [DecisionVoters=[org.springframework.security.web.access.expression.WebExpressionVoter@1dcad16f], AllowIfAllAbstainDecisions=false] TRACE 2540 --- o.s.s.w.a.ExceptionTranslationFilter : Sending AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]] to authentication entry point since access is denied
补充日志
ERROR 8184 --- c.s.e.security.jwt.AuthEntryPointJwt : Unauthorized error: Full authentication is required to access this resource > TRACE 8184 --- o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure] > DEBUG 8184 --- s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
项目类结构

问题分析
从日志可见,AuthTokenFilter已被调用,但后续AnonymousAuthenticationFilter仍设置了匿名身份到SecurityContextHolder,说明AuthTokenFilter未成功解析JWT并设置认证信息。核心原因包括:
AuthTokenFilter缺失从Cookie提取、验证JWT并设置Authentication的逻辑;- CORS配置未允许前端携带Cookie,导致后端无法获取认证信息;
- JWT Cookie的路径、域名等参数配置错误,前端请求未携带该Cookie;
UserDetailsServiceImpl未正确返回包含角色的用户信息。
解决办法
1. 完善AuthTokenFilter认证逻辑
实现从Cookie提取JWT、验证并设置Authentication的逻辑:
public class AuthTokenFilter extends OncePerRequestFilter { @Autowired private JwtUtils jwtUtils; @Autowired private UserDetailsServiceImpl userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = parseJwt(request); if (jwt != null && jwtUtils.validateJwtToken(jwt)) { String username = jwtUtils.getUserNameFromJwtToken(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { logger.error("Cannot set user authentication: {}", e); } filterChain.doFilter(request, response); } private String parseJwt(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { // 与JwtUtils中Cookie名称一致 if ("jwtToken".equals(cookie.getName())) { return cookie.getValue(); } } } return null; } }
2. 配置支持Cookie的CORS策略
在WebSecurityConfig中添加自定义CORS配置:
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowCredentials(true); configuration.setExposedHeaders(Arrays.asList(HttpHeaders.SET_COOKIE)); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf().disable() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests().antMatchers("/api2/auth/**").permitAll() .anyRequest().authenticated(); http.authenticationProvider(authenticationProvider()); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); }
Angular端请求需设置withCredentials: true:
this.http.get('/product/slider', { withCredentials: true }).subscribe(...);
3. 修正JwtUtils的Cookie生成逻辑
确保Cookie参数配置正确:
public ResponseCookie generateJwtCookie(UserDetailsImpl userPrincipal) { String jwt = generateTokenFromUsername(userPrincipal.getUsername()); ResponseCookie cookie = ResponseCookie.from("jwtToken", jwt) .path("/") .maxAge(24 * 60 * 60) .httpOnly(true) .secure(false) // 生产环境改为true .sameSite(SameSiteMode.LAX) .build(); return cookie; }
4. 检查UserDetailsServiceImpl实现
确保正确返回包含角色的用户信息:
@Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired UserRepository userRepository; @Override @Transactional public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username)); return UserDetailsImpl.build(user); } }
UserDetailsImpl的构建方法:
public static UserDetailsImpl build(User user) { List<GrantedAuthority> authorities = user.getRoles().stream() .map(role -> new SimpleGrantedAuthority(role.getName().name())) .collect(Collectors.toList()); return new UserDetailsImpl( user.getId(), user.getUsername(), user.getPassword(), authorities); }
验证步骤
- 登录后检查浏览器是否存在
jwtTokenCookie; - 访问接口时确认请求携带该Cookie;
- 调试
AuthTokenFilter,确认JWT被正确解析并设置Authentication。
内容的提问来源于stack exchange,提问作者Amirreza Andishmand
相关产品推荐
相关产品推荐

