Spring Security helloworld

首先,不好意思,這篇文字不會太好,因爲實在沒有太好的資料去查閱,有個官方文檔英文的,試試硬着頭皮看看吧,然後再整理篇。。。


0Spring Security介紹

Spring Security是一種爲基於Spring的應用程序提供說明性安全保護框架。
他提供了全面的安全性解決方案,同時在Web請求級和方法調用級處理身份確認和授權。

在過去Spring Security被成爲Acegi Security。

在保護Web應用程序時,採用了filter來攔截Servlet請求。
還可以保護方法調用在一個較低層的級別上執行安全措施,只用Spring AOP來代理對象。

主要使用了5個組件來實施安全措施:
安全攔截器:
認證管理器、訪問決策管理器、運行身份管理器、調用後管理器

安全攔截器:只負責上鎖攔截你的請求;
認證管理器:一個靈活的認證策略,用於判定你是誰;
訪問決策管理器:一旦Spring Security確定了你是誰,他就必須決定你是否對受保護資源有訪問授權;
運行身份管理器:運行身份管理器可以用來使用另一個身份替換你的身份,從而允許你訪問應用程序內部更深處的受保護對象。
調用後管理器:確保你拿走的數據是否在允許範圍之內。

1加強URL訪問安全

問題:
控制一些私密的URL被訪問。

解決方案:
首先在web.xml註冊過濾器DelegatingFilterProxy,他會轉到Spring上下文中配置的過濾器。

如果你想使用簡單的方式,可以設置元素的autoconfig屬性爲true,這樣Spring Security將自動註冊和配置以下幾個基本的安全服務:
·基於表單的登錄服務:提供保護應用登錄表單的默認界面。
·註銷服務:提供一個映射到用於用戶退出應用的URL的處理程序。
·HTTP基本驗證:處理HTTP請求頭標中存在的基本驗證憑據,還能用於驗證遠程協議的Web服務發出的驗證請求。
·匿名登錄:爲匿名用戶指派一個角色並授予權限,可以將匿名用戶作爲常規用戶處理。
·Remember-me支持:在多個瀏覽器會話中記憶用戶的身份,通常在用戶瀏覽器中存儲一個Cookie來實現。
·Servlet API集成:允許通過標準Servlet API如HttpServletRequest.isUserInRole()和HttpServletRequest.getUserPrincipal(),訪問Web應用中的安全信息。


工作原理:

以一個demo爲例:
一個留言板,讓用戶粘貼他們的信息。

domain:

public class Message {
    private Long id;
    private String author;
    private String title;
    private String body;

service:
public interface MessageBoardService {
    public List<Message> listMessage();
    public void postMessage(Message message);
    public void deleteMessage(Long id);
    public Message findMessageById(Long id);
}

public class MessageBoardServiceImpl implements MessageBoardService {
    private Map<Long, Message> messages = new LinkedHashMap<Long, Message>();

    @Override
    public List<Message> listMessage() {
        return new LinkedList<Message>(messages.values());
    }

    @Override
    public synchronized void postMessage(Message message) {
        message.setId(System.currentTimeMillis());
        messages.put(message.getId(), message);
    }

    @Override
    public synchronized void deleteMessage(Long id) {
        messages.remove(id);
    }

    @Override
    public Message findMessageById(Long id) {
        return messages.get(id);
    }

}

我們要使用Spring MVC
pom:
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>${spring.version}</version>
</dependency>


<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-acl</artifactId>
    <version>${spring.version}</version>
</dependency>

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <display-name>nongfu888</display-name>

    <!--*********************begin:spring的配置****************** -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:META-INF/spring/beans.xml</param-value>
    </context-param>
    <context-param>
        <param-name>request.charsetencoding</param-name>
        <param-value>GBK</param-value>
    </context-param>

