(声明:本设计目前还没有应用于大型系统权限管理,不排除会有一些问题,仅作技术储备,便于后期查阅;本文中中的Demo并没有进行完整的重构,仅作技术参考)
场景:
1、界面化的管理权限、分配用户权限。(非配置文件、注解形式;可理解为界面化的添加具体权限信息,并分配指定用户权限)
2、依赖于Spring Security。对后期权限系统的扩展必然会有好处,但是查找文档没有发现Spring Security提供便利的方式界面化管理权限(或许Spring Security本身支持,只是我看的文档不够全面,如果已支持还请读者发本人相关资料,不胜感激 partner4java@163.com)。
下面的文章将主要围绕本人如何实现界面化管理解决的一些问题,并不涉及Spring Security的具体细节使用,建议对Spring Security有一定了解再阅读本篇文章,Spring Security可参照《 Spring Security HelloWorld 》 (http://blog.csdn.net/partner4java/article/details/7928000)。
以下内容主要分为三部分:
1、功能展示;
2、基本架构设计;
3、问题解决具体方式。
一:功能展示
Demo下载地址:http://download.csdn.net/detail/partner4java/5220557
SQL脚本:http://download.csdn.net/detail/partner4java/5220915
Demo登录地址:http://localhost:8080/security_test/login.jsp
账号:(admin 1234)
首先展示一下我们管理权限的界面:
用户管理界面:
点击用户对应的权限跳转至权限分配界面:
权限分配为复选框,若已存在的权限会已选中。
二:基本架构设计
1、为AuthenticationManager设置自定义的UserDetailsService加载自定义表中的用户信息(验证用户信息和获取用户权限)。
2、修改默认AccessDecisionManager,在决策之前加入界面维护权限数据。
3、定义AuthorityCacheBean,用于辅助自定义AccessDecisionManager获取指定地址应该匹配的权限,并加入了缓存策略。
4、权限声明entity Authority,为其添加boolean matchUrl(String url)方法,用于返回是否遵循地址匹配;添加boolean matchMethod(HttpMethod httpMethod)方法,用于判断是否匹配方法,采用“大于等于”范围方式。
5、改变自带决策AccessDecisionManager,改为“只需要当前访问所有匹配权限中有一条权限没有人投反对票且有人赞同,则用户可以通过”。
三:问题解决具体方式
改变为界面化管理权限、用户,无非就几点:
1.通过登录信息匹配到维护的用户;
2.根据用户匹配界面化维护的权限;
3.根据地址匹配到对应的界面化维护的权限;
4.改变投票策略为你所需要的方式。
1、通过登录信息匹配到维护的用户
这一点Spring Security已经提供,无需我们再做过多修改,只需要在配置文件中指定:
<beans:property name="usersByUsernameQuery"> <beans:value> select username,password,visible as enabled from Employee where username = ? </beans:value> </beans:property>
同第一点,可以配置形式,我们这里借助了已有的User Service,便于后期统一加入缓存。
那么就需要重写原有的JdbcDaoImpl:
public class DefalutJdbcDaoImpl extends JdbcDaoImpl { private Log logger = LogFactory.getLog(DefalutJdbcDaoImpl.class); private EmployeeService employeeService; public void setEmployeeService(EmployeeService employeeService) { this.employeeService = employeeService; } @Override protected List<GrantedAuthority> loadUserAuthorities(String username) { // List<GrantedAuthority> grantedAuthoritys = // super.loadUserAuthorities(username); List<GrantedAuthority> grantedAuthoritys = new LinkedList<GrantedAuthority>(); Employee employee = employeeService.getEmployee(username); if (employee != null) { Set<Authority> authorities = employee.getAuthorities(); if (authorities != null) { for (Authority authority : authorities) { if (authority.getAuthority() != null && !"".equals(authority.getAuthority().trim())) { String roleName = getRolePrefix() + authority.getAuthority(); grantedAuthoritys.add(new SimpleGrantedAuthority(roleName)); } } } } if (logger.isDebugEnabled()) { logger.debug("loadUserAuthorities() username: " + username + " grantedAuthoritys:" + grantedAuthoritys); } return grantedAuthoritys; } }若你还需要开通配置文件或其他形式形式添加权限,放开注释代码并合并数据库获取的权限即可。
配置文件:
<!-- 通过默认的方式回去用户信息 --> <beans:bean id="userDetailsService" class="com.partner4java.security.core.userdetails.service.DefalutJdbcDaoImpl"> <beans:property name="dataSource" ref="dataSource" /> <beans:property name="usersByUsernameQuery"> <beans:value> select username,password,visible as enabled from Employee where username = ? </beans:value> </beans:property> <!-- <beans:property name="authoritiesByUsernameQuery"> <beans:value> select u.username as username,a.authority as authority from employee u,Authority a,employee_authority au where u.username = ? and u.EMPLOYEE_ID = au.EMPLOYEE_ID and a.AUTHORITY_ID = au.AUTHORITY_ID and a.visible = 1 </beans:value> </beans:property> --> <beans:property name="employeeService" ref="employeeServiceBean"/> </beans:bean>
可以看到我们注释掉了配置的authoritiesByUsernameQuery,这种形式和匹配用户一样可以自己选择使用的方式。
3、根据地址匹配到对应的界面化维护的权限
这一点你可以从获取权限根部入手,我们这里修改的AccessDecisionManager。
但是有一点,我们如何知道当前访问的url和httpmethod呢(根部获取位置传入了request,我们这里并没有request参数传入)?
我们利用Filter,借助ThreadLocal,这里要注意一点当前线程可能会有多次请求,我们需要织入最新方式地址,也就是if (url.get() == null || (url.get() != null && !url.get().equals(rul)) || httpMethod.get() == null
|| (httpMethod.get() != null && !httpMethod.get().equals(request.getMethod())))
这个问题纠结了我好久,后来打印log才发现存在多次请求。
public class InitFilter implements Filter { private static Log logger = LogFactory.getLog(InitFilter.class); private static ThreadLocal<String> httpMethod = new ThreadLocal<String>(); private static ThreadLocal<String> url = new ThreadLocal<String>(); @Override public void destroy() { } @Override public void doFilter(ServletRequest req, ServletResponse rep, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; String rul = UrlUtils.buildRequestUrl(request); // 有些情况是单个线程存在多次访问 if (url.get() == null || (url.get() != null && !url.get().equals(rul)) || httpMethod.get() == null || (httpMethod.get() != null && !httpMethod.get().equals(request.getMethod()))) { url.set(rul); httpMethod.set(request.getMethod()); if (logger.isDebugEnabled()) { logger.debug("doFilter() currentThread id: " + Thread.currentThread().getId()); logger.debug("doFilter() fullRequestUrl:" + UrlUtils.buildFullRequestUrl(request) + " httpMethod:" + request.getMethod()); } } doSetCharsetencoding(req); chain.doFilter(req, rep); } /** * 设置请求编码格式,一定要设置为filter链第一个 * * @param req * @throws UnsupportedEncodingException */ private void doSetCharsetencoding(ServletRequest req) throws UnsupportedEncodingException { String charsetencoding = ((HttpServletRequest) req).getSession().getServletContext() .getInitParameter("request.charsetencoding"); if (charsetencoding != null && !"".equals(charsetencoding.trim())) { req.setCharacterEncoding(charsetencoding); } } @Override public void init(FilterConfig filterConfig) throws ServletException { } /** * 返回当前请求类型 * * @return POST,GET等 */ public static String getLocalMethod() { if (logger.isDebugEnabled()) { logger.debug("getLocalMethod() " + httpMethod.get() + " currentThread id: " + Thread.currentThread().getId()); } return httpMethod.get(); } /** * 返回当前请求地址标识 * * @return */ public static String getLocalURL() { if (logger.isDebugEnabled()) { logger.debug("getLocalURL() " + url.get() + " currentThread id: " + Thread.currentThread().getId()); } return url.get(); } }我们可以拿到url和httpmethod后,借助AuthorityCacheBean的getLocalMatchConfigAttributes()方法。AuthorityCacheBean很有意思,我们加入了定时器ThreadPoolUtil.scheduler.scheduleWithFixedDelay,间隔执行时间为我们重新载入权限的频率,当然在保证第一次加载成功的前提下你可以设置的长一些,我们这里只是为了测试更快的看到修改数据后的效果。
@Service @Scope(value = "singleton") public class AuthorityCacheBean implements AuthorityCache { /** 返回静态实例引用,并不保证本对象不为null,也不保证线程安全性 */ public static AuthorityCacheBean singleton = null; /** 存放当前所有的权限,key为权限名称,value为权限对象 */ private static Map<String, Authority> authorities = new HashMap<String, Authority>(); /** 自定义配置的权限设置 -- 所有权限 */ private static List<ConfigAttribute> configAttributes; /** URL地址和对应权限的对应 */ private static ConcurrentMap<String, List<ConfigAttribute>> configUrlMap = new ConcurrentHashMap<String, List<ConfigAttribute>>(); private static Log logger = LogFactory.getLog(AuthorityCacheBean.class); private AuthorityService authorityService; @Autowired public AuthorityCacheBean(AuthorityService authorityService) { super(); this.authorityService = authorityService; if (logger.isDebugEnabled()) { logger.debug("AuthorityCacheBean init authorityService -- " + authorityService); } singleton = this; } @Override public Authority getAuthority(String authorityName) { if (authorities != null && authorities.size() > 0) { return authorities.get(authorityName); } else { if (logger.isDebugEnabled()) { logger.debug("getAuthority(): authorities is null!"); } } return null; } @Override public void initAuthorities() { initBaseAuthorities(); } /** * 初始化authorities */ private synchronized void initBaseAuthorities() { if (authorityService == null) { if (logger.isErrorEnabled()) { logger.debug("AuthorityCacheBean isn't init by Spring."); } throw new IllegalArgumentException("AuthorityCacheBean isn't init by Spring."); } // 插叙数据库 PageIndex pageIndex = new PageIndex(); pageIndex.setMaxResult(AuthorityService.MAX_RESULT); List<Authority> mAuthorities = authorityService.query(null, pageIndex, null).getResultlist(); if (mAuthorities != null && mAuthorities.size() > 0) { // 重新获取 Map<String, Authority> nAuthorities = new HashMap<String, Authority>(); for (Authority authority : mAuthorities) { if (authority.getAuthority() != null && !"".equals(authority.getAuthority())) { nAuthorities.put(authority.getAuthority(), authority); } } authorities = null; authorities = nAuthorities; configUrlMap = null; configUrlMap = new ConcurrentHashMap<String, List<ConfigAttribute>>(); setConfigAttributes(nAuthorities.keySet()); if (logger.isDebugEnabled()) { logger.debug("init authorities sucessfull :" + authorities); } } } static { // 定时器 ThreadPoolUtil.scheduler.scheduleWithFixedDelay(new TimerTask() { @Override public void run() { try { AuthorityCacheBean.singleton.initBaseAuthorities(); } catch (Exception e) { e.printStackTrace(); } } }, 10, 60, TimeUnit.SECONDS); } public List<ConfigAttribute> getConfigAttributes() { return configAttributes; } /** * 通过权限名称转化为权限系统的ConfigAttribute对象 * * @param attributeNames */ public void setConfigAttributes(Collection<String> attributeNames) { this.configAttributes = null; if (attributeNames != null) { this.configAttributes = SecurityConfig .createList(attributeNames.toArray(new String[attributeNames.size()])); if (logger.isDebugEnabled()) { logger.debug("init configAttributes sucessfull :" + configAttributes); } } } /** * 根据URL和HttpMethod获取对应的权限 * * @return */ public List<ConfigAttribute> getLocalMatchConfigAttributes() { return getMatchConfigAttributes(InitFilter.getLocalURL(), HttpMethod.getHttpMethod(InitFilter.getLocalMethod())); } /** * 根据URL和HttpMethod获取对应的权限 * * @param url * 请求URL * @param httpMethod * 请求类型 * @return */ public List<ConfigAttribute> getMatchConfigAttributes(String url, HttpMethod httpMethod) { // 判断本地缓存是否已经存在此地址的匹配 if (configUrlMap != null && configUrlMap.containsKey(url + httpMethod.toString())) { return configUrlMap.get(url + httpMethod.toString()); } List<ConfigAttribute> mConfigAttributes = doGetMatchConfigAttributes(url, httpMethod); if (logger.isDebugEnabled()) { logger.debug("getMatchConfigAttributes : " + url + httpMethod.toString() + " not in cache get " + mConfigAttributes); } configUrlMap.put(url + httpMethod.toString(), mConfigAttributes); return mConfigAttributes; } /** * 根据URL获取对应的权限 * * @param url * 请求URL * @param httpMethod * 请求类型 * @return */ private List<ConfigAttribute> doGetMatchConfigAttributes(String url, HttpMethod httpMethod) { if (authorities != null) { Map<String, Authority> attributeNames = new HashMap<String, Authority>(); for (Authority authority : authorities.values()) { // 当前遍历权限是否匹配此地址 if (authority.matchUrl(url) && authority.matchMethod(httpMethod)) { attributeNames.put(authority.getAuthority(), authority); } } return SecurityConfig.createList(attributeNames.keySet() .toArray(new String[attributeNames.keySet().size()])); } return null; } }
4、改变投票策略为你所需要的方式
public class MyUnanimousBased extends UnanimousBased { public static final String USER_DEFAULT = "USER_DEFAULT"; public MyUnanimousBased(List<AccessDecisionVoter> decisionVoters) { super(decisionVoters); } @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) throws AccessDeniedException { if (AuthorityCacheBean.singleton != null) { List<ConfigAttribute> configAttributes = AuthorityCacheBean.singleton.getLocalMatchConfigAttributes(); if (logger.isDebugEnabled()) { logger.debug("removeRepeat(): old " + configAttributes + ", new: " + attributes); } if (configAttributes != null) { removeRepeat(configAttributes, attributes); attributes = configAttributes; } } if (logger.isDebugEnabled()) { logger.debug("decide() getLocalMethod:" + InitFilter.getLocalMethod()); logger.debug("decide() getLocalURL:" + InitFilter.getLocalURL()); logger.debug("decide() authentication:" + authentication); logger.debug("decide() object:" + object); logger.debug("decide() attributes:" + attributes); } doDecide(authentication, object, attributes); } private void doDecide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { int grant = 0; int abstain = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, attributes); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: grant++; break; case AccessDecisionVoter.ACCESS_DENIED: throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); default: abstain++; break; } } // To get this far, there were no deny votes if (grant > 0) { return; } // if (abstain > 0) { // return; // } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); } /** * 去重 * * @param matchConfigAttributes * 自定义匹配 * @param attributes * 已有匹配 * @return */ private Collection<ConfigAttribute> removeRepeat(List<ConfigAttribute> matchConfigAttributes, Collection<ConfigAttribute> attributes) { if (matchConfigAttributes == null) { return attributes; } if (attributes != null) { for (ConfigAttribute attribute : attributes) { for (int i = 0; i < matchConfigAttributes.size(); i++) { ConfigAttribute mAttribute = matchConfigAttributes.get(i); if (mAttribute.getAttribute() != null && mAttribute.getAttribute().equals(attribute.getAttribute())) { matchConfigAttributes.remove(i); break; } } } matchConfigAttributes.addAll(attributes); } return matchConfigAttributes; } }如代码也就是在decide投票之前修改传入的attributes。
doDecide方法改变了投票方法:只需要当前访问所有匹配权限中有一条权限没有人投反对票且有人赞同,则用户可以通过。
5、拦截所有地址
因为我们修改的投票方式是需要满足至少一条权限通过即可通过,且由于权限加入阶段为投票阶段,默认Spring Security判断为若没有匹配到它指定方式的权限,不会走到投票阶段;也就是说,Spring Security自身若没有匹配到当前地址的权限,不会执行AccessDecisionManager。
所以我们在XML中加入了:
<!-- 如果需要进行数据库管理权限声明,必须设置本配置 --> <intercept-url pattern="/**" access="ADMIN_USER_DEFAULT" />
6、引入了p4jmvc
p4jmvc对基本的CURD Controller进行了封装,大大缩减的代码量,参考:《p4jmvc 内测版》 http://blog.csdn.net/partner4java/article/details/8759304
7、本架构借助了maven
分离了所有代码层,所以你会发现XML中的配置有classpath*:和classpath:两种,添加*会扫描所有jar。
主要jar为:p4jsecurity-1.0.0.jar、p4jmvc-1.0.0.jar、p4jorm-1.0.4.jar、p4jtools-1.0.0.jar。具体可参照源码
8、log
便于调试,加入了log,你需要做的就是类路径下存在log4j.xml文件,demo中已经加入
9、匿名用户
由于拦截了所有jsp,那么未登录用户如何访问登录界面呢?加入匿名用户权限
<intercept-url pattern="/login.jsp*" access="LOGIN" /> <anonymous username="guest" granted-authority="LOGIN" />
查看源码入口可从配置文件着手:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd "> <beans:import resource="classpath:META-INF/spring/authentication_manager.xml" /> <!-- 权限判断方式 --> <beans:bean id="roleVoter" class="com.partner4java.security.access.vote.DefalutRoleVoter"> <!-- 将只针对GROUP_为前缀的权限进行授权投票,默认为"ROLE_" --> <beans:property name="rolePrefix" value="*" /> </beans:bean> <beans:bean id="authenticatedVoter" class="com.partner4java.security.access.vote.DefaultAuthenticatedVoter" /> <beans:bean id="accessDecisionManager" class="com.partner4java.security.access.vote.MyUnanimousBased"> <beans:constructor-arg name="decisionVoters"> <beans:list> <beans:ref bean="roleVoter" /> <beans:ref bean="authenticatedVoter" /> </beans:list> </beans:constructor-arg> </beans:bean> <beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <beans:constructor-arg value="/login.jsp" /> </beans:bean> <beans:bean id="authenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"> <beans:property name="defaultTargetUrl" value="/manage/index.do" /> </beans:bean> <beans:bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <beans:property name="defaultFailureUrl" value="/login.jsp?error=true" /> </beans:bean> <!-- filter:前期拦截,初始化一些数据 --> <beans:bean id="webAuthenticationDetailsSource" class="com.partner4java.security.web.authentication.DefaultWebAuthenticationDetailsSource" /> <beans:bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <!-- 用户登录信息资源获取类 --> <beans:property name="authenticationDetailsSource" ref="webAuthenticationDetailsSource" /> <!-- 登录失败界面 --> <beans:property name="authenticationFailureHandler" ref="authenticationFailureHandler" /> <!-- 登录成功界面 --> <beans:property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" /> <!-- 认证管理器 --> <beans:property name="authenticationManager" ref="authenticationManager" /> </beans:bean> <http access-decision-manager-ref="accessDecisionManager" entry-point-ref="loginUrlAuthenticationEntryPoint"> <intercept-url pattern="/login.jsp*" access="LOGIN" /> <!-- 如果需要进行数据库管理权限声明,必须设置本配置 --> <intercept-url pattern="/**" access="ADMIN_USER_DEFAULT" /> <anonymous username="guest" granted-authority="LOGIN" /> <logout logout-success-url="/login.jsp" /> <!-- <remember-me /> --> <custom-filter position="FORM_LOGIN_FILTER" ref="authenticationFilter" /> </http> <authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="userDetailsService"> </authentication-provider> </authentication-manager> <!-- 使用默认形式 --> <!-- <http auto-config="true"> <intercept-url pattern="/manage/index*" access="MANAGE_INDEX" method="GET" /> <intercept-url pattern="/manage/authority/viewlist.do*" access="AUTH_VIEW" method="POST" /> <intercept-url pattern="/manage/authority/*" access="AUTH_MANAGE" /> <form-login login-page="/admin/login.jsp" default-target-url="/manage/index.do" authentication-failure-url="/admin/login.jsp?error=true" /> <logout logout-success-url="/admin/login.jsp" /> </http> --> <!-- <authentication-manager alias="daoAuthenticationProvider"> <authentication-provider> <jdbc-user-service data-source-ref="dataSource" users-by-username-query="select username,password,visible as enabled from Employee where username = ?" authorities-by-username-query="select u.username,a.authority as authorities from employee u,Authority a,employee_authority au where u.username = ? and u.EMPLOYEE_ID = au.EMPLOYEE_ID and a.AUTHORITY_ID = au.AUTHORITY_ID" /> </authentication-provider> </authentication-manager> --> </beans:beans>
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd "> <!-- 通过默认的方式回去用户信息 --> <beans:bean id="userDetailsService" class="com.partner4java.security.core.userdetails.service.DefalutJdbcDaoImpl"> <beans:property name="dataSource" ref="dataSource" /> <beans:property name="usersByUsernameQuery"> <beans:value> select username,password,visible as enabled from Employee where username = ? </beans:value> </beans:property> <!-- <beans:property name="authoritiesByUsernameQuery"> <beans:value> select u.username as username,a.authority as authority from employee u,Authority a,employee_authority au where u.username = ? and u.EMPLOYEE_ID = au.EMPLOYEE_ID and a.AUTHORITY_ID = au.AUTHORITY_ID and a.visible = 1 </beans:value> </beans:property> --> <beans:property name="employeeService" ref="employeeServiceBean"/> </beans:bean> <!-- 可以配置用户获取方式和密码加密方式 --> <beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <beans:property name="userDetailsService" ref="userDetailsService" /> </beans:bean> <!-- 认证管理器负责确定用户的身份,认证管理器由AuthenticationManager接口定义 --> <beans:bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager"> <beans:constructor-arg name="providers"> <!-- 他不依靠自己实现认证,而是逐一认证提供者的集合,直到某一个认证提供者能够成功的验证该用户的身份 --> <beans:list> <beans:ref bean="authenticationProvider" /> </beans:list> </beans:constructor-arg> </beans:bean> </beans:beans>
最后一个问题如何把P4jSecurity加入已有系统中?
只需要简单四步:
1、引入jar
p4jsecurity-1.0.0.jar、p4jmvc-1.0.0.jar、p4jorm-1.0.4.jar、p4jtools-1.0.0.jar、Spring相关jar具体可参考Demo
2、配置web.xml
contextConfigLocation加入classpath*:META-INF/spring/security.xml
加入InitFilter、DelegatingFilterProxy两个Filter:
<?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></display-name> <context-param> <param-name>contextConfigLocation</param-name> <!-- "classpath*:"会扫描所有jar --> <param-value> classpath*:META-INF/spring/security.xml classpath:META-INF/spring/datasource-c3p0.xml classpath*:META-INF/spring/beans*.xml </param-value> </context-param> <context-param> <param-name>request.charsetencoding</param-name> <param-value>UTF-8</param-value> </context-param> <!-- 初始化一些参数资源等 --> <filter> <filter-name>initFilter</filter-name> <filter-class>com.partner4java.mvc.filter.InitFilter</filter-class> </filter> <filter-mapping> <filter-name>initFilter</filter-name> <url-pattern>/manage/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>initFilter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping> <filter-mapping> <filter-name>initFilter</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>initFilter</filter-name> <url-pattern>*.html</url-pattern> </filter-mapping> <filter-mapping> <filter-name>initFilter</filter-name> <url-pattern>/j_spring_security_check</url-pattern> </filter-mapping> <filter-mapping> <filter-name>initFilter</filter-name> <url-pattern>/j_spring_security_logout</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- spring mvc配置 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> classpath*:META-INF/spring/controller*.xml classpath:META-INF/spring/cache.xml </param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <!-- spring security安全配置 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/manage/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/j_spring_security_check</url-pattern> </filter-mapping> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/j_spring_security_logout</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>3、导入相关表
employee、authority、employee_authority
4、加入jsp和样式文件
示例中还加入了ehcache,可参考。