上次写了spring security 2.0 的简单配置使用 ,这里再做一下补充。
method级别的权限控制可以通过spring aop来实现。
pointcut:
<aop:config> <aop:pointcut id="securityPointcut" expression="execution(** org.catspaw.ss2test1.service.*Service+.*(..))" /> <aop:advisor advice-ref="accessDeniedInterceptor" pointcut-ref="securityPointcut" order="0" /> <aop:advisor advice-ref="methodSecurityInterceptor" pointcut-ref="securityPointcut" order="1" /> </aop:config>
当调用org.catspaw.ss2test1.service包下所有Service结尾的类的任何方法时,通知accessDeniedInterceptor和methodSecurityInterceptor两个Interceptor。
MethodSecurityInterceptor,作用和FilterSecurityInterceptor差不多,如果授权不通过,也会抛异常。
<bean id="methodSecurityInterceptor" class="org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager" /> <property name="accessDecisionManager" ref="accessDecisionManager" /> <property name="objectDefinitionSource" ref="databaseMethodDefinitionSource" /> </bean>
DefaultMethodDefinitionSource,作用类似于DefaultFilterInvocationDefinitionSource
<bean id="databaseMethodDefinitionSource" class="org.catspaw.ss2test1.security.DefaultMethodDefinitionSource"> <constructor-arg index="0"> <bean class="org.catspaw.ss2test1.security.SimpleAspectJMethodMatcher" /> </constructor-arg> <constructor-arg index="1"> <bean class="org.catspaw.ss2test1.security.MethodMapFactoryBean"> <property name="resourceDao" ref="resourceDao" /> </bean> </constructor-arg> </bean>
实现:
package org.catspaw.ss2test1.security;
import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.intercept.method.AbstractFallbackMethodDefinitionSource;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
/**
* 从数据库获取被访问的资源所需要的权限标识符
* 这里只处理Method(方法)资源
*
* @author 孙宁振
*/
public class DefaultMethodDefinitionSource extends
AbstractFallbackMethodDefinitionSource {
private Matcher<String, Method> methodMatcher;
private LinkedHashMap<String, ConfigAttributeDefinition> methodMap = new LinkedHashMap<String, ConfigAttributeDefinition>();
public DefaultMethodDefinitionSource(Matcher<String, Method> methodMatcher,
LinkedHashMap<String, ConfigAttributeDefinition> methodMap) {
this.methodMatcher = methodMatcher;
this.methodMap = methodMap;
}
public Collection getConfigAttributeDefinitions() {
return Collections.unmodifiableCollection(getMethodMap().values());
}
@Override
protected ConfigAttributeDefinition findAttributes(Method method,
Class targetClass) {
Set<Entry<String, ConfigAttributeDefinition>> set = getMethodMap()
.entrySet();
for (Entry<String, ConfigAttributeDefinition> entry : set) {
String pattern = entry.getKey();
boolean matched = getMethodMatcher().match(pattern, method);
if (matched) {
return entry.getValue();
}
}
return null;
}
@Override
protected ConfigAttributeDefinition findAttributes(Class clazz) {
return null;
}
public Map<String, ConfigAttributeDefinition> getMethodMap() {
return methodMap;
}
public Matcher<String, Method> getMethodMatcher() {
return methodMatcher;
}
}
SimpleAspectJMethodMatcher ,Matcher接口省略
package org.catspaw.ss2test1.security;
import java.lang.reflect.Method;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.ShadowMatch;
/**
* Method匹配器实现
* 用AspectJ PointCut表达式匹配目标方法
*
* @author 孙宁振
*/
public class SimpleAspectJMethodMatcher implements Matcher<String, Method> {
private PointcutParser pointcutParser = PointcutParser
.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
public boolean match(String pattern, Method method) {
pattern = "execution(" + pattern + ")";
PointcutExpression pe = pointcutParser.parsePointcutExpression(pattern);
ShadowMatch match = pe.matchesMethodExecution(method);
return match.alwaysMatches();
}
public void setPointcutParser(PointcutParser pointcutParser) {
this.pointcutParser = pointcutParser;
}
public PointcutParser getPointcutParser() {
return pointcutParser;
}
}
MethodMapFactoryBean ,作用类似于RequestMapFactoryBean:
package org.catspaw.ss2test1.security;
import java.util.LinkedHashMap;
import java.util.List;
import org.catspaw.ss2test1.dao.ResourceDao;
import org.catspaw.ss2test1.model.Resource;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.security.ConfigAttributeDefinition;
public class MethodMapFactoryBean implements FactoryBean {
private static final String RESOURCE_TYPE = "METHOD";
private ResourceDao resourceDao;
private LinkedHashMap<String, ConfigAttributeDefinition> methodMap;
public void init() {
List<Resource> resources = resourceDao.find(RESOURCE_TYPE);
methodMap = new LinkedHashMap<String, ConfigAttributeDefinition>();
for (Resource resource : resources) {
ConfigAttributeDefinition definition = new ConfigAttributeDefinition(
resource.getAuthority());
methodMap.put(resource.getResourceString(), definition);
}
}
public Object getObject() throws Exception {
if (methodMap == null) {
init();
}
return methodMap;
}
public Class getObjectType() {
return LinkedHashMap.class;
}
public boolean isSingleton() {
return true;
}
public ResourceDao getResourceDao() {
return resourceDao;
}
public void setResourceDao(ResourceDao resourceDao) {
this.resourceDao = resourceDao;
}
}
另外,AccessDeniedInterceptor 的作用是在授权失败,抛出异常后,做一些其他的处理工作,需要实现ThrowsAdvice 接口。比如:
package org.catspaw.ss2test1.security;
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.security.AccessDeniedException;
/**
* 控制权限的AOP拦截器抛出AccessDeniedException后,进行相应后续处理
*
* @author 孙宁振
*
*/
public class AccessDeniedInterceptor implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target,
AccessDeniedException exception) {
System.out.println("access denied.....");
//TODO 通过DatabaseMethodDefinitionSource拒绝访问后的处理
}
}
注意:accessDeniedInterceptor的order需要在methodSecurityInterceptor的order之前,否则methodSecurityInterceptor抛出异常后,accessDeniedInterceptor将不会执行。
HelloService
<bean id="helloService" class="org.catspaw.ss2test1.service.impl.HelloServiceImpl"> </bean>
接口:
package org.catspaw.ss2test1.service;
public interface HelloService {
void hello();
void hello(String name);
}
实现:
package org.catspaw.ss2test1.service.impl;
import org.catspaw.ss2test1.service.HelloService;
public class HelloServiceImpl implements HelloService {
public void hello() {
System.out.println("hello.......");
}
public void hello(String name) {
System.out.println("hello " + name);
}
}
测试用的Servlet
package org.catspaw.ss2test1.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.catspaw.ss2test1.service.HelloService;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
public class HelloServlet extends HttpServlet {
private HelloService helloService;
@Override
public void init() throws ServletException {
super.init();
ApplicationContext ctx = WebApplicationContextUtils
.getRequiredWebApplicationContext(getServletContext());
helloService = (HelloService) ctx.getBean("helloService");
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = request.getParameter("name");
if (name != null) {
helloService.hello(name);
} else {
helloService.hello();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
附demo
所用到的jar:spring2.0.7,hibernate3.2.5&jpa,spring-security2.0.3,ehcache1.3
数据库:mysql5,建好相应数据库后,把<prop key="hibernate.hbm2ddl.auto">none</prop>的none改为create即可在运行时自动建表
登录入口为/spring_security_login
用户名 密码
admin admin
aaa aaa
bbb bbb