diff --git a/mall-security/src/main/java/com/macro/mall/security/component/DynamicAccessDecisionManager.java b/mall-security/src/main/java/com/macro/mall/security/component/DynamicAccessDecisionManager.java new file mode 100644 index 0000000..36dfec2 --- /dev/null +++ b/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 configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { + // 当接口未被配置资源时直接放行 + if (CollUtil.isEmpty(configAttributes)) { + return; + } + Iterator 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; + } + +} diff --git a/mall-security/src/main/java/com/macro/mall/security/component/DynamicSecurityFilter.java b/mall-security/src/main/java/com/macro/mall/security/component/DynamicSecurityFilter.java new file mode 100644 index 0000000..ae49b8b --- /dev/null +++ b/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; + } + +} diff --git a/mall-security/src/main/java/com/macro/mall/security/component/DynamicSecurityMetadataSource.java b/mall-security/src/main/java/com/macro/mall/security/component/DynamicSecurityMetadataSource.java new file mode 100644 index 0000000..7bae7ef --- /dev/null +++ b/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 configAttributeMap = null; + @Autowired + private DynamicSecurityService dynamicSecurityService; + + @PostConstruct + public void loadDataSource() { + configAttributeMap = dynamicSecurityService.loadDataSource(); + } + + public void clearDataSource() { + configAttributeMap.clear(); + configAttributeMap = null; + } + + @Override + public Collection getAttributes(Object o) throws IllegalArgumentException { + if (configAttributeMap == null) this.loadDataSource(); + List configAttributes = new ArrayList<>(); + //获取当前访问的路径 + String url = ((FilterInvocation) o).getRequestUrl(); + String path = URLUtil.getPath(url); + PathMatcher pathMatcher = new AntPathMatcher(); + Iterator 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 getAllConfigAttributes() { + return null; + } + + @Override + public boolean supports(Class aClass) { + return true; + } + +} diff --git a/mall-security/src/main/java/com/macro/mall/security/component/DynamicSecurityService.java b/mall-security/src/main/java/com/macro/mall/security/component/DynamicSecurityService.java new file mode 100644 index 0000000..1246672 --- /dev/null +++ b/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 loadDataSource(); +} diff --git a/mall-security/src/main/java/com/macro/mall/security/component/RestfulAccessDeniedHandler.java b/mall-security/src/main/java/com/macro/mall/security/component/RestfulAccessDeniedHandler.java index 4c28a6f..1d0795d 100644 --- a/mall-security/src/main/java/com/macro/mall/security/component/RestfulAccessDeniedHandler.java +++ b/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()))); diff --git a/mall-security/src/main/java/com/macro/mall/security/config/SecurityConfig.java b/mall-security/src/main/java/com/macro/mall/security/config/SecurityConfig.java index 8baf18e..a16d7fe 100644 --- a/mall-security/src/main/java/com/macro/mall/security/config/SecurityConfig.java +++ b/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.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(); + } + }