Browse Source

添加基于路径的动态权限支持

macro 5 years ago
parent
commit
f1bec5df7f

+ 51 - 0
mall-security/src/main/java/com/macro/mall/security/component/DynamicAccessDecisionManager.java

@@ -0,0 +1,51 @@
+package com.macro.mall.security.component;
+
+import cn.hutool.core.collection.CollUtil;
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * 动态权限决策管理器,用于判断用户是否有访问权限
+ * Created by macro on 2020/2/7.
+ */
+public class DynamicAccessDecisionManager implements AccessDecisionManager {
+
+    @Override
+    public void decide(Authentication authentication, Object object,
+                       Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
+        // 当接口未被配置资源时直接放行
+        if (CollUtil.isEmpty(configAttributes)) {
+            return;
+        }
+        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
+        while (iterator.hasNext()) {
+            ConfigAttribute configAttribute = iterator.next();
+            //将访问所需资源或用户拥有资源进行比对
+            String needAuthority = configAttribute.getAttribute();
+            for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
+                if (needAuthority.trim().equals(grantedAuthority.getAuthority())) {
+                    return;
+                }
+            }
+        }
+        throw new AccessDeniedException("抱歉,您没有访问权限");
+    }
+
+    @Override
+    public boolean supports(ConfigAttribute configAttribute) {
+        return true;
+    }
+
+    @Override
+    public boolean supports(Class<?> aClass) {
+        return true;
+    }
+
+}

+ 77 - 0
mall-security/src/main/java/com/macro/mall/security/component/DynamicSecurityFilter.java

@@ -0,0 +1,77 @@
+package com.macro.mall.security.component;
+
+import com.macro.mall.security.config.IgnoreUrlsConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.access.SecurityMetadataSource;
+import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
+import org.springframework.security.access.intercept.InterceptorStatusToken;
+import org.springframework.security.web.FilterInvocation;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * 动态权限过滤器,用于实现基于路径的动态权限过滤
+ * Created by macro on 2020/2/7.
+ */
+public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter {
+
+    @Autowired
+    private DynamicSecurityMetadataSource dynamicSecurityMetadataSource;
+    @Autowired
+    private IgnoreUrlsConfig ignoreUrlsConfig;
+
+    @Autowired
+    public void setMyAccessDecisionManager(DynamicAccessDecisionManager dynamicAccessDecisionManager) {
+        super.setAccessDecisionManager(dynamicAccessDecisionManager);
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+    }
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+        HttpServletRequest request = (HttpServletRequest) servletRequest;
+        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
+        //OPTIONS请求直接放行
+        if(request.getMethod().equals(HttpMethod.OPTIONS.toString())){
+            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
+            return;
+        }
+        //白名单请求直接放行
+        PathMatcher pathMatcher = new AntPathMatcher();
+        for (String path : ignoreUrlsConfig.getUrls()) {
+            if(pathMatcher.match(path,request.getRequestURI())){
+                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
+                return;
+            }
+        }
+        //此处会调用AccessDecisionManager中的decide方法进行鉴权操作
+        InterceptorStatusToken token = super.beforeInvocation(fi);
+        try {
+            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
+        } finally {
+            super.afterInvocation(token, null);
+        }
+    }
+
+    @Override
+    public void destroy() {
+    }
+
+    @Override
+    public Class<?> getSecureObjectClass() {
+        return FilterInvocation.class;
+    }
+
+    @Override
+    public SecurityMetadataSource obtainSecurityMetadataSource() {
+        return dynamicSecurityMetadataSource;
+    }
+
+}

+ 64 - 0
mall-security/src/main/java/com/macro/mall/security/component/DynamicSecurityMetadataSource.java

