在傳統的Web開發中,安全性的代碼都是分散在各個模塊中的,這樣不方便管理,而且有時候可能會漏掉一個地方導致安全漏洞。爲了解決這個問題,有人發明了Spring Security。它的作用是將業務邏輯中有關安全的代碼全都移動到一個模塊中集中管理。本質上是AOP的一個子集。
過濾URL
爲了過濾URL,首先要在web.xml中加入一個過濾器。filter-name不能隨便填寫,因爲它和另外一個bean的名稱是一樣的。
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
下面配置的作用是攔截所有的請求。
<security:http auto-config="true">
<intercept-url pattern="/**" access="ROLE_VIP"/>
</security:http>
auto-config的作用相當於<form-login/><http-basic/><logout/>,它會給你自動生成一個登陸頁面。access="ROLE_VIP"表示只連接身份爲ROLE_VIP的用戶,ROLE_VIP這個名稱是由我們自己定義的。
上面的例子中,Spring框架自動生成了一個登陸頁面,但是不太美觀。因此,我們需要自己定義登陸頁面。
<http auto-config="true">
<form-login login-processing-url="/static/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"/>
</http>
<spring:url var="authUrl" value="/static/j_spring_security_check"/>
<form method="post" action="${authUrl}">
<input name="j_username" type="text"/>
<input name="j_password" type="password"/>
<input name="_spring_security_remember_me" type="checkbox"/>
<input type="submit"/>
</form>
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN') and hasIpAddress('192.168.1.2')"/>
</http>
注意一定要開啓use-expression=true。
認證表達式中支持的函數有:denyAll、hasAnyRole、hasRole、hasIpAddress、isAnonymouse、isAuthenticated、isFullyAuthenticated、isRememberMe、permitAll,支持的變量有autentication、principal。
HTTPS攔截。有些特殊的URL必須要用HTTPS安全連接。寫法如下。
<intercept-url pattern="/admin" requires-channel="https"/>
<intercept-url pattern="/public" requires-channel="http"/>
保護視圖
在JSP文件訪問與認證有關的變量,或者根據訪問者的身份顯示不同的內容。
訪問認證細節。比如訪問登陸的用戶名。
<sec:authentication property="principal.username"/>
根據不同的身份顯示不同的內容。請看下面的例子。
<sec:authorize access="hasRole('ROLE_VIP')">
You are VIP.
</sec:authorize>
認證方式
Spring支持的認證方式有:基於xml配置、基於JDBC、基於LDAP、OpenID、CAS、X509、JAAS。
基於xml配置。將用戶名和密碼寫在配置文件中。
<security:user-service id="userService">
<user name="root" password="123456" authorities="ROLE_VIP,ROLE_ADMIN"/>
<user name="test" password="test" authorities="ROLE_VIP"/>
</security:user-service>
<security:authentication-manager>
<authentication-provider user-service-ref="userService"/>
</security:authentication-manager>
基於JDBC。
<security:jdbc-user-service id="userService" data-source-ref="dataSource" users-by-username-query="select username,password,enabled from user where username=?" authorities-by-username-query="select username,authoritiy from user_auth"/>
基於LDAP。
<security:authentication-manager alias="authenticationManager">
<security:ldap-authentication-provider user-search-filter="(uid={0})" group-search-filter="member={0}">
<security:password-compare hash="md5"/>
<security:ldap-server url="ldap://example.com/dc=test"/>
</security:ldap-authentication-provider>
</security:authentication-manager>
記住登陸
<http auto-config="true">
<remember-me key="myVipKey" token-validity-seconds="86400"/>
</http>
myVipKey是Cookie中的令牌鍵名,令牌中保存了過期時間、用戶名、令牌密鑰。
攔截方法調用
開啓註解方式的安全攔截。
<global-method-security secured-annotations="enabled"/>
根據身份進行攔截。
@Secured("ROLE_VIP")
public void test(){}
過濾返回值。
@PostFilter("filterObject.user.username == principal.name")
public List<User> getUserList(){}
橫切授權
<global-method-security>
<protect-pointcut access="ROLE_VIP" expression="execution(@com.example.User * *.*(String))"/>
</global-method-security>