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