有沒有發現一個問題,我們之前做的所有練習,都沒的權限管理這個模塊。我們的WEB應用中的同一個帳戶可以在多臺機器上同時登陸,每一個用戶可以操作所有功能模塊。這樣在以后的應用開發中是結對不可行的!
今天的重點內容就是權限管理,如果使用傳統的方式進行權限管理,在實現上多少有點麻煩。我在進行桌面開發時,涉及到的權限管理是向用戶表中添加用戶具有的權限ID串,每次用戶登陸時都拿這個串去查詢角色表,并根據用戶的角色隱藏相應的功能。那我們在WEB應用中應該如何做呢?方法都一樣,但是Spring為我們提供了一個框架專門用于權限管理,用起來比較方便——security!
一、Spring-security
SpringSecurity是SpringFramework的一個子項目,之前也叫做AcegiSecruty。
SpringSecurity能用于保護各種Java應用程序(權限管理框架),但在基于Web的應用程序中使用得最為廣泛。
SpringSecurity能以聲明的方式來保護Web應用程序的URL訪問,只需簡單的配置即可實現。
SpringSecurity通過一系列Servlet過濾器為Web應用程序提供了多種安全服務。
SpringSecurity2.x顯著簡化了配置,使用基于XMLSchema和基于注解的配置。
首先讓我們來回想一下,以前老方在講JavaWEB基礎時向大家介紹使用Filter進行權限管理,這是一個十分好的方法。當然他也有說,以后大家學習Spring時,只要簡單的配置幾下就可以進行權限管理了,十分方便。(注意:過濾器)
Security也正是使用過濾器來實現上面的功能的!能想通嗎?讓我們一點點往下看。
二、HelloWorld!
我們依然從這個經典的程序開始,其實HelloWorld!程序只是學習者直觀簡單的進入一門語言或工具。我們的這SecurityHelloWrld程序比較一般的HelloWorld程序內容要多一些。我們需要建立兩個頁面(index.jsp和admin.jsp),index.jsp用于登陸,admin.jsp只有具有ROLE_ADMIN權限的用戶才能查看。
我們先想一下需求:
· 防止一個帳戶同時在多個機器上登陸
· 帳戶只能根據自己的權限操作相應的功能模塊
· 帳戶只能在頁面上看到具有對應權限的數據內容
1.搭建環境
因為我們使用的是Spring-security,所以需要搭建Spring環境。創建一個動態WEB工程(EclipseJavaEE版)。
1).添加jar包: spring.jar、commons-loggin.jar、spring-security-core-2.0.5.jar。
2).WEB文件:
<!-- 搭建Spring -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 添加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>/*</url-pattern>
</filter-mapping>
|
看見沒,在這用到過濾器代理了!過濾器名稱必須指定為”springSecurityFilterChain“,Spring-secrutiy默認為我們提供了一個過濾器。我們也可以編寫一個自己的過濾器,然后將它配置為Spring的Bean,Bean的名稱必須為 springSecurityFilterChain。
3).applicationContext.xml文件:
<!-- 配置SpringSecurity的http安全服務 -->
<sec:http auto-config="true">
<!-- 只有ROLE_ADMIN或ROLE_USER權限用戶才能訪問index.jsp -->
<sec:intercept-url pattern="/index.jsp" access="ROLE_ADMIN,ROLE_USER"/>
<!-- 只有ROLE_ADMIN權限用戶才能訪問admin.jsp -->
<sec:intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/>
</sec:http>
<!-- 配置SpringSecutiry的權限信息 -->
<sec:authentication-provider>
<sec:user-service>
<!-- 帳戶信息 -->
<sec:user password="admin" name="admin" authorities="ROLE_ADMIN"/>
<sec:user password="user" name="user" authorities="ROLE_USER"/>
</sec:user-service>
</sec:authentication-provider>
|
需要為這個spring的配置文件添加:xmlns:sec="http://www.springframework.org/schema/security",具體操作步驟不細說了。
在Spring的配置文件中描述帳戶、權限信息,但把這么重要的信息放在這里是不安全的!
2.添加JSP頁面
index.jsp,body部分的內容:
<body>
<h1>*****INDEX*****</h1>
<security:authorize ifAllGranted="ROLE_ADMIN">
<br>
<a href="admin.jsp">ADMIN</a>
</security:authorize>
<br>
<a href="logout">LOGOUT</a>
</body>
|
需要為這個JSP文件導入:<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%>。
admin.jsp,body部分的內容:
<body>
<h1>*****ADMIN*****</h1>
<a href="index.jsp">RETURN</a>
</body>
|
OK,我們的Spring-securityHelloWorld!程序已經完成了!跑一下,看到這個頁面沒有?
我們沒有為工程添加這們的頁面啊!先不管這些,使用我們配置的兩個帳戶登陸看看功能如何。
3.登出配置
因為我們在頁面中添加了登出連接,所以我們添加一個登出配置:
<!-- 配置登出信息 -->
<logout logout-url="/logout" logout-success-url="/bye.jsp"
invalidate-session="true" />
|
我們添加一個bye.jsp頁面,點擊logout連接時,就會轉到此頁面。
4.登陸配置
上面的頁面是spring-security自動為我們添加的登陸頁面,我們也可以設置自己的登陸頁面:
<!-- 配置登陸信息 -->
<form-login login-page="/login.jsp" login-processing-url="/login" />
|
login.jsp
<!-- 配置登陸信息 -->
<form-login login-page="/login.jsp"
login-processing-url="/login"
default-target-url="/index.jsp"
authentication-failure-url="/loginfail.jsp"
always-use-default-target="true"/>
|
login-page:登陸頁面,這里的login.jsp頁面中的表單請求和字段名稱必須是spring-security指定的,可以查看spring-security默認為我們提供的頁面代碼。
login-processing-url:登陸請求處理URL。
default-target-url:登陸成功后,轉到的頁面。
authentication-failure-url:登陸失敗后,轉到的頁面。
always-use-default-target:一直使用”default-target-url“,這是什么意思?假如我們現在有兩個頁面可以跳到登陸頁面,默認情況下spring-security在登陸成功后會跳轉到——跳轉登陸頁面的頁面。如果這個值真,那么登陸成功后會一直跳轉到”default-target-url“指定的頁面。有些情況下,用戶直接進入登陸頁面進行登陸,此時登陸成功后會跳轉到”default-target-url“指定的頁面,如果沒有指定這個頁面則跳轉到index.jsp頁面,如果沒有index.jsp頁面將出錯。
問題:
將這么重要的信息放在spring的配置文件中是相當不安全的,Spring-security為們提供了安全的方法,將數據放到數據庫中。
三、使用數據庫替換配置文件
下面我們就實現將用戶的帳戶信息存儲到數據庫中,關于頁面的訪問權限配置存放到數據庫中我們明天再總結。在此我們就不大動干戈去使用Hibernate了,我們使用JDBC。
1.添加JDBC
向Spring添加數據源Bean還記得嗎?我們使用的數據源是c3p0。
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${user}" />
<property name="password" value="${password}" />
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
<property name="minPoolSize" value="5" />
<property name="initialPoolSize" value="3" />
<property name="maxPoolSize" value="10" />
<property name="acquireIncrement" value="2" />
</bean>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
<constructor-arg ref="dataSource"/>
</bean>
|
2.建數據庫和數據表
用戶表與角色表是什么關系?當前是多對多嘍!因此我們需要三個表:
1).user(用戶表)
2).role(角色表)
3).user_role(中間表)
3.從數據庫中讀取權限信息
我們的數據源、jdbcTemplate和數據庫已經建好了,接下來就是讓spring-security可以讀取到數據庫中的信息,根據這些信息判斷用戶輸入的登陸信息是否正確以及用戶所具有的權限。
1).要想spring-security可以讀取到數據庫中的信息,我們必須編寫一個實現了UserDetailsService接口的類:
package cn.itcast.cc.spring.security;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.userdetails.User;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
@Component("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private SimpleJdbcTemplate jdbcTemplate = null;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
// 根據用戶名獲取帳戶和權限信息
String sql = "SELECT username,password,status,name rname"
+ " FROM user u,role r,user_role ur"
+ " WHERE u.id=ur.user_id AND r.id=ur.role_id AND username=?";
// 如果一個用戶具有多個權限,連接查詢會返回一個List
List list = this.jdbcTemplate.queryForList(sql,
new Object[] { username });
// 取出帳戶和權限信息填充到User中返回
if (list == null || list.size() <= 0)
// spring-security定義的異常
throw new UsernameNotFoundException("用戶不存在!");
// 如果用戶存在
Map<String, Object> map = (Map<String, Object>) list.get(0);
// 密碼
String password = (String) map.get("password");
// 帳戶是否可用
boolean enabled = ((Integer) map.get("status") == 1);
// 帳戶所具有的權限
GrantedAuthority[] gas = new GrantedAuthority[list.size()];
for (int i = 0; i < gas.length; i++) {
Map<String, Object> temp = (Map<String, Object>) list.get(i);
gas[i] = new GrantedAuthorityImpl((String) temp.get("rname"));
}
// spring-security提供的類
User user = new User(username, password, enabled, true, true, true, gas);
return user;
}
}
|
一定要記住實現的是UserDetailsService 接口,在spring-security幫助手冊中應該有。佟佟今天領著大家看著源碼,下著斷點,找到了這個接口。
2).將applicationContext.xml文件中的”<!-- 配置SpringSecutiry的權限信息 -->“部分,全部替換為:
<authentication-provider user-service-ref="userDetailsService" />
|
3).運行,跑一個!
Spring-security的實現原理是什么?查看它的源文件,東西似乎還不少。我們簡單的想一下,這些功能在Filter中完全可以實現。我們可以在Filter中讀取數據庫的記錄,根據帳戶的權限決定是否允許用戶繼續訪問。我們也可以在JSP頁面中添加代碼,判斷用戶的權限以顯示或隱藏相應的功能。
如果我們手動去實現這樣的功能,那還是有些麻煩的。
明天繼續Spring-sceurity!