Acegi久负盛名,这个家伙是一个spring中广泛使用的认证和安全工具,最初由spring社区爱好者发起,目的是为spring应用提供一个安全服务,比如用户认证及授权等。后来spring官方觉得这个东西很不错,就收编了,并且在2006年发布了spring官方的1.0版本。虽然是基于Acegi,但springsecurity已经在原有基础上增加了很多新的特性进来。为了能够方便一窥Acegi的真容,我们通过一个basic模式来看下Acegi是如何来处理用户认证及授权工作。
1、配置安全所需过滤器org.acegisecurity.util.FilterChainProxy,填充 filterInvocationDefinitionSource 属性如下所示:
<bean id ="filterChainProxy" class= "org.acegisecurity.util.FilterChainProxy"> <property name ="filterInvocationDefinitionSource"> <value> PATTERN_TYPE_APACHE_ANT /**=basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor </value> </property> </bean>
注:默认情况下,在filterInvocationDefinitionSource属性中指明使用PATTERN_TYPE_APACHE_ANT,则说明,这里的配置信息是启用Apache Ant路径风格的URL匹配模式,FilterInvocationDefinitionSourceEditor会实例化PathBasedFilterInvocationDefinitionMap实例。如果这里没有指定则采用默认的正是表达式,此时RegExpBasedFilterInvocationDefinitionMap会被实例化。
FilterInvocationDefinitionSourceEditor在进行初始化过程中,acegi源码处理过程的片段代码如下:
if ((s == null) || "".equals(s)) {
// Leave target object empty
source.setDecorated(new RegExpBasedFilterInvocationDefinitionMap());
} else {
// Check if we need to override the default definition map
if (s.lastIndexOf(DIRECTIVE_PATTERN_TYPE_APACHE_ANT) != -1) {
source.setDecorated(new PathBasedFilterInvocationDefinitionMap());
if (logger.isDebugEnabled()) {
logger.debug(("Detected " + DIRECTIVE_PATTERN_TYPE_APACHE_ANT
+ " directive; using Apache Ant style path expressions"));
}
} else {
source.setDecorated(new RegExpBasedFilterInvocationDefinitionMap());
}
if (s.lastIndexOf(DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON) != -1) {
if (logger.isDebugEnabled()) {
logger.debug("Detected " + DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
+ " directive; Instructing mapper to convert URLs to lowercase before comparison");
}
source.setConvertUrlToLowercaseBeforeComparison(true);
}
另外需要说明的是,PathBasedFilterInvocationDefinitionMap和RegExpBasedFilterInvocationDefinitionMap都是接口FilterInvocationDefinitionSource的实现类。
FilterInvocationDefinitionSourceEditor在初始化athBasedFilterInvocationDefinitionMap和RegExpBasedFilterInvocationDefinitionMap类时,提供了2个常量用:
- DIRECTIVE_CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON:FilterInvocationDefinitionSourceEditor类通过该常量的设值情况判断是否对当前路径进行小写转换
- PATTERN_TYPE_APACHE_ANT:FilterInvocationDefinitionSourceEditor通过这个常量决定具体是采用正则模式还是ant路径风格模式。
我们这里根据执行顺序,指明了3个filter类过滤执行安全策略:basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
basicProcessingFilter:在Basic模式下,用户名和密码通过对称加密算法,将用户的登录信息存放在http请求的header信息中。服务器在收到浏览器发送来的验证请求后,将加密过的用户名密码通过Apache提供的commons-codec工具包中的org.apache.commons.codec.binary.Base64进行解码。
例如:发起一次请求验证通过后的http头摘要如下:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Encoding:gzip, deflate, sdch, br Accept-Language:zh-CN,zh;q=0.8 Authorization:Basic dGVzdDox Connection:keep-alive Cookie:JSESSIONID=A749345B4E56805343189AA5A1223655 Host:localhost:8080 Referer:http://localhost:8080/rest-common-acegi/secure.jsp Upgrade-Insecure-Requests:1 User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
basicProcessingFilter片段代码如下:
String header = httpRequest.getHeader("Authorization");
if (logger.isDebugEnabled()) {
logger.debug("Authorization header: " + header);
}
if ((header != null) && header.startsWith("Basic ")) {
String base64Token = header.substring(6);
String token = new String(Base64.decodeBase64(base64Token.getBytes()));
String username = "";
String password = "";
int delim = token.indexOf(":");
if (delim != -1) {
username = token.substring(0, delim);
password = token.substring(delim + 1);
}
if (authenticationIsRequired(username)) {
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));
Authentication authResult;
try {
authResult = authenticationManager.authenticate(authRequest);
} catch (AuthenticationException failed) {
// Authentication failed
if (logger.isDebugEnabled()) {
logger.debug("Authentication request for user: " + username + " failed: " + failed.toString());
}
SecurityContextHolder.getContext().setAuthentication(null);
if (rememberMeServices != null) {
rememberMeServices.loginFail(httpRequest, httpResponse);
}
if (ignoreFailure) {
chain.doFilter(request, response);
} else {
authenticationEntryPoint.commence(request, response, failed);
}
return;
}
// Authentication success
if (logger.isDebugEnabled()) {
logger.debug("Authentication success: " + authResult.toString());
}
SecurityContextHolder.getContext().setAuthentication(authResult);
if (rememberMeServices != null) {
rememberMeServices.loginSuccess(httpRequest, httpResponse, authResult);
}
}
}
chain.doFilter(request, response);
}
可以看出,basicProcessingFilter从Header中获取Authorization信息,并通过Apache的codec包中的解码工具对token进行解码,从而获取用户输入的用户名、密码信息,用于后面的校验动作。
basicProcessingFilter在获取到验证请求需要用到的用户名及密码信息后,实际的用户有效性验证,交给了org.acegisecurity.providers.ProviderManager来管理的org.acegisecurity.providers.dao.DaoAuthenticationProvider类的执行实际验证处理过程。
对basicProcessingFilter的详细配置如下:
<bean id ="basicProcessingFilter" class= "org.acegisecurity.ui.basicauth.BasicProcessingFilter"> <property name ="authenticationManager" ref= "authenticationManager" /> <property name ="authenticationEntryPoint" ref= "basicProcessingFilterEntryPoint" /> </bean> <bean id ="basicProcessingFilterEntryPoint" class= "org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint"> <property name ="realmName" value="Acegi First Realm Name" /> </bean> <bean id ="authenticationManager" class= "org.acegisecurity.providers.ProviderManager"> <property name ="providers"> <list> <ref bean="daoAuthenticationProvider" /> </list> </property> </bean> <bean id ="daoAuthenticationProvider" class= "org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name ="userDetailsService" ref= "inMemDaoImpl" /> </bean> <bean id="inMemDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl"> <property name="userMap"> <value> test=111111,ROLE_SUPERVISOR zhangsan=111111,ROLE_SUPERVISOR,disabled </value> </property> </bean>
接下来开始配置exceptionTranslationFilter,配置信息如下:
<!-- exception filter --> <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter"> <property name="authenticationEntryPoint" ref="basicProcessingFilterEntryPoint" /> </bean>
注:ExceptionTranslationFilter类用来处理权限验证失败时页面的路由情况,我们这里给ExceptionTranslationFilter配置了一个默认的basicProcessingFilterEntryPoint
对异常处理的片段代码如下:
public void commence(ServletRequest request, ServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
从上面的代码可以看到,当权限验证失败后,response请求被指向了HttpServletResponse.SC_UNAUTHORIZED页面(“401”访问受限页面)
在HttpServletResponse中定义的返回取值范围及常量定义如下所示:
public static final int SC_CONTINUE = 100;
public static final int SC_SWITCHING_PROTOCOLS = 101;
public static final int SC_OK = 200;
public static final int SC_CREATED = 201;
public static final int SC_ACCEPTED = 202;
public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;
public static final int SC_NO_CONTENT = 204;
public static final int SC_RESET_CONTENT = 205;
public static final int SC_PARTIAL_CONTENT = 206;
public static final int SC_MULTIPLE_CHOICES = 300;
public static final int SC_MOVED_PERMANENTLY = 301;
public static final int SC_MOVED_TEMPORARILY = 302;
public static final int SC_FOUND = 302;
public static final int SC_SEE_OTHER = 303;
public static final int SC_NOT_MODIFIED = 304;
public static final int SC_USE_PROXY = 305;
public static final int SC_TEMPORARY_REDIRECT = 307;
public static final int SC_BAD_REQUEST = 400;
public static final int SC_UNAUTHORIZED = 401;
public static final int SC_PAYMENT_REQUIRED = 402;
public static final int SC_FORBIDDEN = 403;
public static final int SC_NOT_FOUND = 404;
public static final int SC_METHOD_NOT_ALLOWED = 405;
public static final int SC_NOT_ACCEPTABLE = 406;
public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
public static final int SC_REQUEST_TIMEOUT = 408;
public static final int SC_CONFLICT = 409;
public static final int SC_GONE = 410;
public static final int SC_LENGTH_REQUIRED = 411;
public static final int SC_PRECONDITION_FAILED = 412;
public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413;
public static final int SC_REQUEST_URI_TOO_LONG = 414;
public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
public static final int SC_EXPECTATION_FAILED = 417;
public static final int SC_INTERNAL_SERVER_ERROR = 500;
public static final int SC_NOT_IMPLEMENTED = 501;
public static final int SC_BAD_GATEWAY = 502;
public static final int SC_SERVICE_UNAVAILABLE = 503;
public static final int SC_GATEWAY_TIMEOUT = 504;
public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
最后配置filterInvocationInterceptor:
<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor"> <property name="authenticationManager" ref="authenticationManager" /> <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager" /> <property name="objectDefinitionSource"> <value><![CDATA[ CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /secure.jsp=ROLE_SUPERVISOR ]]></value> </property> </bean> <bean id="httpRequestAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased"> <property name="decisionVoters"> <list> <bean class="org.acegisecurity.vote.RoleVoter"/> </list> </property> </bean>
注:FilterSecurityInterceptor是filterchain中比较复杂,也是比较核心的过滤器,主要负责授权的工作
spring通过HttpConfigurationBuilder类来为filter构造过滤器实例,代码片段如下:
private void createFilterSecurityInterceptor(BeanReference authManager) {
boolean useExpressions = FilterInvocationSecurityMetadataSourceParser
.isUseExpressions(httpElt);
RootBeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser
.createSecurityMetadataSource(interceptUrls, addAllAuth, httpElt, pc);
RootBeanDefinition accessDecisionMgr;
ManagedList<BeanDefinition> voters = new ManagedList<BeanDefinition>(2);
if (useExpressions) {
//表达式模式,这里省略,不是本例重点
}
else {
voters.add(GrantedAuthorityDefaultsParserUtils.registerWithDefaultRolePrefix(pc, RoleVoterBeanFactory.class));
voters.add(new RootBeanDefinition(AuthenticatedVoter.class));
}
accessDecisionMgr = new RootBeanDefinition(AffirmativeBased.class);
accessDecisionMgr.getConstructorArgumentValues().addGenericArgumentValue(voters);
accessDecisionMgr.setSource(pc.extractSource(httpElt));
// Set up the access manager reference for http
String accessManagerId = httpElt.getAttribute(ATT_ACCESS_MGR);
if (!StringUtils.hasText(accessManagerId)) {
accessManagerId = pc.getReaderContext().generateBeanName(accessDecisionMgr);
pc.registerBeanComponent(new BeanComponentDefinition(accessDecisionMgr,
accessManagerId));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.rootBeanDefinition(FilterSecurityInterceptor.class);
builder.addPropertyReference("accessDecisionManager", accessManagerId);
builder.addPropertyValue("authenticationManager", authManager);
if ("false".equals(httpElt.getAttribute(ATT_ONCE_PER_REQUEST))) {
builder.addPropertyValue("observeOncePerRequest", Boolean.FALSE);
}
builder.addPropertyValue("securityMetadataSource", securityMds);
BeanDefinition fsiBean = builder.getBeanDefinition();
String fsiId = pc.getReaderContext().generateBeanName(fsiBean);
pc.registerBeanComponent(new BeanComponentDefinition(fsiBean, fsiId));
// Create and register a DefaultWebInvocationPrivilegeEvaluator for use with
// taglibs etc.
BeanDefinition wipe = new RootBeanDefinition(
DefaultWebInvocationPrivilegeEvaluator.class);
wipe.getConstructorArgumentValues().addGenericArgumentValue(
new RuntimeBeanReference(fsiId));
pc.registerBeanComponent(new BeanComponentDefinition(wipe, pc.getReaderContext()
.generateBeanName(wipe)));
this.fsi = new RuntimeBeanReference(fsiId);
}
从上面的代码片段可以看出,在FilterInvocationSecurityMetadataSourceParser类中定义了一个静态方法用于处理鉴权元数据,代码片段如下:
static RootBeanDefinition createSecurityMetadataSource(List<Element> interceptUrls,
boolean addAllAuth, Element httpElt, ParserContext pc) {
MatcherType matcherType = MatcherType.fromElement(httpElt);
boolean useExpressions = isUseExpressions(httpElt);
ManagedMap<BeanMetadataElement, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap(
matcherType, interceptUrls, useExpressions, addAllAuth, pc);
BeanDefinitionBuilder fidsBuilder;
if (useExpressions) {
Element expressionHandlerElt = DomUtils.getChildElementByTagName(httpElt,
Elements.EXPRESSION_HANDLER);
String expressionHandlerRef = expressionHandlerElt == null ? null
: expressionHandlerElt.getAttribute("ref");
if (StringUtils.hasText(expressionHandlerRef)) {
logger.info("Using bean '" + expressionHandlerRef
+ "' as web SecurityExpressionHandler implementation");
}
else {
expressionHandlerRef = registerDefaultExpressionHandler(pc);
}
fidsBuilder = BeanDefinitionBuilder
.rootBeanDefinition(ExpressionBasedFilterInvocationSecurityMetadataSource.class);
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
fidsBuilder.addConstructorArgReference(expressionHandlerRef);
}
else {
fidsBuilder = BeanDefinitionBuilder
.rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class);
fidsBuilder.addConstructorArgValue(requestToAttributesMap);
}
fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(httpElt));
return (RootBeanDefinition) fidsBuilder.getBeanDefinition();
}
完整的spring-acegi.xml配置如下所示(完整路径:src/main/resources/META-INF/spring/spring-acegi.xml):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:webflow="http://www.springframework.org/schema/webflow-config" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.4.xsd" default-autowire="byName"> <bean id ="filterChainProxy" class= "org.acegisecurity.util.FilterChainProxy"> <property name ="filterInvocationDefinitionSource"> <value> PATTERN_TYPE_APACHE_ANT /**=basicProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor </value> </property> </bean> <bean id ="basicProcessingFilter" class= "org.acegisecurity.ui.basicauth.BasicProcessingFilter"> <property name ="authenticationManager" ref= "authenticationManager" /> <property name ="authenticationEntryPoint" ref= "basicProcessingFilterEntryPoint" /> </bean> <bean id ="basicProcessingFilterEntryPoint" class= "org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint"> <property name ="realmName" value="Acegi First Realm Name" /> </bean> <bean id ="authenticationManager" class= "org.acegisecurity.providers.ProviderManager"> <property name ="providers"> <list><ref bean="daoAuthenticationProvider" /></list> </property> </bean> <bean id ="daoAuthenticationProvider" class= "org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name ="userDetailsService" ref= "inMemDaoImpl" /> </bean> <!-- 基于内存实现方式--> <bean id ="inMemDaoImpl" class= "org.acegisecurity.userdetails.memory.InMemoryDaoImpl"> <property name ="userMap"> <value> test=1,ROLE_SUPERVISOR zhangsan=1,ROLE_SUPERVISOR,disabled </value> </property> </bean> <!-- exception filter --> <bean id ="exceptionTranslationFilter" class= "org.acegisecurity.ui.ExceptionTranslationFilter"> <property name ="authenticationEntryPoint" ref= "basicProcessingFilterEntryPoint" /> </bean> <bean id ="filterInvocationInterceptor" class= "org.acegisecurity.intercept.web.FilterSecurityInterceptor"> <property name ="authenticationManager" ref= "authenticationManager" /> <property name ="accessDecisionManager" ref= "httpRequestAccessDecisionManager" /> <property name ="objectDefinitionSource"> <value ><![CDATA[ CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /secure.jsp=ROLE_SUPERVISOR ]]></value> </property> </bean> <bean id ="httpRequestAccessDecisionManager" class= "org.acegisecurity.vote.AffirmativeBased"> <property name ="decisionVoters"> <list><bean class= "org.acegisecurity.vote.RoleVoter" /></list> </property> </bean> </beans>
对应的web.xml文件配置信息如下所示:
<?xml version="1.0" encoding= "UTF-8"?> <web-app xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns= "http://java.sun.com/xml/ns/javaee" xmlns:web= "http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version= "2.5"> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:META-INF/spring/spring-acegi.xml</param-value> </context-param> <filter> <filter-name>AcegiFilterChainProxy</filter-name> <filter-class> org.acegisecurity.util.FilterToBeanProxy </filter-class> <init-param> <param-name>targetBean</param-name> <param-value>filterChainProxy</param-value> </init-param> </filter> <filter-mapping> <filter-name>AcegiFilterChainProxy</filter-name> <url-pattern>/j_acegi_security_check</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AcegiFilterChainProxy</filter-name> <url-pattern>/j_acegi_logout</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AcegiFilterChainProxy</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AcegiFilterChainProxy</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> </web-app>
不失完整性,用于构建工程用到的指令如下:
mvn archetype:generate -DgroupId=com.myteay -DartifactId=rest-common-acegi -Dversion=1.0.0 -Dpackage=com.myteay -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeGroupId=org.apache.maven.archetypes -DinteractiveMode=false
方便使用起见,贴出工程用到的完整pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.myteay</groupId> <artifactId>rest-common-acegi</artifactId> <packaging>war</packaging> <version>1.0.0</version> <name>rest-common-acegi Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.2.RELEASE</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-servlet_2.4_spec</artifactId> <version>1.1.1</version> <scope>provided</scope> </dependency> <!-- spring dependency start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.2.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.2.2.RELEASE</version> </dependency> <!-- spring dependency end --> <!-- unit test start --> <dependency> <groupId>jmock</groupId> <artifactId>jmock</artifactId> <version>1.2.0</version> </dependency> <dependency> <groupId>jmock</groupId> <artifactId>jmock-cglib</artifactId> <version>1.2.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.acegisecurity</groupId> <artifactId>acegi-security</artifactId> <version>1.0.7</version> </dependency> </dependencies> <build> <finalName>rest-common-acegi</finalName> <plugins> <plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.26</version> <configuration> <scanIntervalSeconds>3</scanIntervalSeconds> <connectors> <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"> <port>80</port> </connector> </connectors> <scanTargetPatterns> <scanTargetPattern> <directory>src/main/webapp/WEB-INF</directory> <excludes> <exclude>**/*.jsp</exclude> </excludes> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> </scanTargetPattern> </scanTargetPatterns> </configuration> </plugin> </plugins> </build> </project>
所有评论(0)