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);

        }
    }


}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM