zoukankan      html  css  js  c++  java
  • SpringBoot SpringSecurity4整合,灵活权限配置,弃用注解方式.

    SpringSecurity 可以使用注解对方法进行细颗粒权限控制,但是很不灵活,必须在编码期间,就已经写死权限

    其实关于SpringSecurity,大部分类都不需要重写,需要的只是妥善的配置.

    每次修改权限以后,需要让MetaDataSource刷新 资源-权限 的MAP,这里应该需要做一些处理,或者优化.

    这里实现,可以在后台随时开启关闭权限,不需要硬编码写死.而且资源的RequestMapping,可以是有多个地址

    可以根据角色分配权限,也可以精确到为每一个用户分配权限,模块,或者方法.

    这样比较灵活,但是UI会很复杂,用户也不好理解

    资源注解:注解使用在控制器类,或者方法中.注解在类中,粗颗粒控制,注解在方法中细颗粒

    /**
     * Created by ZhenWeiLai on on 2016-10-16.
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface AclResc {
        int id();//ACLResource 因为特殊原因不使用 id 自动增长,所以必须自定义ID ,并且不能重复
        String code();
        String name();
        String homePage() default "";
        boolean isMenu() default true;
    }

    注解在类:

    @AclResc(id = 5000,code = "aclRescUser", name = AclRescUserController.MODULE_NAME,homePage = AclRescUserController.HOME_PAGE)
    public class AclRescUserController extends BaseController<AclRescUser> 

    注解在方法:

        @RequestMapping(value = "/list",method = RequestMethod.GET)
        @AclResc(id = 5001,code = "list",name = "用户资源列表")
        public ResultDataDto list(){
    
        }

    系统完全启动后,更新资源信息:

    /**
     * Created by ZhenWeiLai on on 2016-10-16.
     * SpringBoot 启动完毕做些事情
     */
    @Component
    public class ApplicationStartup  implements CommandLineRunner {
    
        @Resource
        private AclResourceService aclResourceService;
        @Resource
        private AclAuthService aclAuthService;
    
        @Resource
        private RequestMappingHandlerMapping requestMappingHandlerMapping;
    
        @Resource
        private MySecurityMetadataSource securityMetadataSource;
    
    
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        @Override
        public void run(String... strings) throws Exception {
            /**
             * 初始化资源,保存到数据库
             */
            initModule();
            /**
             * Spring Security 需要的资源-权限
             */
            securityMetadataSource.doLoadResourceDefine();
        }
    
    
        /**
         * 读取所有Controller包括以内的方法
         */
        private void initModule() {
            /**
             * 模块 - 方法map
             */
            Map<AclResource, List<AclResource>> resourcesMap = new HashMap<>();
            Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
            for (RequestMappingInfo info : map.keySet()) {
                AclResc moduleAclResc = map.get(info).getBeanType().getAnnotation(AclResc.class);
                if (moduleAclResc != null) {
                    if (StringUtils.isBlank(moduleAclResc.homePage()))
                        throw new RuntimeException("使用:" + AclResc.class.getName() + " 注解类时,请配置 homePage ");
                    Class<?> aclResourceClass = map.get(info).getBeanType();
                    RequestMapping moduleMapping = aclResourceClass.getAnnotation(RequestMapping.class);
                    AclResource moduleResc = new AclResource(moduleAclResc.id(), moduleAclResc.code(), moduleAclResc.name(), Arrays.toString(moduleMapping.value()), AclResource.Type.MODULE.getCode(), moduleAclResc.homePage(), moduleAclResc.isMenu());
                    if (moduleMapping != null) {
                        List<AclResource> resources;
                        AclResource methodResc;
                        Method method = map.get(info).getMethod();
                        AclResc methodAclResc = method.getAnnotation(AclResc.class);
                        if (methodAclResc != null) {
                            methodResc = new AclResource(methodAclResc.id(), methodAclResc.code(), methodAclResc.name(), info.getPatternsCondition().toString().replace("||", Delimiter.COMMA.getDelimiter()), AclResource.Type.METHOD.getCode(), null);
                            if (resourcesMap.get(moduleResc) == null) {
                                resources = new ArrayList<>();
                                resources.add(methodResc);
                                resourcesMap.put(moduleResc, resources);
                            } else {
                                resourcesMap.get(moduleResc).add(methodResc);
                            }
                        }
                    }
                }
            }
            addModule(resourcesMap);
        }
    
        /**
         * 检查新模块,添加到数据库,并更新视图的模块ID
         *
         * @param resourcesMap
         */
        private void addModule(Map<AclResource, List<AclResource>> resourcesMap) {
            for (Map.Entry<AclResource, List<AclResource>> item : resourcesMap.entrySet()) {
                AclResource resultResc = aclResourceService.findEntityById(item.getKey().getId());
                //如果模块是新模块,那么新增到数据库
                if (resultResc == null) {
                    aclResourceService.addEntity(item.getKey());
                    List<AclResource> resources = item.getValue();
                    for (AclResource resc : resources) {
                        resc.setModuleId(item.getKey().getId());
                    }
                } else {
                    //如果已存在模块,那么更新需要的字段
                    aclResourceService.updateEntity(item.getKey());
                    List<AclResource> resources = item.getValue();
                    for (AclResource methodResc : resources) {
                        //方法模块CODE 根据 模块CODE + 方法CODE 生成
                        methodResc.setCode(item.getKey().getCode() + "_" + methodResc.getCode());
                        methodResc.setModuleId(resultResc.getId());
                        AclResource oringinalMethodResc = aclResourceService.findEntityById(methodResc.getId());
                        if (oringinalMethodResc != null) {
                            //RequestMapping可能被修改,所以这里要做一次更新
                            aclResourceService.updateEntity(methodResc);
                            //同时code也可能被更改,所以更新权限code
                            aclAuthService.updateCodeByRescId(methodResc.getCode(), methodResc.getId());
                        } else {
                            aclResourceService.addEntity(methodResc);
                        }
                    }
                }
            }
        }
    
    }

    构建权限菜单:

    /**
         * 根据用户权限构建菜单
         */
        @Override
        public Map<AclMenu, List<AclResource>> getAclUserMenus() {
    
            //创建完整的菜单,然后删除没有权限的菜单
            Map<AclMenu, List<AclResource>> userMenuModuleMap = findAclMenuModuleMap();
            //获取资源/权限集
            Map<String, Collection<ConfigAttribute>> moduleMap = securityMetadataSource.getModuleMap();
            for (String path : moduleMap.keySet()) {
                //如果没有权限
                if (!SecurityUtil.hastAnyAuth(moduleMap.get(path))) {
                    Iterator<AclMenu> userMenuModuleMapKey = userMenuModuleMap.keySet().iterator();
                    while (userMenuModuleMapKey.hasNext()) {
                        AclMenu key = userMenuModuleMapKey.next();
                        List<AclResource> modules = userMenuModuleMap.get(key);
                        if (modules.isEmpty()) {
                            userMenuModuleMapKey.remove();
                            continue;
                        }
                        Iterator<AclResource> aclResourceIterator = modules.iterator();
                        while (aclResourceIterator.hasNext()) {
                            String rescPath = aclResourceIterator.next().getPath();
                            String[] pathArr = rescPath.substring(1, rescPath.length() - 1).split(Delimiter.COMMA.getDelimiter());
                            for (String item : pathArr) {
                                if (item.equals(path)) {
                                    //从菜单模块中删除
                                    aclResourceIterator.remove();
                                    //如果模块为空
                                    if (modules.isEmpty()) {
                                        //删除菜单
                                        userMenuModuleMapKey.remove();
                                    }
                                }
                            }
    
                        }
                    }
                }
            }
            return userMenuModuleMap;
        }
    FilterInvocationSecurityMetadataSource:
    /**
     * Created by ZhenWeiLai on 2016-10-16.
     */
    @Component("securityMetadataSource")
    public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    
        private static Map<String, Collection<ConfigAttribute>> moduleMap = null;
    
        private static Map<String, Collection<ConfigAttribute>> methodMap = null;
    
        @Resource
        private AclResourceService aclResourceService;
    
        @Resource
        private AclRescRoleService aclRescRoleService;
    
        @Resource
        private AclRoleService aclRoleService;
    
        @Resource
        private AclAuthService aclAuthService;
    
        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            Collection<ConfigAttribute> collection;
            collection = getAttributesHandler(methodMap, object);
            if (collection != null)
                return collection;
            collection = getAttributesHandler(moduleMap, object);
            return collection;
        }
    
        /**
         * 处理方法
         *
         * @param map
         * @return
         */
        private Collection<ConfigAttribute> getAttributesHandler(Map<String, Collection<ConfigAttribute>> map, Object object) {
            HttpServletRequest request = ((FilterInvocation) object).getRequest();
            Iterator var3 = map.entrySet().iterator();
            Map.Entry entry;
            do {
                if (!var3.hasNext()) {
                    return null;
                }
                entry = (Map.Entry) var3.next();
    
            } while (!(new AntPathRequestMatcher(entry.getKey().toString())).matches(request));
            return (Collection) entry.getValue();
        }
    
    
        //4
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            Set<ConfigAttribute> allAttributes = new HashSet();
            Map<String, Collection<ConfigAttribute>> all = new HashMap<>(this.moduleMap);
            all.putAll(this.methodMap);
            Iterator var2 = all.entrySet().iterator();
            while (var2.hasNext()) {
                Map.Entry<String, Collection<ConfigAttribute>> entry = (Map.Entry) var2.next();
                allAttributes.addAll(entry.getValue());
            }
    
            return allAttributes;
        }
    
        //3
        @Override
        public boolean supports(Class<?> clazz) {
            return FilterInvocation.class.isAssignableFrom(clazz);
        }
    
    
        @Transactional(readOnly = true)
        private void loadResourceDefine() {
            loadModuleResources();
            loadMethodResources();
        }
    
    
        /**
         * 提供一个外部使用方法.获取module权限MAP;
         *
         * @return
         */
        public Map<String, Collection<ConfigAttribute>> getModuleMap() {
            Map<String, Collection<ConfigAttribute>> map = new HashMap<>(moduleMap);
            return map;
        }
    
    
        /**
         * 提供外部方法让Spring环境启动完成后调用
         */
        public void doLoadResourceDefine() {
            loadResourceDefine();
        }
    
    
        /**
         * 读取模块资源
         */
        private void loadModuleResources() {
            /**
             * 查询模块资源权限,配置模块权限验证
             */
            List<AclResource> aclResources = aclResourceService.findAllModule();
    
            //模块资源为KEY,角色为Value 的list
            moduleMap = new HashMap<>();
            for (AclResource module : aclResources) {
                /**
                 * 加载所有模块资源
                 */
                List<AclRescRole> aclRescRoles = aclRescRoleService.findByRescId(module.getId());
    
    
                /**
                 * 无论如何超级管理员拥有所有权限
                 */
                stuff(new SecurityConfig(SecurityUtil.ADMIN), moduleMap, module.getPath());
    
                for (AclRescRole aclRescRole : aclRescRoles) {
                    Integer roleId = aclRescRole.getRoleId();//角色ID
                    String roleCode = aclRoleService.findEntityById(roleId).getCode();//角色编码
                    stuff(new SecurityConfig(roleCode.toUpperCase()), moduleMap, module.getPath());
                }
            }
        }
    
    
        /**
         * 读取精确方法权限资源
         */
        private void loadMethodResources() {
            /**
             * 因为只有权限控制的资源才需要被拦截验证,所以只加载有权限控制的资源
             */
            //方法资源为key,权限编码为
            methodMap = new HashMap<>();
            List<Map<String, String>> pathAuths = aclAuthService.findPathCode();
            for (Map pathAuth : pathAuths) {
                String path = pathAuth.get("path").toString();
                ConfigAttribute ca = new SecurityConfig(pathAuth.get("code").toString().toUpperCase());
                stuff(ca, methodMap, path);
            }
        }
    
        private void stuff(ConfigAttribute ca, Map<String, Collection<ConfigAttribute>> map, String path) {
    
            String[] pathArr = path.substring(1, path.length() - 1).split(Delimiter.COMMA.getDelimiter());
            for (String item : pathArr) {
                Collection<ConfigAttribute> collection = map.get(item + "/**");
                if (collection != null) {
                    collection.add(ca);
                } else {
                    collection = new ArrayList<>();
                    collection.add(ca);
                    String pattern = StringUtils.trimToEmpty(item) + "/**";
                    map.put(pattern, collection);
                }
            }
        }
    }

    最后:

    /**
     * Created by ZhenWeiLai on on 2016-10-16.
     * <p>
     * 三种方法级权限控制
     * <p>
     * 1.securedEnabled: Spring Security’s native annotation
     * 2.jsr250Enabled: standards-based and allow simple role-based constraints
     * 3.prePostEnabled: expression-based
     */
    @EnableWebSecurity
    //@EnableGlobalMethodSecurity(prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Resource
        private UserDetailsService userDetailsService;
    
        @Resource
        private MySecurityMetadataSource securityMetadataSource;
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/css/**");
            web.ignoring().antMatchers("/images/**");
            web.ignoring().antMatchers("/js/**");
    //忽略登录界面
            web.ignoring().antMatchers("/login");
    
            //注册地址不拦截
    //        web.ignoring().antMatchers("/reg");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //解决不允许显示在iframe的问题
            http.headers().frameOptions().disable();
    
            http.addFilterAt(usernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    
            http.authorizeRequests().anyRequest().fullyAuthenticated();
    
            //自定义过滤器
            MyFilterSecurityInterceptor filterSecurityInterceptor = new MyFilterSecurityInterceptor(securityMetadataSource,accessDecisionManager(),authenticationManagerBean());
            //在适当的地方加入
            http.addFilterAt(filterSecurityInterceptor,FilterSecurityInterceptor.class);
    
            http.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).and().logout().logoutUrl("/logout").logoutSuccessUrl("/login").and().exceptionHandling().accessDeniedPage("/accessDenied");
    
            // 关闭csrf
            http.csrf().disable();
    
            //session管理
            //session失效后跳转
            http.sessionManagement().invalidSessionUrl("/login");
            //只允许一个用户登录,如果同一个账户两次登录,那么第一个账户将被踢下线,跳转到登录页面
            http.sessionManagement().maximumSessions(1).expiredUrl("/login");
        }
    
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth)
                throws Exception {
            // 自定义UserDetailsService,设置加密算法
            auth.userDetailsService(userDetailsService);
    //.passwordEncoder(passwordEncoder())
            //不删除凭据,以便记住用户
            auth.eraseCredentials(false);
        }
    
        UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
            UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
            usernamePasswordAuthenticationFilter.setPostOnly(true);
            usernamePasswordAuthenticationFilter.setAuthenticationManager(this.authenticationManager());
            usernamePasswordAuthenticationFilter.setUsernameParameter("name_key");
            usernamePasswordAuthenticationFilter.setPasswordParameter("pwd_key");
            usernamePasswordAuthenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/checkLogin", "POST"));
            usernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler());
            usernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
            return usernamePasswordAuthenticationFilter;
        }
    
    //    @Bean
    //    public LoggerListener loggerListener() {
    //        System.out.println("org.springframework.security.authentication.event.LoggerListener");
    //        return new LoggerListener();
    //    }
    //
    //    @Bean
    //    public org.springframework.security.access.event.LoggerListener eventLoggerListener() {
    //        System.out.println("org.springframework.security.access.event.LoggerListener");
    //        return new org.springframework.security.access.event.LoggerListener();
    //    }
    
        /**
         * 投票器
         */
        private AbstractAccessDecisionManager accessDecisionManager() {
            List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList();
            decisionVoters.add(new AuthenticatedVoter());
            decisionVoters.add(new RoleVoter());//角色投票器,默认前缀为ROLE_
            RoleVoter AuthVoter = new RoleVoter();
            AuthVoter.setRolePrefix("AUTH_");//特殊权限投票器,修改前缀为AUTH_
            decisionVoters.add(AuthVoter);
            AbstractAccessDecisionManager accessDecisionManager = new AffirmativeBased(decisionVoters);
            return accessDecisionManager;
        }
    
        @Override
        public AuthenticationManager authenticationManagerBean() {
            AuthenticationManager authenticationManager = null;
            try {
                authenticationManager = super.authenticationManagerBean();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return authenticationManager;
        }
    
    
        /**
         * 验证异常处理器
         *
         * @return
         */
        private SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler() {
            return new SimpleUrlAuthenticationFailureHandler("/getLoginError");
        }
    
    
    //    /**
    //     * 表达式控制器
    //     *
    //     * @return
    //     */
    //    private DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
    //        DefaultWebSecurityExpressionHandler webSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
    //        return webSecurityExpressionHandler;
    //    }
    
    //    /**
    //     * 表达式投票器
    //     *
    //     * @return
    //     */
    //    private WebExpressionVoter webExpressionVoter() {
    //        WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
    //        webExpressionVoter.setExpressionHandler(webSecurityExpressionHandler());
    //        return webExpressionVoter;
    //    }
    
        // Code5  官方推荐加密算法
    //    @Bean("passwordEncoder")
    //    public BCryptPasswordEncoder passwordEncoder() {
    //        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    //        return bCryptPasswordEncoder;
    //    }
    
    //    // Code3----------------------------------------------
    
    
        /**
         * 登录成功后跳转
         * 如果需要根据不同的角色做不同的跳转处理,那么继承AuthenticationSuccessHandler重写方法
         *
         * @return
         */
        private SimpleUrlAuthenticationSuccessHandler authenticationSuccessHandler() {
            return new SimpleUrlAuthenticationSuccessHandler("/loginSuccess");
        }
    
    
    
        /**
         * Created by ZhenWeiLai on on 2016-10-16.
         */
        public static class MyFilterSecurityInterceptor extends FilterSecurityInterceptor {
    
            public MyFilterSecurityInterceptor(FilterInvocationSecurityMetadataSource securityMetadataSource, AccessDecisionManager accessDecisionManager, AuthenticationManager authenticationManager){
                this.setSecurityMetadataSource(securityMetadataSource);
                this.setAccessDecisionManager(accessDecisionManager);
                this.setAuthenticationManager(authenticationManager);
    
            }
        }
    
    
    }
  • 相关阅读:
    selenium实战脚本集(2)——简单的知乎爬虫
    selenium实战脚本集(1)——新浪微博发送QQ每日焦点
    使用swift和rails来实现ios账号系统
    一段js代码
    你应该学会使用的5个ruby方法
    小而美的ghost driver
    还没被玩坏的robobrowser(8)——robobrowser的实现原理
    还没被玩坏的robobrowser(7)——表单操作
    还没被玩坏的robobrowser(6)——follow_link
    还没被玩坏的robobrowser(5)——Beautiful Soup的过滤器
  • 原文地址:https://www.cnblogs.com/sweetchildomine/p/5998659.html
Copyright © 2011-2022 走看看