spring security 自定义URL权限和权限校验异常

Spring Security使用FilterSecurityInterceptor过滤器来进行URL权限校验,实际使用流程大致如下:

  1. 通过数据库动态配置url资源权限
  2. 系统启动时,通过FilterSecurityInterceptor滤器到数据库加载系统资源权限列表
  3. 用户登陆时通过自定义的UserDetailsService加载当前用户的角色列表
  4. 当有请求访问时,通过FilterSecurityInterceptor对比系统资源权限列表和用户资源权限列表(在用户登录时添加到用户信息中)来判断用户是否有该url的访问权限。
自定义URL权限验证需要在FilterSecurityInterceptor自定义的配置项
  1. securityMetadataSource:实现FilterInvocationSecurityMetadataSource接口,在实现类中加载资源权限,并在filterSecurityInterceptor中注入该实现类。
  2. accessDecisionManager:通过实现AccessDecisionManager接口自定义一个决策管理器,判断是否有访问权限。判断逻辑可以写在决策管理器的决策方法中,也可以通过投票器实现,除了框架提供的三种投票器还可以添加自定义投票器。自定义投票器通过实现AccessDecisionVoter接口来实现。
    下是Spring Security官方文档提供的一个图,其展示了与基于投票的AccessDecisionManager实现相关的类
    spring security 自定义URL权限和权限校验异常_第1张图片
权限校验异常需要在ExceptionTranslationFilter自定义的配置项

1、AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
2、AccessDeineHandler 用来解决登陆认证过的用户访问无权限资源时的异常

demo代码如下:
1、自定义securityMetadataSource从数据库加载动态配置的角色url资源权限

@Component
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    private Map<RequestMatcher, Collection<ConfigAttribute>> allRoleSource = new HashMap<>();
    public MyFilterInvocationSecurityMetadataSource(){
        //模拟从数据库加载角色URL权限信息
        Map<String,String> urlRoleMap = new HashMap<String,String>(){{
            put("/open/**","ROLE_ANONYMOUS");
            put("/home","ADMIN,USER");
            put("/admin/**","ADMIN");
            put("/user/**","ADMIN,USER");
        }};
        Map<RequestMatcher, Collection<ConfigAttribute>> loadRequestMap = new HashMap<>();
        for(Map.Entry<String,String> entry:urlRoleMap.entrySet()){
            loadRequestMap.put(new AntPathRequestMatcher(entry.getKey()),SecurityConfig.createList(entry.getValue().split(",")));
        }
        allRoleSource = loadRequestMap;
    }

    //返回当前URL允许访问的角色列表
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        FilterInvocation fi = (FilterInvocation) object;
        HttpServletRequest request = fi.getRequest();
        //String url = fi.getRequestUrl();
        //String httpMethod = fi.getRequest().getMethod();
        for(Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry:allRoleSource.entrySet()){
            if(entry.getKey().matches(request)){
                return entry.getValue();
            }
        }
        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

2、自定义accessDecisionManager替换默认的AffirmativeBased,直接在决策管理器的决策方法中自己校验,不使用AccessDecisionVoter

public class MyAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
        while (iterator.hasNext()) {

            if (authentication == null) {
                throw new AccessDeniedException("当前访问没有权限");
            }

            ConfigAttribute configAttribute = iterator.next();
            String needCode = configAttribute.getAttribute();

            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (StringUtils.equals(authority.getAuthority(), needCode)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("当前访问没有权限");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return false;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

3、自定义AuthenticationEntryPoint替换默认的LoginUrlAuthenticationEntryPoint

public class MyAuthenticationEntryPoint  implements AuthenticationEntryPoint {
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        redirectStrategy.sendRedirect(request, response, "/login");
    }
}

4、定义 AccessDeniedHandler替换默认的AccessDeniedHandlerImpl

public class MyAccessDeineHandler implements AccessDeniedHandler {
    private String errorPage="error";
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        if (!response.isCommitted()) {
            if (errorPage != null) {
                // Put exception into request scope (perhaps of use to a view)
                request.setAttribute(WebAttributes.ACCESS_DENIED_403,
                        accessDeniedException);

                // Set the 403 status code.
                response.setStatus(HttpStatus.FORBIDDEN.value());

                // forward to error page.
                RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
                dispatcher.forward(request, response);
            }
            else {
                response.sendError(HttpStatus.FORBIDDEN.value(),
                        HttpStatus.FORBIDDEN.getReasonPhrase());
            }
        }
    }
}

3、同过HttpSecurity来配置自定义的配置项

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/login","/userlogin","/timeout").permitAll()
        //.accessDecisionManager()
        //.antMatchers("/user/**").hasRole("USER")
        //其他地址的访问均需验证权限
        .anyRequest().authenticated()
        .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
            public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
                fsi.setAccessDecisionManager(new MyAccessDecisionManager());
                fsi.setSecurityMetadataSource(new MyFilterInvocationSecurityMetadataSource());
                return fsi;
            }
        });
………………………………………………………………………………
    http.exceptionHandling()
        .authenticationEntryPoint(new MyAuthenticationEntryPoint())
        .accessDeniedHandler(new MyAccessDeineHandler());
}

你可能感兴趣的:(spring,boot)