spring security自定义后台权限过滤的方案

发表于 2024-10-20 | 后端

大概思路

其实方案有好几种,比如注解权限@PreAuthorize("hasRole('ROLE_admin') and hasAnyRole('ROLE_user')"),类似这种注解式的,还有在配置里的hasRole之类的。

我的其中一种思路

@Service
@RequiredArgsConstructor
public class InyaaAccessDecisionManager implements AuthorizationManager<RequestAuthorizationContext> {
 
    private final SecurityMetadataSource securityMetadataSource;
 
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        Collection<ConfigAttribute> collection = this.securityMetadataSource.getAttributes(context);
        // 遍历角色
        for (ConfigAttribute ca : collection) {
            // ① 当前url请求需要的权限
            String needRole = ca.getAttribute();
            if ("ROLE_ANY".equals(needRole)) {
                return new AuthorizationDecision(true);
            } else {
                // ② 当前用户所具有的角色
                Collection<? extends GrantedAuthority> authorities = authentication.get().getAuthorities();
                for (GrantedAuthority authority : authorities) {
                    if ("ROLE_ANONYMOUS".equals(authority.getAuthority())) {
                        return new AuthorizationDecision(false);
                    } else {
                        return new AuthorizationDecision(true);
                    }
                }
            }
        }
        return new AuthorizationDecision(false);
    }
}
 
@Service
@RequiredArgsConstructor
public class InyaaFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
 
    private final CacheService cacheService;
 
    /***
     * 返回该url所需要的用户权限信息
     *
     * @param object: 储存请求url信息
     * @return: null:标识不需要任何权限都可以访问
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        HttpServletRequest request = ((RequestAuthorizationContext) object).getRequest();
        Map<String, Collection<ConfigAttribute>> cacheMap = cacheService.getConfigAttributeMap();
        for (String url : cacheMap.keySet()) {
            if (new AntPathRequestMatcher(url).matches(request)) {
                return cacheMap.get(url);
            }
        }
        throw new AccessDeniedException("当前访问没有权限!");
    }
 
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }
 
    @Override
    public boolean supports(Class<?> aClass) {
        return FilterInvocation.class.isAssignableFrom(aClass);
    }
}
 

AuthorizationManager<RequestAuthorizationContext>主要是对角色的验证,他加载FilterInvocationSecurityMetadataSource接口的数据。 而FilterInvocationSecurityMetadataSource接口主要是做权限的匹配,其中我用到了缓存来加载所有的权限,然后通过url去匹配

而缓存内的加载大致如下:

public Map<String, Collection<ConfigAttribute>> getConfigAttributeMap() {
        if (AuthCache.size() < 1) {
            List<InyawSysApi> list = inyawSysApiDao.findAll();
            for (InyawSysApi api : list) {
                List<ConfigAttribute> configAttributeList = new ArrayList<>();
                ConfigAttribute configAttribute;
                switch (api.getType()) {
                    case 0 -> configAttribute = new SecurityConfig("ROLE_ANY");
                    case 1 -> configAttribute = new SecurityConfig("ROLE_LOGIN");
                    case 2 -> {
                        InyawSysRole role = inyawSysRoleService.getById(api.getId());
                        configAttribute = new SecurityConfig(role.getRoleKey());
                    }
                    default -> throw new IllegalStateException("错误的类型: " + api.getType());
                }
                configAttributeList.add(configAttribute);
                AuthCache.put(api.getUrl(), configAttributeList);
            }
        }
        return AuthCache;
    }
 

其余的不多做解释了,代码水平也一般。大概就是api表按type太判断具体的角色权限,因为我当时没想好具体的权限表如何设计,我现实没这样的需求

最后是配置类

http
                .authorizeHttpRequests((authorize) -> authorize
                        .anyRequest().access(inyaaAccessDecisionManager)
                )
                .csrf(AbstractHttpConfigurer::disable)
                //.httpBasic(Customizer.withDefaults())
                .oauth2ResourceServer(httpSecurityOAuth2ResourceServerConfigurer -> httpSecurityOAuth2ResourceServerConfigurer.jwt(Customizer.withDefaults()))
                .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling((exceptions) -> exceptions
                        .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                        .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
                );

这个配置类是官方demo的jwt方案的配置类,权限代码具体有用的就只有前几行csrf前边那部分。