@@ -0,0 +1,64 @@
+package com.macro.mall.security.component;
+
+import cn.hutool.core.util.URLUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.web.FilterInvocation;
+import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+
+import javax.annotation.PostConstruct;
+import java.util.*;
+
+/**
+ * 动态权限数据源,用于获取动态权限规则
+ * Created by macro on 2020/2/7.
+ */
+public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
+
+    private static Map<String, ConfigAttribute> configAttributeMap = null;
+    @Autowired
+    private DynamicSecurityService dynamicSecurityService;
+
+    @PostConstruct
+    public void loadDataSource() {
+        configAttributeMap = dynamicSecurityService.loadDataSource();
+    }
+
+    public void clearDataSource() {
+        configAttributeMap.clear();
+        configAttributeMap = null;
+    }
+
+    @Override
+    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
+        if (configAttributeMap == null) this.loadDataSource();
+        List<ConfigAttribute>  configAttributes = new ArrayList<>();
+        //获取当前访问的路径
+        String url = ((FilterInvocation) o).getRequestUrl();
+        String path = URLUtil.getPath(url);
+        PathMatcher pathMatcher = new AntPathMatcher();
+        Iterator<String> iterator = configAttributeMap.keySet().iterator();
+        //获取访问该路径所需资源
+        while (iterator.hasNext()) {
+            String pattern = iterator.next();
+            if (pathMatcher.match(pattern, path)) {
+                configAttributes.add(configAttributeMap.get(pattern));
+            }
+        }
+        // 未设置操作请求权限,返回空集合
+        return configAttributes;
+    }
+
+    @Override
+    public Collection<ConfigAttribute> getAllConfigAttributes() {
+        return null;
+    }
+
+    @Override
+    public boolean supports(Class<?> aClass) {
+        return true;
+    }
+
+}

+ 16 - 0
mall-security/src/main/java/com/macro/mall/security/component/DynamicSecurityService.java

@@ -0,0 +1,16 @@
+package com.macro.mall.security.component;
+
+import org.springframework.security.access.ConfigAttribute;
+
+import java.util.Map;
+
+/**
+ * 动态权限相关业务类
+ * Created by macro on 2020/2/7.
+ */
+public interface DynamicSecurityService {
+    /**
+     * 加载资源ANT通配符和资源对应MAP
+     */
+    Map<String, ConfigAttribute> loadDataSource();
+}

+ 2 - 0
mall-security/src/main/java/com/macro/mall/security/component/RestfulAccessDeniedHandler.java

@@ -20,6 +20,8 @@ public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
     public void handle(HttpServletRequest request,
                        HttpServletResponse response,
                        AccessDeniedException e) throws IOException, ServletException {
+        response.setHeader("Access-Control-Allow-Origin", "*");
+        response.setHeader("Cache-Control","no-cache");
         response.setCharacterEncoding("UTF-8");
         response.setContentType("application/json");
         response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));

+ 30 - 3
mall-security/src/main/java/com/macro/mall/security/config/SecurityConfig.java

@@ -1,9 +1,9 @@
 package com.macro.mall.security.config;
 
-import com.macro.mall.security.component.JwtAuthenticationTokenFilter;
-import com.macro.mall.security.component.RestAuthenticationEntryPoint;
-import com.macro.mall.security.component.RestfulAccessDeniedHandler;
+import com.macro.mall.security.component.*;
 import com.macro.mall.security.util.JwtTokenUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.authentication.AuthenticationManager;
@@ -14,6 +14,7 @@ import org.springframework.security.config.annotation.web.configurers.Expression
 import org.springframework.security.config.http.SessionCreationPolicy;
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 
 
@@ -23,6 +24,9 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
  */
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
+    @Autowired(required = false)
+    private DynamicSecurityService dynamicSecurityService;
+
     @Override
     protected void configure(HttpSecurity httpSecurity) throws Exception {
         ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
@@ -53,6 +57,10 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
                 // 自定义权限拦截器JWT过滤器
                 .and()
                 .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
+        //有动态权限配置时添加动态权限校验过滤器
+        if(dynamicSecurityService!=null){
+            registry.and().addFilterBefore(dynamicSecurityFilter(), FilterSecurityInterceptor.class);
+        }
     }
 
     @Override
@@ -97,4 +105,23 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
         return new JwtTokenUtil();
     }
 
+    @ConditionalOnBean(name = "dynamicSecurityService")
+    @Bean
+    public DynamicAccessDecisionManager dynamicAccessDecisionManager() {
+        return new DynamicAccessDecisionManager();
+    }
+
+
+    @ConditionalOnBean(name = "dynamicSecurityService")
+    @Bean
+    public DynamicSecurityFilter dynamicSecurityFilter() {
+        return new DynamicSecurityFilter();
+    }
+
+    @ConditionalOnBean(name = "dynamicSecurityService")
+    @Bean
+    public DynamicSecurityMetadataSource dynamicSecurityMetadataSource() {
+        return new DynamicSecurityMetadataSource();
+    }
+
 }