You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot控制器中如何利用自定义JWT声明实现授权?

在@PreAuthorize中使用Keycloak自定义声明的最佳方案

针对你的需求,有几种实用的方案可以直接在@PreAuthorize表达式中对比Keycloak JWT的自定义声明与路径参数,下面逐一说明:

方案一:自定义SpEL函数(最直观易用)

通过扩展Spring Security的SpEL表达式,添加一个直接获取custom_claim的函数,这样可以在@PreAuthorize中像调用普通方法一样使用。

1. 创建自定义表达式根类

继承SecurityExpressionRoot并实现MethodSecurityExpressionOperations,添加获取自定义声明的方法:

import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
import org.springframework.security.core.Authentication;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.representations.AccessToken;

public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    private Object filterObject;
    private Object returnObject;
    private Object target;

    public CustomMethodSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    // 自定义获取custom_claim的方法
    public Object getCustomClaim() {
        if (getAuthentication() instanceof KeycloakAuthenticationToken token) {
            AccessToken accessToken = token.getAccount().getKeycloakSecurityContext().getToken();
            // 根据你的实际结构获取声明,原问题中是otherclaims[0],这里按需调整
            return accessToken.getOtherClaims().get("custom_claim");
        }
        return null;
    }

    @Override
    public void setFilterObject(Object filterObject) {
        this.filterObject = filterObject;
    }

    @Override
    public Object getFilterObject() {
        return filterObject;
    }

    @Override
    public void setReturnObject(Object returnObject) {
        this.returnObject = returnObject;
    }

    @Override
    public Object getReturnObject() {
        return returnObject;
    }

    @Override
    public Object getThis() {
        return target;
    }

    public void setThis(Object target) {
        this.target = target;
    }
}

2. 配置自定义表达式处理器

注册一个自定义的MethodSecurityExpressionHandler,让Spring Security使用我们的表达式根:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {

    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler() {
            @Override
            protected SecurityExpressionRoot createSecurityExpressionRoot(Authentication authentication) {
                CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
                root.setPermissionEvaluator(getPermissionEvaluator());
                root.setTrustResolver(getTrustResolver());
                root.setRoleHierarchy(getRoleHierarchy());
                return root;
            }
        };
        return handler;
    }
}

3. 在控制器中使用

直接在@PreAuthorize中调用getCustomClaim()与路径参数对比:

@RestController
@RequestMapping("/api")
public class ResourceController {

    @GetMapping("/items/{itemId}")
    @PreAuthorize("getCustomClaim() == #itemId")
    public String getItem(@PathVariable("itemId") Integer itemId) {
        return "Access granted to item: " + itemId;
    }
}

注意:如果custom_claim和路径参数类型不同(比如一个是String一个是Integer),需要做类型转换,例如getCustomClaim().toString() == #itemId或者T(Integer).valueOf(#itemId) == getCustomClaim()

方案二:将自定义声明注入Authentication详情

通过自定义Keycloak认证转换器,把custom_claim提取出来放到Authentication的details属性中,这样可以直接在SpEL中访问。

1. 自定义Keycloak认证转换器

import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationConverter;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.representations.AccessToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

public class CustomKeycloakAuthConverter implements KeycloakAuthenticationConverter {

    private final KeycloakAuthenticationConverter delegate = new KeycloakAuthenticationConverter() {};

    @Override
    public Authentication convert(Authentication authentication) {
        KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) delegate.convert(authentication);
        if (token != null) {
            AccessToken accessToken = token.getAccount().getKeycloakSecurityContext().getToken();
            Object customClaim = accessToken.getOtherClaims().get("custom_claim");
            // 将自定义声明存入自定义Details类
            token.setDetails(new CustomAuthDetails(customClaim));
        }
        return token;
    }

    @Override
    public Collection<GrantedAuthority> extractAuthorities(AccessToken accessToken) {
        return delegate.extractAuthorities(accessToken);
    }

    // 自定义Details类存储声明
    public static class CustomAuthDetails {
        private final Object customClaim;

        public CustomAuthDetails(Object customClaim) {
            this.customClaim = customClaim;
        }

        public Object getCustomClaim() {
            return customClaim;
        }
    }
}

2. 配置Keycloak认证提供者

import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;

@Configuration
public class KeycloakAuthConfig {

    @Bean
    public AuthenticationProvider keycloakAuthenticationProvider() {
        KeycloakAuthenticationProvider provider = new KeycloakAuthenticationProvider();
        provider.setKeycloakAuthenticationConverter(new CustomKeycloakAuthConverter());
        return provider;
    }
}

3. 控制器中使用

@RestController
@RequestMapping("/api")
public class ResourceController {

    @GetMapping("/items/{itemId}")
    @PreAuthorize("authentication.details.customClaim == #itemId")
    public String getItem(@PathVariable("itemId") Integer itemId) {
        return "Access granted to item: " + itemId;
    }
}

方案三:使用静态工具类(最简实现)

编写一个静态工具类获取当前认证的自定义声明,然后在SpEL中直接调用。

1. 静态工具类

import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.representations.AccessToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

public class ClaimHelper {

    public static Object getCustomClaim() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth instanceof KeycloakAuthenticationToken token) {
            AccessToken accessToken = token.getAccount().getKeycloakSecurityContext().getToken();
            return accessToken.getOtherClaims().get("custom_claim");
        }
        return null;
    }
}

2. 控制器中使用

@RestController
@RequestMapping("/api")
public class ResourceController {

    @GetMapping("/items/{itemId}")
    @PreAuthorize("T(com.yourpackage.ClaimHelper).getCustomClaim() == #itemId")
    public String getItem(@PathVariable("itemId") Integer itemId) {
        return "Access granted to item: " + itemId;
    }
}

内容的提问来源于stack exchange,提问作者George

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 18:35:21