    <!-- 對Spring容器進行實例化 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <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/springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.jsp</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.tile</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

在Web層次配置文件springmvc.xml,你定義了一個視圖解析器,將視圖名稱解析爲/WEB-INF/jsp/目錄中的JSP文件。
然後配置你的控制器:
<?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:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  <context:component-scan base-package="cn.partner4java.controller" />
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/" />
    <property name="suffix" value=".jsp" />
  </bean>
</beans>        

服務層配置文件service.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean id="messageBoardService" class="com.partner4java.demo.service.MessageBoardServiceImpl" />
</beans>       

創建控制器和頁面視圖:
@Controller
@RequestMapping("/messageList*")
public class MessageListController {
    private MessageBoardService messageBoardService;

    @Autowired
    public void setMessageBoardService(MessageBoardService messageBoardService) {
        this.messageBoardService = messageBoardService;
    }

    @RequestMapping(method = RequestMethod.GET)
    public String generateList(Model model) {
        model.addAttribute("messages", messageBoardService.listMessage());
        return "messageList";
    }
}

然後就是創建界面:
/webapp/WEB-INF/jsp/messageList.jsp

 ....然後又添加了添加和刪除類。
 
 
 現在開始做我們保護URL的事情:
 
首先要攔截請求
使用Servlet過濾器,保護Web應用程序。

代理Spring Security的過濾器:
代理Servlet過濾器:
FilterToBeanFactory是一個特殊的Servlet過濾器,他本身做的工作並不多,而是將自己的工作委託給Spring應用程序上下文中的一個Bean來完成。(已被DelegatingFilterProxy取代)
 
   <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>

被委託的Bean幾乎和其他的Servlet過濾器一樣,實現Filter,但是他在Spring配置文件中不是web.xml中。
FilterChainProxy:
可以被配置來同時把幾個過濾器連接在一起。

看看上下文中的bean:

The order that filters are defined in the chain is very important. Irrespective of which filters you are actually
using, the order should be as follows:
1.ChannelProcessingFilter, because it might need to redirect to a different protocol
確保正通過HTTP或HTTPS發送一個請求

2.ConcurrentSessionFilter, because it doesn't use any SecurityContextHolder functionality
but needs to update the SessionRegistry to reflect ongoing requests from the principal
確保一個用戶沒有在設置的時間內同時登錄

3.SecurityContextPersistenceFilter,  so  a  SecurityContext  can  be  set  up  in  the
SecurityContextHolder  at  the  beginning  of  a  web  request,  and  any  changes  to  the
SecurityContext can be copied to the HttpSession when the web request ends (ready for use with
the next web request)

4.Authentication  processing  mechanisms  -  UsernamePasswordAuthenticationFilter,
CasAuthenticationFilter,  BasicAuthenticationFilter  etc  -  so  that  the
SecurityContextHolder can be modified to contain a valid Authentication request token

5.The SecurityContextHolderAwareRequestFilter,  if  you  are  using  it  to  install  a  Spring
Security aware HttpServletRequestWrapper into your servlet container
爲servlet請求增加一個請求外殼

6.The  JaasApiIntegrationFilter,  if  a  JaasAuthenticationToken  is  in  the
SecurityContextHolder  this  will  process  the  FilterChain  as  the  Subject  in  the
JaasAuthenticationToken

7.RememberMeAuthenticationFilter,  so  that  if  no  earlier  authentication  processing  mechanism
updated the SecurityContextHolder, and the request presents a cookie that enables remember-me
services to take place, a suitable remembered Authentication object will be put there

8.AnonymousAuthenticationFilter,  so  that  if  no  earlier  authentication  processing  mechanism
updated the SecurityContextHolder, an anonymous Authentication object will be put there

9.ExceptionTranslationFilter, to catch any Spring Security exceptions so that either an HTTP error
response can be returned or an appropriate AuthenticationEntryPoint can be launched

10.FilterSecurityInterceptor, to protect web URIs and raise exceptions when access is denied
充當安全攔截器角色,決定是否允許訪問某一受保護的資源
    
    
要加在SpringMVC的Servlet之前。    

然後,我們配置一個最簡單的控制訪問策略
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <!-- auto-config="true"典型的基本應用方式 -->
    <http auto-config="true">
        <intercept-url pattern="/messageList*" access="ROLE_USER,ROLE_GUEST" />
        <intercept-url pattern="/messagePost*" access="ROLE_USER" />
        <intercept-url pattern="/messageDelete*" access="ROLE_ADMIN" />
    </http>

