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

Spring Boot中如何将自定义UserInfo类存入Security Context

如何将自定义UserInfo存入Spring Security Context

要把自定义的UserInfo类存入Spring Security的Security Context,核心是让Spring Security能够识别这个类作为认证主体——通常我们会通过实现UserDetails接口来达成这个目的,然后构建合法的Authentication对象并注入上下文。下面是具体的实现步骤:

1. 让UserInfo适配UserDetails接口

Spring Security的认证体系依赖UserDetails来描述用户信息,所以我们需要把UserInfo转换成UserDetails类型,有两种可选方式:

方式一:直接让UserInfo实现UserDetails

修改你的UserInfo类,实现UserDetails接口并完成必要方法的实现:

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo implements Serializable, UserDetails {
    private List<String> role = new ArrayList<>();
    private String username;
    private String email;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 将自定义的role列表转换为Spring Security可识别的GrantedAuthority集合
        return role.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        // 由于我们是从认证服务器获取的用户信息,这里不需要密码,返回null即可
        return null;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true; // 根据你的业务需求调整,默认账号未过期
    }

    @Override
    public boolean isAccountNonLocked() {
        return true; // 默认账号未锁定
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true; // 默认凭证未过期
    }

    @Override
    public boolean isEnabled() {
        return true; // 默认账号启用
    }
}

方式二:创建UserDetails适配器(解耦原有类)

如果不想修改原有的UserInfo类(比如它是第三方依赖类,或者不想和Spring Security强耦合),可以创建一个适配器类:

public class UserInfoUserDetails implements UserDetails {
    private final UserInfo userInfo;

    public UserInfoUserDetails(UserInfo userInfo) {
        this.userInfo = userInfo;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return userInfo.getRole().stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return userInfo.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    // 提供方法获取原始UserInfo对象,方便后续业务使用
    public UserInfo getUserInfo() {
        return userInfo;
    }
}

2. 在JwtAuthenticationFilter中构建Authentication并注入上下文

修改你的getJwtFromRequest方法,在成功获取并验证UserInfo后,构建Authentication对象并存入SecurityContextHolder:

如果用方式一(UserInfo直接实现UserDetails):

private void getJwtFromRequest(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    String bearerToken = request.getHeader("Authorization");
    if (!StringUtils.hasText(bearerToken) || !bearerToken.startsWith("Bearer ")) {
        throw new AccessTokenMissingException("No access token found in request headers");
    }
    // Call auth server to validate token
    try {
        ResponseEntity<String> result = authenticationService.getUserInfo(bearerToken.substring(7));
        
        // 先判断响应状态码,再解析用户信息(更合理的顺序)
        if (!result.getStatusCode().is2xxSuccessful()) {
            throw new InvalidAccessTokenException("Invalid access token");
        }
        
        UserInfo user = new ObjectMapper().readValue(result.getBody(), UserInfo.class);
        System.out.println(user.toString());

        // 构建Authentication对象
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                user,          // principal:认证主体,这里UserInfo已经是UserDetails类型
                null,          // credentials:JWT认证已完成密码验证,这里传null即可
                user.getAuthorities() // 用户权限集合
        );

        // 将认证信息存入Security Context
        SecurityContextHolder.getContext().setAuthentication(authentication);

    } catch (HttpClientErrorException.Unauthorized | IOException e) {
        throw new InvalidAccessTokenException("Invalid access token");
    }
    filterChain.doFilter(request, response);
}

如果用方式二(适配器):

把构建Authentication的部分替换成:

UserInfo user = new ObjectMapper().readValue(result.getBody(), UserInfo.class);
UserInfoUserDetails userDetails = new UserInfoUserDetails(user);

Authentication authentication = new UsernamePasswordAuthenticationToken(
        userDetails,
        null,
        userDetails.getAuthorities()
);

SecurityContextHolder.getContext().setAuthentication(authentication);

关键说明

  • UsernamePasswordAuthenticationToken是Spring Security中最常用的Authentication实现类,这里我们用它来封装认证信息。
  • SecurityContextHolder.getContext().setAuthentication(authentication)是将认证信息存入上下文的核心操作,后续Spring Security的其他组件(比如方法权限注解@PreAuthorize)就能从上下文中获取到用户信息和权限了。
  • 注意调整代码中状态码判断和用户信息解析的顺序,避免在响应失败时去解析无效的响应体。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:12:50