使用 @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?的主要内容,如果未能解决你的问题,请参考以下文章