I have been trying to implement GraphAPI methods authorization. But, always get 403.
Environment:
- JDK 17
- Spring Boot 3.1.5
- MongoDB
- Spring Security
- Graph QL
- OS: MacOS
JDK 17
My application file looks like:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class,
UserDetailsServiceAutoConfiguration.class})
@EnableAspectJAutoProxy
public class JoblobApplication {
public static void main(String[] args) {
SpringApplication.run(JoblobApplication.class, args);
}
}
Book entity looks like:
@Document(collection = "books")
@Getter
@Setter
public class Book {
@Id
private String id;
private String title;
private String author;
// Getters and setters
}
Book Repository :
public interface BookRepository extends MongoRepository<Book, String> {
}
GraphQL Resolver:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface GraphQLResolver {
}
@Component
public class ResourceResolver {
@Autowired
private BookRepository bookRepository;
@QueryMapping
@PreAuthorize("hasRole('ROLE_USER')")
public List<Book> books() {
return bookRepository.findAll();
}
@QueryMapping
@PreAuthorize("hasRole('ROLE_USER')")
public Book bookById(String id) {
return bookRepository.findById(id).orElse(null);
}
}
GraphQL Aspect:
@Aspect
@Component
@Order(1)
public class SecurityQraphQLAspect {
@Before("allGraphQLResolverMethods() && isDefinedInApplication() && !isMethodAnnotatedAsUnsecured()")
public void doSecurityCheck() {
if (SecurityContextHolder.getContext() == null ||
SecurityContextHolder.getContext().getAuthentication() == null ||
!SecurityContextHolder.getContext().getAuthentication().isAuthenticated() ||
AnonymousAuthenticationToken.class.isAssignableFrom(SecurityContextHolder.getContext().getAuthentication().getClass())) {
throw new AccessDeniedException("User not authenticated");
}
}
@Pointcut("@annotation(resolver.GraphQLResolver)")
private void allGraphQLResolverMethods() {
}
@Pointcut("within(resolver.*)")
private void isDefinedInApplication() {
}
@Pointcut("execution(public java.lang.String resolver.ResourceResolver.unsecuredResource())")
private void isUnsecuredResourceMethod() {
}
@Pointcut("@annotation(oresolver.Unsecured)")
private void isMethodAnnotatedAsUnsecured() {
}
}
Annotation Unsecured:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Unsecured {
}
Whenever I try to access using graphiql it returns 403. If I add @Unsecured to any query method it returns the response.
Security config looks like:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
private static final String REALM_ACCESS_CLAIM = "realm_access";
private static final String ROLES_CLAIM = "roles";
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.oauth2Client()
.and()
.oauth2Login()
.tokenEndpoint()
.and()
.userInfoEndpoint();
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
http
.authorizeHttpRequests()
.requestMatchers("/unauthenticated", "/graphiql","/graphql", "/oauth2/**", "/login/**").permitAll()
.anyRequest()
.fullyAuthenticated()
.and()
.logout()
.logoutSuccessUrl(
"https://localhost:8180/realms/test/protocol/openid-connect/logout?redirect_uri=https://localhost:9999/");
http.csrf().disable();
return http.build();
}
@Bean
@SuppressWarnings("unchecked")
public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {
return authorities -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
var authority = authorities.iterator().next();
boolean isOidc = authority instanceof OidcUserAuthority;
if (isOidc) {
var oidcUserAuthority = (OidcUserAuthority) authority;
var userInfo = oidcUserAuthority.getUserInfo();
if (userInfo.hasClaim(REALM_ACCESS_CLAIM)) {
var realmAccess = userInfo.getClaimAsMap(REALM_ACCESS_CLAIM);
var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
}
} else {
var oauth2UserAuthority = (OAuth2UserAuthority) authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey(REALM_ACCESS_CLAIM)) {
var realmAccess = (Map<String, Object>) userAttributes.get(REALM_ACCESS_CLAIM);
var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
}
}
return mappedAuthorities;
};
}
Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) {
return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase(Locale.ROOT))).collect(Collectors.toList());
}
1
Please enable spring security DEBUG logs and edit your question to include the full logs.
1 hour ago