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

Spring Boot 2/Security 5中@AuthenticationPrincipal空值、@PreAuthorize失效问题

解决Spring Security中@AuthenticationPrincipal返回Null及@PreAuthorize失效问题

你的核心问题是登录成功后@AuthenticationPrincipal无法注入用户对象,进而导致@PreAuthorize注解无法正常生效。结合你提供的代码,我整理了几个关键排查点和解决方案:

1. 类型不匹配导致注入失败

你控制器中@AuthenticationPrincipal User user里的User是业务实体类,但Spring Security的SecurityContext中存储的是UserDetails接口的实现类(你在UserDetailsServiceImpl中返回的是org.springframework.security.core.userdetails.User)。这两个类型不匹配,Spring无法自动注入,因此返回Null。

解决方法:

有两种常见处理方式:

方式一:直接使用UserDetails作为参数

修改控制器方法,先通过UserDetails获取用户名,再查询业务用户:

@RequestMapping(value = "/self",method = RequestMethod.GET)
public ResponseEntity<User> getLogedInUser(@AuthenticationPrincipal UserDetails userDetails){
    User businessUser = service.getUserByName(userDetails.getUsername());
    return new ResponseEntity<>(businessUser, HttpStatus.OK);
}

方式二:自定义UserDetails实现类,封装业务用户

创建自定义UserDetails实现,把业务User对象包含进去:

public class CustomUserDetails extends org.springframework.security.core.userdetails.User {
    private final User businessUser;

    public CustomUserDetails(User businessUser, Collection<? extends GrantedAuthority> authorities) {
        super(businessUser.getName(), businessUser.getPassword(), authorities);
        this.businessUser = businessUser;
    }

    public User getBusinessUser() {
        return businessUser;
    }
}

然后在UserDetailsServiceImpl中返回这个自定义类:

@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = userRepository.findUserByNameEquals(username)
            .orElseThrow(() -> new UsernameNotFoundException(errorMessage));
    HashSet<GrantedAuthority> authorities = new HashSet<>();
    if(user.getRoles() != null){
        user.getRoles().stream()
                .map(Role::getName)
                .map(SimpleGrantedAuthority::new)
                .forEach(authorities::add);
    }
    return new CustomUserDetails(user, authorities);
}

最后在控制器中通过表达式注入业务用户:

@RequestMapping(value = "/self",method = RequestMethod.GET)
public ResponseEntity<User> getLogedInUser(@AuthenticationPrincipal(expression = "businessUser") User user){
    return new ResponseEntity<>(user, HttpStatus.OK);
}

2. 自定义登录过滤器未正确设置SecurityContext

你的RESTAuthenticationFilter是自定义登录过滤器,需确保认证成功后将Authentication对象存入SecurityContextHolder,否则后续请求无法获取认证信息。

检查RESTAuthenticationFilter是否重写了successfulAuthentication方法,若没有,添加以下逻辑:

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    // 将认证结果存入SecurityContext
    SecurityContextHolder.getContext().setAuthentication(authResult);
    // 调用成功处理器
    getSuccessHandler().onAuthenticationSuccess(request, response, authResult);
    // 继续执行过滤器链
    chain.doFilter(request, response);
}

如果过滤器继承了UsernamePasswordAuthenticationFilter,直接调用父类的successfulAuthentication方法即可,它会自动处理SecurityContext的存储。

3. 验证@PreAuthorize的角色匹配问题

注意hasRole('USER')会自动给角色名称添加ROLE_前缀,若你数据库中存储的角色名称是USER(无前缀),hasRole('USER')实际会匹配ROLE_USER,导致权限校验失败。

若角色名称无前缀,可改用hasAuthority('USER'):

@PreAuthorize("hasAuthority('USER')")
public ResponseEntity<List<User>> getAllUsers() {
    return new ResponseEntity<>(service.getAllUsers(), HttpStatus.OK);
}

或者在配置中移除默认前缀:

@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
    return new GrantedAuthorityDefaults(""); // 移除默认的ROLE_前缀
}

4. 确认SecurityContext的存储策略

由于你使用基于Session的认证,需确保SecurityContext被正确存储到HttpSession中。Spring Security默认通过HttpSessionSecurityContextRepository处理该逻辑,但如果自定义过滤器未正确触发存储,会导致后续请求无法获取认证信息。

可在登录成功处理器中添加调试代码,检查Session中是否存在SPRING_SECURITY_CONTEXT属性,确认存储是否正常。

内容的提问来源于stack exchange,提问作者Yanick Schraner

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:20:21