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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值