    <authentication-manager>
        <authentication-provider>
            <user-service>
                <user name="admin" password="secret" authorities="ROLE_ADMIN,ROLE_USER" />
                <user name="user1" password="1111" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>


</beans:beans>
這樣就可以訪問了,這個配置Spring配置文件要放到web.xml的contextConfigLocation裏,
可以說下的是auto-config="true",已默認的策略進行訪問控制,包括登錄界面都會給你提供默認的,然後其中的另外兩個標籤沒有什麼好說的,一個是權限管理,一個是用戶管理。

也許你想說,我們一般登錄界面是自己寫的,然後用戶權限也是放到庫裏的....


2登錄到Web應用

自定義一個網頁作爲登錄界面、HTTP基本驗證、remember-me登錄等

去掉自動配置

<http>
    <intercept-url pattern="/messageList*" access="ROLE_USER,ROLE_GUEST" />
    <intercept-url pattern="/messagePost*" access="ROLE_USER" />
    <intercept-url pattern="/messageDelete*" access="ROLE_ADMIN" />
    <form-login login-page="/login.jsp" default-target-url="/messageList"
        authentication-failure-url="/login.jsp?error=true" />
    <logout logout-success-url="/login.jsp" />
    <remember-me />
</http>
HTTP基本驗證:<http-basic/>
基於表單的登錄:<form-login/>
登錄失敗:
authentication-failure-url="/login.jsp?error=true"
前臺展示${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}
註銷登錄:<logout logout-success-url="/login.jsp" />
<a href="<c:url value="/j_spring_security_logout" />">退出登錄</a>
匿名登錄:<anonymous username="guest" granted-authority="ROLE_GUEST"/>可以自定義匿名用戶的用戶名和權限
Remember-Me支持:<remember-me /> <input type="checkbox" name="_spring_security_remember_me" />


3驗證用戶

解決用戶:
採取了本地內存驗證和數據庫驗證,還有LDAP方式。還額外提供了緩存支持。

工作原理:

首先說新的簡化方式:

本地內存方式:

<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="admin" password="secret" authorities="ROLE_ADMIN,ROLE_USER" />
            <user name="user1" password="1111" authorities="ROLE_USER" />
        </user-service>
    </authentication-provider>
</authentication-manager>
properties方式:
<authentication-manager>
    <authentication-provider>
        <user-service properties="classpath:META-INF/spring/users.properties"/>
    </authentication-provider>
</authentication-manager>


依靠數據庫驗證用戶:
<jdbc-user-service data-source-ref="dataSource"/>
簡單方式:
CREATE TABLE `users` (
  `username` varchar(10) NOT NULL,
  `password` varchar(32) NOT NULL,
  `enabled` smallint(6) DEFAULT NULL,
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `authorities` (
  `username` varchar(10) NOT NULL,
  `authority` varchar(10) NOT NULL,
  KEY `[OwnerName]_fk[num_for_dup]` (`username`),
  CONSTRAINT `[OwnerName]_fk[num_for_dup]` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `users` (`username`, `password`, `enabled`) VALUES
  ('admin', 'secret', 1),
  ('user1', '1111', 1),
  ('user2', '2222', 1);
 
INSERT INTO `authorities` (`username`, `authority`) VALUES
  ('admin', 'ROLE_ADMIN'),
  ('user1', 'ROLE_USER'),
  ('admin', 'ROLE_USER'),
  ('user2', 'ROLE_USER');  

也可以自己制定數據庫:
<authentication-manager>
    <authentication-provider>
        <!--<user-service properties="classpath:META-INF/spring/users.properties"/> -->
        <jdbc-user-service data-source-ref="dataSource"
            users-by-username-query="
        select username,password,'true' as enabled from member where username=?"
            authorities-by-username-query="select m.username,mr.role as authorities from from member m,member_role mr where
            m.username=? and m.id = mr.member_id" />
    </authentication-provider>
</authentication-manager>
CREATE TABLE `member` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) NOT NULL,
  `password` varchar(32) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

CREATE TABLE `member_role` (
  `member_id` int(11) NOT NULL,
  `role` varchar(10) NOT NULL,
  KEY `member_id` (`member_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `member` (`id`, `username`, `password`) VALUES
  (1, 'admin', 'secret'),
  (2, 'user', '1111');

INSERT INTO `member_role` (`member_id`, `role`) VALUES
  (1, 'ROLE_ADMIN'),
  (1, 'ROLE_USER'),
  (2, 'ROLE_USER');  

爲了減少對數據庫的壓力還可以添加緩存:
    <bean id="cacheManager"
        class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache.xml" />
    </bean>
    <bean id="userEhCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="cacheManager" />
        <property name="cacheName" value="userCache" />
    </bean>
    <bean id="userCache"
        class="org.springframework.security.core.userdetails.cache.EhCacheBasedUserCache">
        <property name="cache" ref="userEhCache" />
    </bean>  

    <authentication-manager>
        <authentication-provider>
            <!--<password-encoder hash="md5"/> -->
            <!--<user-service properties="classpath:META-INF/spring/users.properties"/> -->
            <jdbc-user-service data-source-ref="dataSource"
                users-by-username-query="
            select username,password,'true' as enabled from member where username=?"
                authorities-by-username-query="select m.username,mr.role as authorities from member m,member_role mr where
                m.username=? and m.id = mr.member_id"
                cache-ref="userCache" />
        </authentication-provider>
    </authentication-manager>  

   
authentication-manager是一種簡單標籤方式,原始bean標籤方式爲:
(以下只做參考,可忽略)
一般會以登錄的方式提供身份認證,但是也會存在例外。

認證管理器負責確定用戶的身份,認證管理器由AuthenticationManager接口定義。
org.springframework.security.authentication.AuthenticationManager:
只有一個方法Authentication authenticate(Authentication authentication)
將會嘗試利用org.springframework.security.core.Authentication對象來驗證用戶身份。
如果認證成功,authenticate會返回一個完整的Authentication對象,其中包括用戶已被授予的權限信息,如果驗證失敗拋出AuthenticationException。

1、配置ProviderManager:
ProviderManager是認證管理器的一種實現,他將驗證身份的責任委託給一個或多個認證提供者。
ProviderManager的用途是使你能夠根絕多個身份管理源來認定用戶。

他不依靠自己實現認證,而是逐一認證提供者的集合,直到某一個認證提供者能夠成功的驗證該用戶的身份。
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
    <constructor-arg name="providers">
        <list>
            <ref bean=""/>
        </list>
    </constructor-arg>
</bean>    



列表值接口:org.springframework.security.authentication.AuthenticationProvider
AuthenticationProvider接口與前面的AuthenticationManager接口沒有什麼太大不同,他們都有一個處理認證authenticate()方法。實際上,可以把認證提供者看做是第二等級的認證管理器。

org.springframework.security.authentication.AnonymousAuthenticationProvider:
以匿名用戶方式驗證用戶。在即使用戶尚未登錄,扔需要用戶令牌時,會比較有用。

org.springframework.security.authentication.dao.DaoAuthenticationProvider:
從數據庫中獲取用戶信息,包括用戶名和密碼。

org.springframework.security.authentication.jaas.JaasAuthenticationProvider:
從JAAS登錄配置中獲取用戶信息。

org.springframework.security.authentication.RememberMeAuthenticationProvider:
驗證某一之前驗證過並且被記住的用戶的身份。這使得無需提示輸入用戶名和登錄即自動登錄某一用戶成爲可能。

org.springframework.security.authentication.rcp.RemoteAuthenticationProvider:
根據遠程服務驗證用戶身份。

org.springframework.security.authentication.TestingAuthenticationProvider:
用於單元測試。自動認爲一個TestingAuthenticationToken是有效的。不應用於生產環境。

org.springframework.security.access.intercept.RunAsImplAuthenticationProvider:
針對身份已經被運行身份管理器替換的用戶進行認證。


2、根據數據庫驗證身份
DaoAuthenticationProvider是一個簡單的認證提供者,他使用數據庫對象(DAO)來從關係數據庫中檢索用戶信息。
    <bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <property name="userDetailsService" ref=""/>
    </bean>    
這裏的userDetailsService屬性被用來指定將用於從數據庫中檢索用戶信息的那個Bean。
這個用戶期望org.springframework.security.core.userdetails.UserDetailsService的一個實例。
public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

一種方式使用內存Dao:
org.springframework.security.provisioning.InMemoryUserDetailsManager
Non-persistent implementation of UserDetailsManager which is backed by an in-memory map.
Mainly intended for testing and demonstration purposes, where a full blown persistent system isn't required.
非持久的UserDetailsManager的實施,這是內存中的地圖支持。
主要用於試驗和示範的目的,不需要在一個完全成熟的持久系統。


另一種方式 聲明一個JDBC DAO:
    <bean id="userDetailsService"
        class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
        <property name="dataSource" ref="dataSource" />
    </bean>

他內部假設了一些query語句:
    public static final String DEF_USERS_BY_USERNAME_QUERY =
            "select username,password,enabled " +
            "from users " +
            "where username = ?";
    public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
            "select username,authority " +
            "from authorities " +
            "where username = ?";
    public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
            "select g.id, g.group_name, ga.authority " +
            "from groups g, group_members gm, group_authorities ga " +
            "where gm.username = ? " +
            "and g.id = ga.group_id " +
            "and g.id = gm.group_id";

    private String authoritiesByUsernameQuery;
    private String groupAuthoritiesByUsernameQuery;
    private String usersByUsernameQuery;

那麼也就是這三個查詢有默認值,當然你可以可以自己配置。
    <bean id="userDetailsService"
        class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
        <property name="dataSource" ref="dataSource" />
        <property name="usersByUsernameQuery">
            <value>
                select email as username,password,enabled
                    from motorist
                    where email = ?
            </value>
        </property>
        <property name="authoritiesByUsernameQuery">
            <value>
                select email as username,privilege as authority  
                    from motorist_privileges mp,motorist m
                    where mp.motorist_id = m.id
                    and m.email = ?
            </value>
        </property>
    </bean>

使用加密的密碼:
DaoAuthenticationProvider中配置加密方式org.springframework.security.authentication.encoding.PasswordEncoder

org.springframework.security.authentication.encoding.Md5PasswordEncoder:
在密碼上執行“信息摘要”(MD5)編碼。
org.springframework.security.authentication.encoding.PlaintextPasswordEncoder:
在密碼上不執行任何編碼,照原樣返回他。
org.springframework.security.authentication.encoding.ShaPasswordEncode:
在密碼上執行“安全散列算法”(SHA)編碼。
org.springframework.security.authentication.encoding.LdapShaPasswordEncoder:
使用LDAP SHA和salted-SHA(SSHA)編碼技術編碼密碼。
    <bean id="daoAuthenticationProvider"
        class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <property name="userDetailsService" ref="userDetailsService" />
        <property name="passwordEncoder">
            <bean
                class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
        </property>
    </bean>

你還將需要爲編碼器設置一個種子源(salt source)。一個種子源爲所用的編碼技術提供種子(salt),或者稱加密密鑰。
org.springframework.security.authentication.dao.SaltSource:
org.springframework.security.authentication.dao.SystemWideSaltSource:對所有的用戶提供相同的種子。
org.springframework.security.authentication.dao.ReflectionSaltSource:利用用戶的User對象中某個指定屬性的反射來生成種子。
    <bean id="daoAuthenticationProvider"
        class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <property name="userDetailsService" ref="userDetailsService" />
        <property name="passwordEncoder">
            <bean
                class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
        </property>
        <property name="saltSource">
            <bean class="org.springframework.security.authentication.dao.ReflectionSaltSource">
                <property name="userPropertyToUse" value="userName"/>
            </bean>
        </property>
    </bean>



4做出訪問控制決策
 
問題:
在驗證過程中,應用將把一組權限授予成功驗證的用戶。
當這個用戶試圖訪問應用中的資源時,應用必須用授予的權限或者其他特性決定哪些資源可以訪問。

解決方案:
org.springframework.security.access.AccessDecisionManager:
訪問決策投票:
org.springframework.security.access.vote.AffirmativeBased:
只要有一個投票者投票贊成授予訪問權,就允許訪問。
org.springframework.security.access.vote.ConsensusBased:
只要大多數投票允許訪問,就允許訪問。
org.springframework.security.access.vote.UnanimousBased:
只有都贊成訪問的時候,纔可以訪問。

默認情況下如果不配置會給一個默認配置。

你還可以自定義訪問策略,只需要實現AccessDecisionManager。
    <!-- 決定如何投票 -->
    <bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter">
        <!-- 將只針對GROUP_爲前綴的權限進行授權投票,默認爲"ROLE_" -->
        <property name="rolePrefix" value="GROUP_" />
    </bean>

    <!-- 訪問決策管理器 -->
    <bean id="accessDecisionManager"
        class="org.springframework.security.access.vote.AffirmativeBased">
        <constructor-arg name="decisionVoters">
            <list>
                <ref bean="roleVoter" />
            </list>
        </constructor-arg>
        <!-- 沉默即同意的政策 -->
        <!--<property name="allowIfAllAbstainDecisions" value="true"/> -->
    </bean>


5加強方法調用的安全

問題:
有時候你可能想具體控制到某個方法上。

解決方案:

三種方式:
1、在Bean定義中嵌入一個<se:intercept-methods>,如:

<bean id="messageBoardService" class="com.partner4java.demo.service.MessageBoardServiceImpl">
    <se:intercept-methods access-decision-manager-ref="accessDecisionManager">
        <se:protect access="ROLE_USER,ROLE_GUEST" method="com.partner4java.demo.service.MessageBoardService.listMessage"/>
    </se:intercept-methods>
</bean>


2、利用AOP和Security提供的xml方式
<global-method-security access-decision-manager-ref="accessDecisionManager">
    <protect-pointcut access="ROLE_USER,ROLE_GUEST" expression="execution(* com.partner4java.demo.service.*Service.list*(..))"/>
    <protect-pointcut access="ROLE_USER" expression="execution(* com.partner4java.demo.service.*Service.post*(..))"/>
</global-method-security>

3、註解
@Override
@Secured({"ROLE_USER","ROLE_GUEST"})
public List<Message> listMessage() {
    return new LinkedList<Message>(messages.values());
}


6處理視圖中的安全性

問題:
有時,你可能希望在Web應用的視圖中顯示用的驗證信息,例如角色名稱和授權。此外,你還希望根據用戶授權有條件的顯示視圖內容。

解決方案:
Spring Security提供了一個JSP標記庫。

顯示驗證信息:
<security:authentication/>標記暴漏當前用戶的Authentication,供你顯示其屬性。
你可以在property屬性中指定屬性名稱和屬性路徑。

例如:<security:authentication property="name"/>顯示用戶的角色名稱。

<security:authentication property="authorities" var="authorities"/>
<ul>
    <c:forEach items="${authorities }" var="authority">
        <li>${authority.authority }</li>
    </c:forEach>
</ul>


遍歷權限。


有條件地顯示視圖內容:
如果你希望根據用戶授權有條件的顯示視圖內容,可以使用
<security:authorize ifAnyGranted="ROLE_ADMIN">
    親
</security:authorize>
還有ifAllGranted="" ifNotGranted="" ,如詞義。


7處理領域對象安全性

問題:
有時候,你可能有複雜的安全需求,需要在領域對象的級別上處理安全性。
這意味着你必須讓每個領域對象對不同的角色有不同的訪問屬性。


解決方案:
Spring Security提供一個名爲ACL的模塊,使每個領域對象都有自己的訪問控制列表(ACL)。
(簡單說,我們前面是保護方法和顯示塊,現在我們是來保護具體數據)
ACL包含一個與領域對象關聯的對象標識(object identity),還保存多個訪問控制項(Access Control Entries ,ACEs),每個控制項包含下面兩個核心部分。
·權限:ACE的權限由一個特別的位屏蔽代表,每位的值用於特定類型的權限。
BasePermission預先定義了5種基本權限的常量值供你使用:READ(讀,第0位或者整數1)、WRITE(寫,第1位或者整數2)、CREATE(創建,第2位或者整數4)、DELETE(刪除,第3位或者整數8)以及ADMINISTRATION(管理,第4位或者整數16)。你還可以自行定義其他未使用的位。
·安全標識(SID):每個ACE包含特定的SID的權限。SID可以是一個與權限關聯的角色(PrincipalSid)或者授權(GrantedAuthoritySid)。


工作原理:

設置ACL服務

兩個定義ACL服務操作的接口:AclService和MutableAclService。
AclService定義讀取ACL的操作。
MutableAclService是AclService的一個子接口,定義用於創建、更新和刪除ACL的操作。
http://blog.csdn.net/kongxx/article/details/5884352
http://hi.baidu.com/danghj/item/0f6be1c4ab95af7489ad9ed0

@Override
@Transactional
@Secured({ "ROLE_USER" })
public synchronized void postMessage(Message message) {
    message.setId(System.currentTimeMillis());
    messages.put(message.getId(), message);

    ObjectIdentity oid = new ObjectIdentityImpl(Message.class,
            message.getId());
    MutableAcl acl = mutableAclService.createAcl(oid);
    acl.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(
            message.getAuthor()), true);
    acl.insertAce(1, BasePermission.DELETE, new GrantedAuthoritySid("ROLE_ADMIN"), true);
    acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER"), true);
    mutableAclService.updateAcl(acl);
}

@Override
@Secured({ "ROLE_ADMIN","ACL_MESSAGE_DELETE" })
public synchronized void deleteMessage(Long id) {
    messages.remove(id);
    ObjectIdentity oid = new ObjectIdentityImpl(Message.class, id);
    mutableAclService.deleteAcl(oid, false);
}



爲領域對象維護ACL:
在你的後端服務和DAO中,你可以用前面通過依賴注入定義的ACL服務,爲領域對象維護ACL。
對於你的留言板,你必須在留言張貼時爲其創建ACL,並在留言刪除時刪除該ACL:

<!-- 標記查詢指定的領域對象ACL,檢查當前用戶有無指定的權限。這個標記只在用戶具有一個必要權限時顯示其主體信息。8,16代表前面說的DELETE(刪除,第3位或者整數8)以及ADMINISTRATION(管理,第4位或者整數16) -->
<security:accesscontrollist domainObject="${message}" hasPermission="8,16">
    <tr>
        <td colspan="2">
            <a href="messageDelete?messageId=${message.id}">Delete</a>
        </td>
    </tr>
</security:accesscontrollist>


但是上面標籤:AccessControlListTag裏的PermissionEvaluator報空指針,不知道爲什麼

過濾返回結果:
<bean id="afterAclRead" class="org.springframework.security.acls.afterinvocation.AclEntryAfterInvocationProvider">
    <security:after-invocation-provider/>
    <constructor-arg ref="aclService"/>
    <constructor-arg>
        <list>
            <util:constant
                static-field="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION" />
            <util:constant
                static-field="org.springframework.security.acls.domain.BasePermission.DELETE" />
        </list>
    </constructor-arg>
</bean>
<bean id="afterAclCollectionRead" class="org.springframework.security.acls.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider">
    <security:after-invocation-provider/>
    <constructor-arg ref="aclService"/>
    <constructor-arg>
        <list>
            <util:constant
                static-field="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION" />
            <util:constant
                static-field="org.springframework.security.acls.domain.BasePermission.DELETE" />
        </list>
    </constructor-arg>
</bean>


bean中嵌入一個<security:after-invocation-provider/>,就能註冊一個自定義後調用提供者.
然後就可以在權限組中加入AFTER_ACL_COLLECTION_READ和AFTER_ACL_READ到上面的權限註解中.
(後處理沒驗證)

源碼下在地址:http://download.csdn.net/detail/partner4java/4538585

發佈了183 篇原創文章 · 獲贊 20 · 訪問量 80萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章