(聲明:本設計目前還沒有應用於大型系統權限管理,不排除會有一些問題,僅作技術儲備,便於後期查閱;本文中中的Demo並沒有進行完整的重構,僅作技術參考)
場景:
1、界面化的管理權限、分配用戶權限。(非配置文件、註解形式;可理解爲界面化的添加具體權限信息,並分配指定用戶權限)
2、依賴於Spring Security。對後期權限系統的擴展必然會有好處,但是查找文檔沒有發現Spring Security提供便利的方式界面化管理權限(或許Spring Security本身支持,只是我看的文檔不夠全面,如果已支持還請讀者發本人相關資料,不勝感激 [email protected])。
下面的文章將主要圍繞本人如何實現界面化管理解決的一些問題,並不涉及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,可參考。