Spring Acegi鉴权管理之基础模式(BASIC)
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>