如何在 Spring Boot OAuth2 的 SecurityContextHolder 中获取 JWT 令牌?

Posted

技术标签:

【中文标题】如何在 Spring Boot OAuth2 的 SecurityContextHolder 中获取 JWT 令牌?【英文标题】:How to get JWT token in SecurityContextHolder in Spring Boot OAuth2? 【发布时间】:2021-04-30 00:09:58 【问题描述】:

我有一个资源服务器正在接收带有有效承载令牌的请求。我可以将@AuthenticationPrincipal Jwt token 用于我需要从令牌中获取声明的所有请求,或者我应该能够从SecurityContextHolder 获取用户信息,对吗?我想从上下文持有者那里得到它。我假设我需要在SecurityConfig 中定义 jwt 转换器?我找不到有关推荐方法的任何信息。

here 中的代码适用于一些旧版本的 Spring Security... 我正在使用最新的 Spring Boot 和 Spring Security 5.4.2

@Configuration
@EnableWebSecurity
@Order(1)
public class JwtSecurityConfig extends WebSecurityConfigurerAdapter 

    @Value("$spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
    private String jwkSetUri;

    protected void configure(HttpSecurity http) throws Exception 
        http
                .httpBasic().disable()
                .formLogin(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeRequests(authorize -> authorize
                        .mvcMatchers(HttpMethod.GET, "/test/**").authenticated()
                        .mvcMatchers(HttpMethod.POST, "/test/**").authenticated()
                )
                .oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
    

    private JwtAuthenticationConverter jwtAuthenticationConverter() 
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("entGrps");
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    

    @Bean
    public JwtDecoder jwtDecoder() 
        return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
    

【问题讨论】:

【参考方案1】:

如果你打算使用 spring-security-oauth2-resource-server 依赖, 我认为最好使用@AuthenticationPrincipal Jwt token 来获取令牌的声明。

这样的代码更简单,样板代码更少。

但这取决于你,Imranmadbar 的解决方案也有效)

【讨论】:

【参考方案2】:

如果您想要来自 SecurityContextHolder 的信息,您必须将其保留在那里。

这是最简单的解决方案

    从您的当前日志用户信息所在的request获取Auth Token。 使用某些 Util 方法从 jwt 中提取日志 用户名。 使用此用户名从数据库中获取用户详细信息。 最后将此用户信息设置到 Spring Security 上下文持有者中。

您必须使用 HTTP 请求过滤器 (OncePerRequestFilter) 对每个请求执行此操作。

喜欢:

        import java.io.IOException;

        import javax.servlet.FilterChain;
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
        import org.springframework.security.core.context.SecurityContextHolder;
        import org.springframework.security.core.userdetails.UserDetails;
        import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
        import org.springframework.stereotype.Component;
        import org.springframework.web.filter.OncePerRequestFilter;
        import com.madbarsoft.config.MyUserDetailsService;
        import com.madbarsoft.utility.JwtUitl;

        @Component
        public class JwtRequestFilter extends OncePerRequestFilter     
                    
                    @Autowired
                    private JwtUitl jwtUitl;
                    
                    @Autowired
                    private MyUserDetailsService userDetailsService; 

                    @Override
                    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chin)
                            throws ServletException, IOException 
                        
                        final String authorizationHeader = request.getHeader("Authorization");
                        System.out.println("Authorization Header #:"+authorizationHeader);
                        
                        String userName = null;
                        String jwtStr = null;
                        
                        if(authorizationHeader != null && authorizationHeader.startsWith("Bearer "))
                            jwtStr = authorizationHeader.substring(7);
                            userName = jwtUitl.extractUserName(jwtStr);
                            System.out.println("Form JWT userName: "+userName);
                        
                        System.out.println("In Filter Before set #: "+SecurityContextHolder.getContext().getAuthentication());
                        if(userName != null && SecurityContextHolder.getContext().getAuthentication() == null)
                            UserDetails userDetails = this.userDetailsService.loadUserByUsername(userName);
                            if(jwtUitl.validateToken(jwtStr, userDetails))
                                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                                        userDetails, null, userDetails.getAuthorities());
                                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                            
                            System.out.println("In Filter After Set #: "+SecurityContextHolder.getContext().getAuthentication());
                        
                        chin.doFilter(request, response);
                    
                    
                

现在您可以从整个项目中的任何位置访问用户信息。

(这是一个正在运行的项目,您可以在此处从gitLink 获取一些信息。)

【讨论】:

【参考方案3】:

如果我们唯一关心的是从标头中检索令牌,我想出的最简单的解决方案是使用 DefaultBearerTokenResolver:

public class CheckAuthTokenFilter extends GenericFilterBean 

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 
        String token = new DefaultBearerTokenResolver().resolve((HttpServletRequest) servletRequest);
    

【讨论】:

以上是关于如何在 Spring Boot OAuth2 的 SecurityContextHolder 中获取 JWT 令牌?的主要内容,如果未能解决你的问题,请参考以下文章

如何在我的 Spring Boot Security OAuth2 应用程序中仅为某些类启用 OAuth2?

如何在 Spring Boot 中实现 oAuth2 和 JWT 身份验证? [关闭]

如何从 facebook/google/... oauth2 身份验证在 Spring (Boot) 中为单页应用程序派生自定义登录令牌?

如何在 Spring Boot OAuth2 的 SecurityContextHolder 中获取 JWT 令牌?

如何使用spring-boot 1.3.0.RC1为oauth2提供自定义安全性配置

Spring Boot OAuth2 资源服务器:库如何在没有公钥的情况下验证 JWT 令牌?