CustomFilterSecurityMetadataSource和CustomAccessDecisionManager中的代码不执行

问题描述:

使用springsecurity中,对用户进行权限验证,自定义权限控制管理器始终无法执行decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)方法和getAttributes(Object object)方法。
一开始以为是两个实现类没有加入到spring容器中,后来为他们添加了构造函数,项目启动的时候执行了构造,说明他们已经早容器中了。


问题原因

这个问题困扰了我一天多的时间,真的是太后悔使用shiro、springsecurity这种授权框架了,自己写一个都很简单,言归正传。原因是WebSecurityConfig中配置出错。我贴一下正确的配置方式代码(重要的部分不多不用自己看,看分析就行)

package com.luntek.security.springsecurity.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.luntek.security.springsecurity.entity.User;
import com.luntek.security.springsecurity.service.impl.PasswordEncoderImpl;
import com.luntek.security.springsecurity.util.JsonUtil;
import com.luntek.security.springsecurity.util.JwtTokenUtil;
import com.luntek.security.springsecurity.util.RedisOperator;
import com.luntek.security.springsecurity.util.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * 自定义的用户名密码认证配置类
 *
 * @Date 2020/3/5 0005 下午 2:32
 * @Created by Czw
 */
@Slf4j
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    @Qualifier("userDetailsService")
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private RedisOperator redisOperator;

    @Autowired
    @Qualifier("customAccessDecisionManager")
    private AccessDecisionManager accessDecisionManager;

    @Autowired
    @Qualifier("customFilterInvocationSecurityMetadataSource")
    private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;

    //密码编码器,对比账号密码前的密码加密规则,使用自定义的密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return new PasswordEncoderImpl();
    }

    //权限提供者
    @Bean
    public AuthenticationProvider authenticationProvider() {
    
    
        log.info("***authenticationProvider***");
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        //对默认的UserDetailsService进行覆盖
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder);
        return authenticationProvider;
    }

    /**
     * 提供User的 bean
     *
     * @auther Czw
     * @date 2020/3/16 0016 上午 11:31
     */
    @Bean
    public User getUser() {
    
    
        return new User();
    }




    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        //基于数据库查数据
        log.info("***WebSecurityConfig.configure()基于数据库查数据***");
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }


    //从数据库中动态获取后添加
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        log.info("***WebSecurityConfig.configure()拦截机制***");
        AuthenticationProvider authenticationProvider = authenticationProvider();
        http.authenticationProvider(authenticationProvider)
                .httpBasic()

                //未登录时,进行json格式的提示,不用单独写一个又一个的类
                .authenticationEntryPoint((request, response, authException) -> {
    
    
                    response.setContentType("application/json;charset=utf-8");
                    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    PrintWriter out = response.getWriter();
                    out.write(objectMapper.writeValueAsString(new ResponseResult<>(302, "未登录或已过期,请重新登录")));
                    out.flush();
                    out.close();
                })

                .and()
                .authorizeRequests()
                .anyRequest().authenticated().withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor())

                .and()
                .formLogin() //使用自带的登录
                .permitAll()
                //登录失败,返回json
                .failureHandler((request, response, ex) -> {
    
    
                    response.setContentType("application/json;charset=utf-8");
                    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                    PrintWriter out = response.getWriter();
                    ResponseResult responseResult = new ResponseResult<String>();
                    responseResult.setState(4004);
                    responseResult.setSuccess(false);
                    if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
    
    
                        responseResult.setMessage("用户名或密码错误");
                    } else if (ex instanceof DisabledException) {
    
    
                        responseResult.setMessage("账号被禁用");
                    } else {
    
    
                        responseResult.setMessage("登录失败");
                    }
                    out.write(objectMapper.writeValueAsString(responseResult));
                    out.flush();
                    out.close();
                })

                //登录成功,返回json
                .successHandler((request, response, authentication) -> {
    
    
                    sendSuccessResponse(response, authentication);
                })
                .and()
                .exceptionHandling()

                //没有权限,返回json
                .accessDeniedHandler((request, response, ex) -> {
    
    
                    response.setContentType("application/json;charset=utf-8");
                    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    PrintWriter out = response.getWriter();
                    out.write(objectMapper.writeValueAsString(new ResponseResult<>(403, "用户权限不足")));
                    out.flush();
                    out.close();
                })
                .and()
                .logout()
                //退出成功,返回json
                .logoutSuccessHandler((request, response, authentication) -> {
    
    
                    sendLogoutResponse(response, authentication);
                })
                .permitAll();
        //开启跨域访问
        http.cors().disable();
        //开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误
        http.csrf().disable();

    }


    /**
     * 忽略拦截的路径
     *
     * @param web
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
    
    
        super.configure(web);
        // 设置拦截忽略url - 会直接过滤该url - 将不会经过Spring Security过滤器链
        web.ignoring().antMatchers("/ignore");
        // 设置拦截忽略文件夹,可以对静态资源放行
        web.ignoring().antMatchers("/css/**", "/js/**");
    }


    private void sendSuccessResponse(HttpServletResponse response, Authentication authentication) throws IOException {
    
    
        response.setContentType("application/json;charset=utf-8");
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        PrintWriter out = response.getWriter();
        String mobile = userDetails.getUsername();
        String token = JwtTokenUtil.createToken(mobile);
        //将 用户信息 & 权限信息 放入到Redis中
        redisOperator.set(RedisOperator.USER_INFO_SUFFIX + mobile, JsonUtil.objectToJson(userDetails));
        List<String> permissionList = new ArrayList<>();
        for (GrantedAuthority authority : userDetails.getAuthorities()) {
    
    
            permissionList.add(authority.getAuthority());
        }
        redisOperator.set(RedisOperator.USER_PERMISSION + mobile, JsonUtil.objectToJson(permissionList));
        out.write(objectMapper.writeValueAsString(new ResponseResult<>(token)));
        out.flush();
        out.close();
    }


    private void sendLogoutResponse(HttpServletResponse response, Authentication authentication) throws IOException {
    
    
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        String mobile = userDetails.getUsername();
        redisOperator.delByKey(RedisOperator.USER_INFO_SUFFIX + mobile);
        redisOperator.delByKey(RedisOperator.USER_PERMISSION + mobile);
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(objectMapper.writeValueAsString(new ResponseResult<>(200, "系统登出成功")));
        out.flush();
        out.close();
    }

    /**
     * 自定义 FilterSecurityInterceptor  ObjectPostProcessor 以替换默认配置达到动态权限的目的
     *
     * @return ObjectPostProcessor
     */
    private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor() {
    
    
        return new ObjectPostProcessor<FilterSecurityInterceptor>() {
    
    
            @Override
            public <O extends FilterSecurityInterceptor> O postProcess(O object) {
    
    
                object.setAccessDecisionManager(accessDecisionManager);
                object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
                return object;
            }
        };
    }
}

