CustomFilterSecurityMetadataSourceおよびCustomAccessDecisionManagerのコードが実行されない

問題の説明:

SpringSecurityを使用してユーザーの権限を検証する場合、カスタム権限制御マネージャーは、決定(認証認証、オブジェクトオブジェクト、Collection <ConfigAttribute> configAttributes)メソッドとgetAttributes(オブジェクトオブジェクト)メソッドを常に実行できるわけではありません。
最初は、2つの実装クラスがSpringコンテナーに追加されていないと思いましたが、後でそれらのコンストラクターが追加されました。プロジェクトが開始されたときに、コンテナーに既にあることを示す構築が実行されました。


問題の原因

この問題は1日以上悩みました。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;
            }
        };
    }
}

ログインの成功と失敗はすべてそこに記述されているため、コードは少し多すぎます。分析は、実際には、多くの重要なコードはなく、10行未満です。アクセスできない理由は、設定
.authorizeRequests().anyRequest().authenticated().withObjectPostProcessor(filterSecurityInterceptorObjectPostProcessor())
ここに画像の説明を挿入
がないためです。withObjectPostProcessorパラメータが設定されていないため、コードを実行できませんでした。

問題が解決しました

withObjectPostProcessorパラメータを追加し、filterSecurityInterceptorObjectPostProcessor()メソッドを追加して、このメソッドのコンテンツをポストします。実際には、2つの実装クラスを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;
            }
        };
    }

エピローグ:この時点で問題は解決され、2つのクラスのメソッドを実行できます。その中でも、上記の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の2つのリレーショナルテーブルのデモを書いています。必要に応じて、非公開でチャットしたり、後で投稿するcsdnをフォローしたりできます。この問題を見つけるのに1日以上かかりました。気に入ってください。

おすすめ

転載: blog.csdn.net/qq_42910468/article/details/104939947