使用graphql在Spring Boot中进行身份验证
我正在使用GraphQL进行Spring Boot项目.我正在使用graphql-java-tools和graphql-spring-boot-starter.正如您在下面的Java配置文件中看到的那样,我设法通过Spring Security配置了安全性和会话管理.
I’m working on a spring boot project with GraphQL. I'm using graphql-java-tools and graphql-spring-boot-starter. I managed to configure security and session management with spring security as you can see in the java config files below.
现在"/graphql"路径是安全的(只能在请求的http头中发送基本http身份验证"或会话令牌(x-auth-token
)来访问它).在任何GraphQL操作上使用基本http身份验证"进行身份验证将启动一个新会话,并在标头中发送回新的会话令牌,该令牌可进一步用于继续该会话.
Now the "/graphql" path is secured (it can be accessed only sending the "basic http authentication" or a session token (x-auth-token
) in a http header of the request). Authenticating with "basic http authentication" on any GraphQL operation will start a new session and send back the new session token in a header, and that token can be used further to continue that session.
如何授予匿名用户访问一些GraphQL查询/突变的权限,以保持上述行为?
How to give access to anonymous users to some GraphQL queries/mutations keeping the above behavior?
如果为了允许匿名访问而将antMatchers("/graphql").authenticated()
更改为antMatchers("/graphql").permitAll()
,那么即使我尝试使用基本http身份验证"进行身份验证,也不会再调用我的自定义AuthenticationProvider
.
If I change antMatchers("/graphql").authenticated()
to antMatchers("/graphql").permitAll()
in order to allow anonymous access, then my custom AuthenticationProvider
is not called anymore even when I try to authenticate with "basic http authentication".
谢谢!
这是我的配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationProvider authenticationProvider;
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(authenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/graphql").authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic()
.and()
.headers()
.frameOptions().sameOrigin() // needed for H2 web console
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry());
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 180)
public class HttpSessionConfig {
@Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
}
我们使用.antMatchers("/graphql").permitAll()
代替了.antMatchers("/graphql").permitAll()
,然后删除了.httpBasic()
,还删除了自定义的AuthenticationProvider
.现在,安全配置如下所示:
Instead of .antMatchers("/graphql").authenticated()
we used .antMatchers("/graphql").permitAll()
, then we removed .httpBasic()
and also removed the custom AuthenticationProvider
. Now the security configs look like this:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/graphql").permitAll()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.headers()
.frameOptions().sameOrigin() // needed for H2 web console
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry());
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
然后,我们为登录创建了一个变异,该变异接受用户的凭据并返回会话令牌.这是graphql模式:
Then we created a mutation for login that accepts the user's credentials and returns the session token. Here is the graphql schema:
login(credentials: CredentialsInputDto!): String
input CredentialsInputDto {
username: String!
password: String!
}
基本上,我们在自定义AuthenticationProvider中拥有的代码已进入登录操作所调用的服务:
Basically the code we had in our custom AuthenticationProvider went into the service that is called by the login operation:
public String login(CredentialsInputDto credentials) {
String username = credentials.getUsername();
String password = credentials.getPassword();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
... credential checks and third party authentication ...
Authentication authentication = new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
httpSession.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
return httpSession.getId();
}
关键在于,我们使用经过身份验证的用户的身份验证准备了会话上下文,然后将其保存(以redis格式)作为称为"SPRING_SECURITY_CONTEXT"的会话属性.当您发出一个请求,该请求设置了"x-auth-token"标头并设置了从登录操作获得的会话令牌的值时,spring便能够自动恢复上下文.
The key is that we prepared the session context with the authenticated user's authentication and then we save it (in redis) as a session attribute called "SPRING_SECURITY_CONTEXT". This is all what spring needs to be able to automatically restore the context when you make a request having the "x-auth-token" header set with the value of the session token obtained from the login operation.
由于.antMatchers("/graphql").permitAll()
现在也允许匿名调用,并且在服务层中,在公共方法上,我们可以使用如下注释:@Preauthorize("isAnonymous()
或hasRole("USER")")
.
Now also anonymous calls are allowed because of .antMatchers("/graphql").permitAll()
and in the service layer, on public methods we can use annotations like this: @Preauthorize("isAnonymous()
OR hasRole("USER")")
.