关于数据拦截列表,SS官方提供的例子是基于配置的简单实现,例如以下代码:
1 <http> 2 <intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/> 3 <intercept-url pattern="/**" access="ROLE_USER" /> 4 <form-login login-page=‘/login.jsp‘/> 5 </http>
这样的硬性代码加入配置自然不能满足复杂的业务需求,最好的实现就是将所有的数据使用数据库配置,然后可以自由的添加删除而不影响程序。
数据资源列表一般应用在请求资源的时候,根据访问的资源,来获取当前资源所需要的权限列表,然后根据用户的权限,判断是否允许通过。所以数据资源列表在访问时候相当于数据源。我们所有的请求应当都能在数据资源列表中找到。
自定义数据资源列表应该实现FilterInvocationSecurityMetadataSource接口,ss默认实现是DefaultFilterInvocationSecurityMetadataSource。它有三个实现方法,分别是getAllConfigAttributes(),返回全部的配置集合。getAttributes(Object object),根据当前访问的对象返回相应的配置。supports(Class<?> clazz),根据当前对象决定是否支持该类操作。此处重点是根据当前请求资源获取配置,即getAttributes(Object object)方法。该方法是根据object返回一个该object对应的配置,通常情况下,object是一个FilterInvocation,其中可以获取到url资源,那么可以理解为该url有哪些配置(权限)。所以得根据url存储权限,用map最好不过了。
1 public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource { 2 3 @Autowired 4 private ResourceService resourceService; 5 6 private Map<String, Collection<ConfigAttribute>> resource; 7 8 private void loadResource() { 9 resource = new LinkedHashMap<String, Collection<ConfigAttribute>>(); 10 Map<String, List<Privilege>> resource_privilege = resourceService.getResourcePrivilege(); 11 Iterator<String> iterator = resource_privilege.keySet().iterator(); 12 while (iterator.hasNext()) { 13 String url = (String) iterator.next(); 14 List<Privilege> privileges = resource_privilege.get(url); 15 Collection<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>(); 16 for (Privilege privilege : privileges) { 17 ConfigAttribute attribute = new SecurityConfig(privilege.getId().toString()); 18 attributes.add(attribute); 19 } 20 resource.put(url, attributes); 21 } 22 } 23 24 @Override 25 public Collection<ConfigAttribute> getAllConfigAttributes() { 26 return null; 27 } 28 29 @Override 30 public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { 31 // 每次从数据库加载当前所请求的资源 32 System.out.println("请求 " + object + " 资源"); 33 loadResource(); 34 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); 35 Iterator<String> iterator = resource.keySet().iterator(); 36 while (iterator.hasNext()) { 37 String url = (String) iterator.next(); 38 AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(url); 39 if (antPathRequestMatcher.matches(request)) { 40 System.out.println("资源配置的权限id列表:" + resource.get(url)); 41 return resource.get(url); 42 } 43 } 44 return null; 45 } 46 47 @Override 48 public boolean supports(Class<?> clazz) { 49 return FilterInvocation.class.isAssignableFrom(clazz); 50 } 51 52 }
在38行代码中,使用了一个AntPathRequestMatcher作为url的匹配规则,AntPathRequestMatcher的匹配规则如下:
1. * 任意匹配多个字符,但不能跨越目录
2. ** 任意匹配多个字符,可以跨越目录
3. ? 任意匹配一个字符
通常情况下AntPathRequestMatcher可以满足我们的要求,如果不能满足复杂的业务,可以使用RegexRequestMatcher,它支持正则表达式来进行匹配。
在getAttributes方法中,调用了loadResource方法,在loadResource方法中,请求了存放数据资源的数据表。该表和权限关联起来,entity如下:
1 @Entity 2 public class MetaResource implements Serializable { 3 4 private static final long serialVersionUID = 1L; 5 6 @Id 7 @GeneratedValue 8 private Long id; 9 10 @Column 11 private String url; 12 13 @Column 14 private String resourceName; 15 16 @Column 17 private Long sequence=0L; 18 19 @ManyToMany(fetch=FetchType.EAGER) 20 @LazyCollection(LazyCollectionOption.FALSE) 21 @JoinTable(name="resource_privilege", 22 joinColumns={@JoinColumn(name="s_id",referencedColumnName="id")}, 23 inverseJoinColumns={@JoinColumn(name="p_id",referencedColumnName="id")}) 24 private List<Privilege> privileges; 25 26 public Long getId() { 27 return id; 28 } 29 30 public void setId(Long id) { 31 this.id = id; 32 } 33 34 public String getUrl() { 35 return url; 36 } 37 38 public void setUrl(String url) { 39 this.url = url; 40 } 41 42 public List<Privilege> getPrivileges() { 43 return privileges; 44 } 45 46 public void setPrivileges(List<Privilege> privileges) { 47 this.privileges = privileges; 48 } 49 50 public String getResourceName() { 51 return resourceName; 52 } 53 54 public void setResourceName(String resourceName) { 55 this.resourceName = resourceName; 56 } 57 58 public Long getSequence() { 59 return sequence; 60 } 61 62 public void setSequence(Long sequence) { 63 this.sequence = sequence; 64 } 65 }
在entity中,加入了一个sequence属性,该属性是为了记录url的循序的,等同于在http里配置的拦截url的循序。ss的url循序是特殊优先,也就是说那些单独需要设置权限或者有特殊权限的需要放在前面,因为不这样做,有可能在前一个url就被拦截,后面的根本访问不到,所以我们一般在http中配置的时候,会把登录等无权限的页面放在最前,以防止登录页面都不能访问。
获取权限资源的service方法如下,此处注意使用的LinkedHashMap以保障我们存入是数据不会乱序:
1 @SuppressWarnings("unchecked") 2 public Map<String, List<Privilege>> getResourcePrivilege(){ 3 String queryString ="SELECT $ FROM MetaResource $ order by $.sequence asc"; 4 Query query = em.createQuery(queryString); 5 // query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); 6 List<MetaResource> resources = query.getResultList(); 7 Map<String, List<Privilege>> results = new LinkedHashMap<String, List<Privilege>>(); 8 for (MetaResource metaResource : resources) { 9 results.put(metaResource.getUrl(), (List<Privilege>) metaResource.getPrivileges()); 10 } 11 return results; 12 }