使用 @PreAuthorize 的 GraphQL 和 Spring Security?

Posted

技术标签:

【中文标题】使用 @PreAuthorize 的 GraphQL 和 Spring Security?【英文标题】:GraphQL and Spring Security using @PreAuthorize? 【发布时间】:2021-06-28 14:25:07 【问题描述】:

我在设置 spring 安全性和禁用/启用对 graphql 服务的 jwt-authenticated 基于角色的用户的访问时遇到问题。所有其他REST 端点都受到适当保护,JWT 身份验证和基于角色的授权工作正常。

到目前为止我所拥有的:

在我的WebSecurityConfigurerAdapter 课程中,我有以下代码:

@Override
protected void configure(HttpSecurity http) throws Exception 

    http.csrf().disable().cors()
                         .and()
                         .authorizeRequests().antMatchers(HttpMethod.OPTIONS, "**/student-service/auth/**").permitAll().antMatchers("**/student-service/auth/**").authenticated()
                         .and()
                         .authorizeRequests().antMatchers(HttpMethod.OPTIONS, "**/graphql/**").permitAll().antMatchers("**/graphql/**").authenticated()
                         .and()
                         .exceptionHandling()
            .authenticationEntryPoint(entryPoint).and().sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    http.headers().cacheControl();


graphql 服务上,我有一个@PreAuthorize

@Component 
public class UserResolver implements GraphQLQueryResolver
    
    @Autowired
    UserRepo repo;

    @PreAuthorize("hasAnyAuthority('ADMIN')")
    public User findUser(int id) 
        return User.builder()
                    .id(1)
                   .email("test@grr.la")
                   .password("123")
                   .username("John")
                   .bankAccount(BankAccount.builder()
                                            .id(1)
                                            .accountName("some account name")
                                            .accountNumber("some account number")
                                            .build())
                    .build();
    

localhost:8080/login 上获取 JWT 并发送graphql 查询后,使用上述配置和代码,我得到:

org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73) ~[spring-security-core-5.4.5.jar:5.4.5]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.attemptAuthorization(AbstractSecurityInterceptor.java:238) ~[spring-security-core-5.4.5.jar:5.4.5]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:208) ~[spring-security-core-5.4.5.jar:5.4.5]
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:58) ~[spring-security-core-5.4.5.jar:5.4.5]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.5.jar:5.3.5]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.5.jar:5.3.5]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) ~[spring-aop-5.3.5.jar:5.3.5]

这是来自Postman的请求的样子:

GraphQL 查询:

query
  findUser(id : 1)
    id
    email
  

然后回应:

    
    "errors": [
        
            "message": "Access is denied",
            "locations": [
                
                    "line": 2,
                    "column": 1
                
            ],
            "path": [
                "findUser"
            ],
            "extensions": 
                "type": "AccessDeniedException",
                "classification": "DataFetchingException"
            
        
    ],
    "data": 
        "findUser": null
    

application.yml文件:

graphql:
  servlet:
    max-query-depth: 100
    exception-handlers-enabled: true
  playground:
    headers:
      Authorization: Bearer TOKEN 

query.graphqls文件:

type Query
    
    findUser(id: ID): User
    


type User
    
    id: ID!
    username: String
    password: String
    email: String
    bankAccount: BankAccount


type BankAccount 
    id: ID!
    accountName: String
    accountNumber: String
    

【问题讨论】:

【参考方案1】:

我花了一天时间试图弄清楚这一点。在您的数据获取环境中,如果您调用

environment.getContext()

您应该取回一个 GraphQLContext 实例,该实例具有 HTTP 请求和具有授权的标头。对我来说,这本质上是一个空的 HashMap,没有关于请求的详细信息。在四处挖掘并尝试了 AOP 更改的所有内容后,我发现了 auth0 的建议,即创建一个实现 GraphQLInvocation 的类。这是我的解决方案,它将 Spring Security 上下文的实例放入数据获取环境上下文对象中。我现在至少能够验证数据获取器,因为我有一个 Spring Security 上下文可以使用(具有授予的权限等)。我宁愿有一个与 Spring Security 集成的过滤器(我可以让 preAuthorize 方法像你正在做),但我现在正在滚动这个时间。

@Primary
@Component
@Internal
public class SecurityContextGraphQLInvocation implements GraphQLInvocation 

    private final GraphQL graphQL;
    private final AuthenticationManager authenticationManager;

    public SecurityContextGraphQLInvocation(GraphQL graphQL, AuthenticationManager authenticationManager) 
        this.graphQL = graphQL;
        this.authenticationManager = authenticationManager;
    

    @Override
    public CompletableFuture<ExecutionResult> invoke(GraphQLInvocationData invocationData, WebRequest webRequest) 
        final String header = webRequest.getHeader("Authorization");
        SecurityContext securityContext;
        if (header == null || !header.startsWith("Bearer ")) 
            securityContext = new SecurityContextImpl();
         else 
            String authToken = header.substring(7);
            JwtAuthenticationToken authRequest = new JwtAuthenticationToken(authToken);
            final var authentication = authenticationManager.authenticate(authRequest);
            securityContext = new SecurityContextImpl(authentication);
        

        ExecutionInput executionInput = ExecutionInput.newExecutionInput()
                .query(invocationData.getQuery())
                .context(securityContext)
                .operationName(invocationData.getOperationName())
                .variables(invocationData.getVariables())
                .build();
        return graphQL.executeAsync(executionInput);
    

【讨论】:

嗨,迈克。感谢您花时间和精力调查我遇到的问题。不幸的是,由于我的工作职责,我不确定何时能够就这个问题给出详细的答案。我记得我找到了正确的解决方案,并且我切换到了 Graphql SPQR 库,它作为 spring 启动器依赖项提供。

以上是关于使用 @PreAuthorize 的 GraphQL 和 Spring Security?的主要内容,如果未能解决你的问题,请参考以下文章

Spring自定义授权无法使用@PreAuthorize

使用@PreAuthorize 时的安全配置

使用 JUnit 测试具有 @PreAuthorize 的服务方法

使用自定义 bean 测试 PreAuthorize 注释

spring @Preauthorize 中的自定义方法

使用 @PreAuthorize 时从 Spring 控制器返回 405 与 403