由于登录成功、失败等全部写在里面所以代码有点多。分析一下,其实重要的代码并不多,只有不到十行。无法进入的原因是没有配置
.authorizeRequests().anyRequest().authenticated().withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor())
在这里插入图片描述
我是因为没有配置withObjectPostProcessor参数,所以就一直无法执行代码

问题解决

添加withObjectPostProcessor参数,并且添加一个filterSecurityInterceptorObjectPostProcessor()方法,贴一下这个方法的内容,其实就是将两个实现类设置到ObjectPostProcessor中


    @Autowired
    private AccessDecisionManager accessDecisionManager;

    @Autowired
    private FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource;
    
    /**
     * 自定义 FilterSecurityInterceptor  ObjectPostProcessor
     *  以替换默认配置达到动态权限的目的
     *
     * @return ObjectPostProcessor
     */
    private ObjectPostProcessor<FilterSecurityInterceptor> filterSecurityInterceptorObjectPostProcessor() {
    
    
        return new ObjectPostProcessor<FilterSecurityInterceptor>() {
    
    
            @Override
            public <O extends FilterSecurityInterceptor> O postProcess(O object) {
    
    
                object.setAccessDecisionManager(accessDecisionManager);
                object.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
                return object;
            }
        };
    }

尾声:此时问题已经得到解决,两个类中的方法能够执行到。其中,上面的ObjectPostProcessor也可以使用实现类的方法实现。如下,实现ObjectPostProcessor<FilterSecurityInterceptor>接口重写postProcess方法,个人认为没什么问题,但在启动的时候总是报错NoUniqueBeanDefinitionException(不是唯一的bean),在这也没时间去找原因,用的是上面内部类的方式

package com.luntek.security.springsecurity.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.stereotype.Component;

/**
 * @Date 2020/3/16 0016 下午 3:45
 * @Created by Czw
 */
@Qualifier("mySecurityObjectPostProcessor")
@Component
public class SecurityObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
    
    
    @Override
    public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
    
    
        fsi.setAccessDecisionManager(new CustomAccessDecisionManager()); //权限决策处理类
        fsi.setSecurityMetadataSource(new CustomFilterSecurityMetadataSource()); //路径(资源)拦截处理
        return fsi;
    }
}


lz在写一个springsecurity+jwt+springboot的user、role、permission、两张关系表的全部基于数据库的Demo,如果需要的话可以私聊我关注我csdn,后面会发出来。花了一天多的时间找这个问题,点个赞吧,嘿嘿

猜你喜欢

转载自blog.csdn.net/qq_42910468/article/details/104939947
今日推荐