spring security 2.X 配置文件小结(转)
2011-05-04 11:06
309 查看
Java代码:
# 一 、方式一:用户名密码都在配置文件中。
# <?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-2.0.xsd
# http://www.springframework.org/schema/security
# http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
#
# <http auto-config='true'>
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# </http>
#
# <authentication-provider>
# <user-service>
# <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
# <user name="user" password="user" authorities="ROLE_USER" />
# </user-service>
# </authentication-provider>
#
# </beans:beans>
#
#
# 二、方式二:运用spring security 提供的默认表结构,authentication部分修改如下:
# <authentication-provider>
# <jdbc-user-service data-source-ref="dataSource"/>
# </authentication-provider>
#
# <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
# <beans:property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
# <beans:property name="url" value="jdbc:hsqldb:res:/hsqldb/test"/>
# <beans:property name="username" value="sa"/>
# <beans:property name="password" value=""/>
# </beans:bean>
#
#
# 三、方式三:运用原先本地的数据库,authentication修改如下:
# <authentication-provider>
# <jdbc-user-service data-source-ref="dataSource"
# users-by-username-query="select username,password,status as enabled
# from user
# where username=?"
# authorities-by-username-query="select u.username,r.name as authority
# from user u
# join user_role ur
# on u.id=ur.user_id
# join role r
# on r.id=ur.role_id
# where u.username=?"/>
# </authentication-provider>
#
# 注:
# users-by-username-query为根据用户名查找用户,系统通过传入的用户名查询当前用户的登录名,密码和是否被禁用这一状态。
# authorities-by-username-query为根据用户名查找权限,系统通过传入的用户名查询当前用户已被授予的所有权限。
#
# 四、方式四;在方式三的基础上,修改登录页面
# 在xml中的http标签中添加一个form-login标签。
#
# <http auto-config='true'>
# <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />[1]
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# <form-login login-page="/login.jsp"[2]
# authentication-failure-url="/login.jsp?error=true"[3]
# default-target-url="/" />[4]
# </http>
#
#
# 1、让没登陆的用户也可以访问login.jsp。[1]
#
# 这是因为配置文件中的“/**”配置,要求用户访问任意一个系统资源时,必须拥有ROLE_USER角色,/login.jsp也不例外,如果我们不为/login.jsp单独配置访问权限,会造成用户连登陆的权限都没有,这是不正确的。
#
# 2、login-page表示用户登陆时显示我们自定义的login.jsp。[2]
#
# 这时我们访问系统显示的登陆页面将是我们上面创建的login.jsp。
#
# 3、authentication-failure-url表示用户登陆失败时,跳转到哪个页面。[3]
#
# 当用户输入的登录名和密码不正确时,系统将再次跳转到/login.jsp,并添加一个error=true参数作为登陆失败的标示。
#
#
# 4、default-target-url表示登陆成功时,跳转到哪个页面。[4]
#
# 五、方式五:使用数据配置角色可以访问的资源(控制到URL级别)(可以参见工程005)
# <!--从配置文件上可以看到,Spring Security所需的数据应该是一系列URL网址和访问这些网址所需的权限:
#
# <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
#
# SpringSecurity 所做的就是在系统初始化时,将以上XML中的信息转换为特定的数据格式,而框架中其他组件可以利用这些特定格式的数据,用于控制之后的验证操作。现在这些资源信息都保存在数据库中了,从数据库中取出数据,然后让它等同于从xml文件中取出,需要做以下几步。
# -->
# 1、实现一个符合该功能的类,主要参看该类中1-5步骤,了解即可。
# <!--
#
# public class JdbcFilterInvocationDefinitionSourceFactoryBean
# extends JdbcDaoSupport implements FactoryBean {
# private String resourceQuery;
#
# public boolean isSingleton() {
# return true;
# }
#
# public Class getObjectType() {
# return FilterInvocationDefinitionSource.class;
# }
# //4、 使用urlMatcher和requestMap创建DefaultFilterInvocationDefinitionSource。
# public Object getObject() {
# return new DefaultFilterInvocationDefinitionSource(this
# .getUrlMatcher(), this.buildRequestMap());
# }
# //2、这样我们可以执行它的execute()方法获得所有资源信息。并把它封装到map中。
#
# protected Map<String, String> findResources() {
# ResourceMapping resourceMapping = new ResourceMapping(getDataSource(),
# resourceQuery);
#
# Map<String, String> resourceMap = new LinkedHashMap<String, String>();
#
# for (Resource resource : (List<Resource>) resourceMapping.execute()) {
# String url = resource.getUrl();
# String role = resource.getRole();
#
# if (resourceMap.containsKey(url)) {
# String value = resourceMap.get(url);
# resourceMap.put(url, value + "," + role);
# } else {
# resourceMap.put(url, role);
# }
# }
#
# return resourceMap;
# }
#
#
# /* <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />*/
#
# //3、使用获得的资源信息组装requestMap。再把第二步封装到map中的方法进一步加工,使它等价于“如spring从上面模式的配置文件中读取一样”
# protected LinkedHashMap<RequestKey, ConfigAttributeDefinition> buildRequestMap() {
# LinkedHashMap<RequestKey, ConfigAttributeDefinition> requestMap = null;
# requestMap = new LinkedHashMap<RequestKey, ConfigAttributeDefinition>();
#
# ConfigAttributeEditor editor = new ConfigAttributeEditor();
#
# Map<String, String> resourceMap = this.findResources();
#
# for (Map.Entry<String, String> entry : resourceMap.entrySet()) {
# RequestKey key = new RequestKey(entry.getKey(), null);
# editor.setAsText(entry.getValue());
# requestMap.put(key,
# (ConfigAttributeDefinition) editor.getValue());
# }
#
# return requestMap;
# }
#
# protected UrlMatcher getUrlMatcher() {
# return new AntUrlPathMatcher();
# }
#
# public void setResourceQuery(String resourceQuery) {
# this.resourceQuery = resourceQuery;
# }
#
# private class Resource {
# private String url;
# private String role;
#
# public Resource(String url, String role) {
# this.url = url;
# this.role = role;
# }
#
# public String getUrl() {
# return url;
# }
#
# public String getRole() {
# return role;
# }
# }
# //1、我们通过定义一个MappingSqlQuery实现数据库操作。
# private class ResourceMapping extends MappingSqlQuery {
# protected ResourceMapping(DataSource dataSource,
# String resourceQuery) {
# super(dataSource, resourceQuery);
# compile();
# }
#
# protected Object mapRow(ResultSet rs, int rownum)
# throws SQLException {
# String url = rs.getString(1);
# String role = rs.getString(2);
# Resource resource = new Resource(url, role);
#
# return resource;
# }
# }
# }
#
# -->
# 2、替换原有功能的切入点
# 在spring中配置我们编写的代码。
#
# <beans:bean id="filterInvocationDefinitionSource"
# class="com.family168.springsecuritybook.ch005.JdbcFilterInvocationDefinitionSourceFactoryBean">
# <beans:property name="dataSource" ref="dataSource"/>
# <beans:property name="resourceQuery" value="
# select re.res_string,r.name
# from role r
# join resc_role rr
# on r.id=rr.role_id
# join resc re
# on re.id=rr.resc_id
# order by priority
# "/>
# </beans:bean>
#
# 下一步使用这个filterInvocationDefinitionSource创建filterSecurityInterceptor,并使用它替换系统原来创建的那个过滤器。
#
# <beans:bean id="filterSecurityInterceptor"
# class="org.springframework.security.intercept.web.FilterSecurityInterceptor" autowire="byType">
# <custom-filter before="FILTER_SECURITY_INTERCEPTOR" />
# <beans:property name="objectDefinitionSource" ref="filterInvocationDefinitionSource" />
# </beans:bean>
#
# 注意这个custom-filter标签,它表示将filterSecurityInterceptor放在框架原来的 FILTER_SECURITY_INTERCEPTOR过滤器之前,这样我们的过滤器会先于原来的过滤器执行,因为它的功能与老过滤器完全一样,所以这就等于把原来的过滤器替换掉了。
#
# 3、完整的配置文件如下所示:
#
# <?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-2.0.xsd
# http://www.springframework.org/schema/security
# http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
#
# <http auto-config="true"/>
#
# <authentication-provider>
# <jdbc-user-service data-source-ref="dataSource"
# users-by-username-query="select username,password,status as enabled
# from user
# where username=?"
# authorities-by-username-query="select u.username,r.name as authority
# from user u
# join user_role ur
# on u.id=ur.user_id
# join role r
# on r.id=ur.role_id
# where u.username=?"/>
# </authentication-provider>
#
# <beans:bean id="filterSecurityInterceptor"
# class="org.springframework.security.intercept.web.FilterSecurityInterceptor" autowire="byType">
# <custom-filter before="FILTER_SECURITY_INTERCEPTOR" />
# <beans:property name="objectDefinitionSource" ref="filterInvocationDefinitionSource" />
# </beans:bean>
#
# <beans:bean id="filterInvocationDefinitionSource"
# class="com.family168.springsecuritybook.ch05.JdbcFilterInvocationDefinitionSourceFactoryBean">
# <beans:property name="dataSource" ref="dataSource"/>
# <beans:property name="resourceQuery" value="
# select re.res_string,r.name
# from role r
# join resc_role rr
# on r.id=rr.role_id
# join resc re
# on re.id=rr.resc_id
# order by priority
# "/>
# </beans:bean>
#
# <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
# <beans:property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
# <beans:property name="url" value="jdbc:hsqldb:res:/hsqldb/test"/>
# <beans:property name="username" value="sa"/>
# <beans:property name="password" value=""/>
# </beans:bean>
# </beans:beans>
#
#
# 4、允许动态增加某个用户权限。
# 目前存在的问题是,系统会在初始化时一次将所有资源加载到内存中,即使在数据库中修改了资源信息,系统也不会再次去从数据库中读取资源信息。这就造成了每次修改完数据库后,都需要重启系统才能时资源配置生效。
#
# 解决方案是,如果数据库中的资源出现的变化,需要刷新内存中已加载的资源信息时,使用下面代码:
#
# <%@page import="org.springframework.context.ApplicationContext"%>
# <%@page import="org.springframework.web.context.support.WebApplicationContextUtils"%>
# <%@page import="org.springframework.beans.factory.FactoryBean"%>
# <%@page import="org.springframework.security.intercept.web.FilterSecurityInterceptor"%>
# <%@page import="org.springframework.security.intercept.web.FilterInvocationDefinitionSource"%>
# <%
# ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(application);
# FactoryBean factoryBean = (FactoryBean) ctx.getBean("&filterInvocationDefinitionSource");
# FilterInvocationDefinitionSource fids = (FilterInvocationDefinitionSource) factoryBean.getObject();
# FilterSecurityInterceptor filter = (FilterSecurityInterceptor) ctx.getBean("filterSecurityInterceptor");
# filter.setObjectDefinitionSource(fids);
# %>
# <jsp:forward page="/"/>
# 六、为了使用MD5对密码加密,我们需要修改一下配置文件。
# 任何一个正式的企业应用中,都不会在数据库中使用明文来保存密码的,我们在之前的章节中都是为了方便起见没有对数据库中的用户密码进行加密,这在实际应用中是极为幼稚的做法。可以想象一下,只要有人进入数据库就可以看到所有人的密码,这是一件多么恐怖的事情,为此我们至少要对密码进行加密,这样即使数据库被攻破,也可以保证用户密码的安全。
# 最常用的方法是使用MD5算法对密码进行摘要加密,这是一种单项加密手段,无法通过加密后的结果反推回原来的密码明文。
#
# <authentication-provider>
# <password-encoder hash="md5"/>
# <jdbc-user-service data-source-ref="dataSource"/>
# </authentication-provider>
#
# 盐值加密
# <authentication-provider>
# <password-encoder hash="md5">
# <salt-source user-property="username"/>
# </password-encoder>
# <jdbc-user-service data-source-ref="dataSource"/>
# </authentication-provider>
#
# 在password-encoder下添加了salt-source,并且指定使用username作为盐值。
# 盐值的原理非常简单,就是先把密码和盐值指定的内容合并在一起,再使用md5对合并后的内容进行演算,这样一来,就算密码是一个很常见的字符串,再加上用户名,最后算出来的md5值就没那么容易猜出来了。因为攻击者不知道盐值的值,也很难反算出密码原文。
#
# 七、 用户信息缓存
# 介于系统的用户信息并不会经常改变,因此使用缓存就成为了提升性能的一个非常好的选择。Spring Security内置的缓存实现是基于ehcache的,为了启用缓存功能,我们要在配置文件中添加相关的内容。
#
# <authentication-provider>
# <password-encoder hash="md5">
# <salt-source user-property="username"/>
# </password-encoder>
# <jdbc-user-service data-source-ref="dataSource" cache-ref="userCache"/>
# </authentication-provider>
#
# 我们在jdbc-user-service部分添加了对userCache的引用,它将使用这个bean作为用户权限缓存的实现。对userCache的配置如下所示:
#
# <beans:bean id="userCache" class="org.springframework.security.providers.dao.cache.EhCacheBasedUserCache">
# <beans:property name="cache" ref="userEhCache"/>
# </beans:bean>
#
# <beans:bean id="userEhCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
# <beans:property name="cacheManager" ref="cacheManager"/>
# <beans:property name="cacheName" value="userCache"/>
# </beans:bean>
#
# <beans:bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
#
# EhCacheBasedUserCache 是Spring Security内置的缓存实现,它将为jdbc-user-service提供缓存功能。它所引用的userEhCache来自 spring提供的EhCacheFactoryBean和EhCacheManagerFactoryBean,对于userCache的缓存配置放在 ehcache.xml中:
#
# <ehcache>
# <diskStore path="java.io.tmpdir"/>
#
# <defaultCache
# maxElementsInMemory="1000"
# eternal="false"
# timeToIdleSeconds="120"
# timeToLiveSeconds="120"
# overflowToDisk="true"
# />
#
# <cache
# name="userCache"
# maxElementsInMemory="100"
# eternal="false"
# timeToIdleSeconds="600"
# timeToLiveSeconds="3600"
# overflowToDisk="true"
# />
# </ehcache>
#
# cache解释:
# 内存中最多存放100个对象。
# 不是永久缓存。
# 最大空闲时间为600秒。
# 最大活动时间为3600秒。
# 如果内存对象溢出则保存到磁盘。
#
# 八、获取当前用户信息
# 如果只是想从页面上显示当前登陆的用户名,可以直接使用Spring Security提供的taglib。
#
# <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
# <div>username : <sec:authentication property="name"/></div>
#
# 如果想在程序中获得当前登陆用户对应的对象。
#
# UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
# .getAuthentication()
# .getPrincipal();
#
# 如果想获得当前登陆用户所拥有的所有权限。
#
# GrantedAuthority[] authorities = userDetails.getAuthorities();
#
# 关于UserDetails是如何放到SecuirtyContext中去的,以及Spring Security所使用的TheadLocal模式,我们会在后面详细介绍。这里我们已经了解了如何获得当前登陆用户的信息。
#
# 九、自定义访问拒绝页面
# 在我们的例子中,user用户是不能访问/admin.jsp页面的,当我们使用user用户登录系统之后,访问/admin.jsp时系统默认会返回403响应。
# 如果我们希望自定义访问拒绝页面,只需要随便创建一个jsp页面,让后将这个页面的位置放到配置文件中。
#
# 下面创建一个accessDenied.jsp
#
# <%@ page contentType="text/html;charset=UTF-8"%>
# <html>
# <head>
# <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
# <title>Access Denied</title>
# <style type="text/css">
# div.error {
# width: 260px;
# border: 2px solid red;
# background-color: yellow;
# text-align: center;
# }
# </style>
# </head>
# <body>
# <h1>Access Denied</h1>
# <hr>
# <div class="error">
# 访问被拒绝<br>
# ${requestScope['SPRING_SECURITY_403_EXCEPTION'].message}
# </div>
# <hr>
# </body>
# </html>
#
#
# 下一步修改配置文件,添加自定义访问拒绝页面的地址。
#
# <http auto-config='true' access-denied-page="/accessDenied.jsp">
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# </http>
#
# 十、动态管理资源结合自定义登录页面
# 如果想将动态管理资源与自定义登录页面一起使用,最简单的办法就是在数据库中将登录页面对应的权限设置为IS_AUTHENTICATED_ANONYMOUSLY。
#
# 因此在数据库中添加一条资源信息。
#
# INSERT INTO RESC VALUES(1,'','URL','/login.jsp*',1,'')
#
# 这里的/login.jsp*就是我们自定义登录页面的地址。
#
# 然后为匿名用户添加一条角色信息:
#
# INSERT INTO ROLE VALUES(3,'IS_AUTHENTICATED_ANONYMOUSLY','anonymous')
#
# 最后为这两条记录进行关联即可。
#
# INSERT INTO RESC_ROLE VALUES(1,3)
#
# 这样就实现了将动态管理资源与自定义登录页面进行结合。
#
#
# 十一、后登陆的将先登录的踢出系统 vs 后面的用户禁止登陆
# 默认情况下,后登陆的用户会把先登录的用户踢出系统。
# 想测试一下的话,先打开firefox使用user/user登陆系统,然后再打开ie使用user/user登陆系统。这时ie下的user用户会登陆成功,进入登陆成功页面。而firefox下的用户如何刷新页面,就会显示如下信息:
# This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).
# 这是因为先登录的用户已经被强行踢出了系统,如果他再次使用user/user登陆,ie下的用户也会被踢出系统了。
#
#
# 后面的用户禁止登陆
# 如果不想让之前登录的用户被自动踢出系统,需要为concurrent-session-control设置一个参数。
# <http auto-config='true'>
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# <concurrent-session-control exception-if-maximum-exceeded="true"/>
# </http>
#
# 这个参数用来控制是否在会话数目超过最大限制时抛出异常,默认值是false,也就是不抛出异常,而是把之前的session都销毁掉,所以之前登陆的用户就会被踢出系统了。
#
# 现在我们把这个参数改为true,再使用同一个账号同时登陆一下系统,看看会发生什么现象。
# 很好,现在只要有一个人使用user/user登陆过系统,其他人就不能再次登录了。这样可能出现一个问题,如果有人登陆的时候因为某些问题没有进行logout就退出了系统,那么他只能等到session过期自动销毁之后,才能再次登录系统。
#
# 十二、单点登录;略。
#
# 十三、为不同用户显示各自的登陆成功页面
# 一个常见的需求是,普通用户登录之后显示普通用户的工作台,管理员登陆之后显示后台管理页面。这个功能可以使用taglib解决。
# 其实只要在登录成功后的jsp页面中使用taglib判断当前用户拥有的权限进行跳转就可以。
# <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
# <sec:authorize ifAllGranted="ROLE_ADMIN">
# <%response.sendRedirect("admin.jsp");%>
# </sec:authorize>
# <sec:authorize ifNotGranted="ROLE_ADMIN">
# <%response.sendRedirect("user.jsp");%>
# </sec:authorize>
# 当用户拥有ROLE_ADMIN权限时,既跳转到admin.jsp显示管理后台。
# 当用户没有ROLE_ADMIN权限时,既跳转到user.jsp显示普通用户工作台。
# 这里我们只做最简单的判断,只区分当前用户是否为管理员。可以根据实际情况做更加复杂的跳转,当用户具有不同权限时,跳到对应的页面,甚至可以根据用户username跳转到各自的页面
#
# 十四、匿名登录
# 匿名登录,即用户尚未登录系统,系统会为所有未登录的用户分配一个匿名用户,这个用户也拥有自己的权限,不过他是不能访问任何被保护资源的。
#
# 设置一个匿名用户的好处是,我们在进行权限判断时,可以保证SecurityContext中永远是存在着一个权限主体的,启用了匿名登录功能之后,我们所需要做的工作就是从SecurityContext中取出权限主体,然后对其拥有的权限进行校验,不需要每次去检验这个权限主体是否为空了。这样做的好处是我们永远认为请求的主体是拥有权限的,即便他没有登录,系统也会自动为他赋予未登录系统角色的权限,这样后面所有的安全组件都只需要在当前权限主体上进行处理,不用一次一次的判断当前权限主体是否存在。这就更容易保证系统中操作的一致性。
#
# 配置文件
# 在配置文件中使用auto-config="true"就会启用匿名登录功能。在启用匿名登录之后,如果我们希望允许未登录就可以访问一些资源,可以在进行如下配置。
#
# <http auto-config='true'>
# <intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY" />
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# </http>
#
# 在access中指定IS_AUTHENTICATED_ANONYMOUSLY后,系统就知道此资源可以被匿名用户访问了。当未登录时访问系统的“/”,就会被自动赋以匿名用户的身份。我们可以使用taglib获得用户的权限主体信息。
#
# 这里的IS_AUTHENTICATED_ANONYMOUSLY将会交由AuthenticatedVoter处理,内部会依据AuthenticationTrustResolver判断当前登录的用户是否是匿名用户。
#
# <div>
# username : <sec:authentication property="name"/>
# |
# authorities: <sec:authentication property="authorities" var="authorities" scope="page"/>
# <c:forEach items="${authorities}" var="authority">
# ${authority.authority}
# </c:forEach>
# </div>
#
# 当用户访问系统时,就会看到如下信息,这时他还没有进行登录。
# 这里显示的是分配给所有未登录用户的一个默认用户名roleAnonyMous,拥有的权限是ROLE_ANONYMOUS。我们可以看到系统已经把匿名用户当做了一个合法有效的用户进行处理,可以获得它的用户名和拥有的权限,而不需判断SecurityContext中是否为空。
#
# 实际上,我们完全可以把匿名用户像一个正常用户那样进行配置,我们可以在配置文件中直接使用ROLE_ANONYMOUS指定它可以访问的资源。
#
# <http auto-config='true'>
# <intercept-url pattern="/" access="ROLE_ANONYMOUS" />
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# </http>
#
# 不过,为了更明显的将匿名用户与系统中的其他用户区分开,我们推荐在配置时尽量使用IS_AUTHENTICATED_ANONYMOUSLY来指定匿名用户可以访问的资源。
#
#
# 十五、使用JAAS机制
# 可以在Spring Security中使用JAAS机制进行用户的身份认证。
#
# JAAS 即Java Authentication and Authorization Service,它是JDK自带的一套专门用于处理用户认证和授权的标准API,Spring Security中可以使用API作为AuthenticationProvider处理用户认证与授权。
#
# 配置文件中,我们使用JaasAuthenticationProvider作为AuthenticationProvider。
#
# <http>
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# <form-login/>
# <logout/>
# </http>
#
# <beans:bean id="jaasAuthenticationProvider"
# class="org.springframework.security.providers.jaas.JaasAuthenticationProvider">
# <custom-authentication-provider/>
# <beans:property name="loginConfig" value="/WEB-INF/login.conf" />
# <beans:property name="loginContextName" value="JAASTest" />
# <beans:property name="callbackHandlers">
# <beans:list>
# <beans:bean class="org.springframework.security.providers.jaas.JaasNameCallbackHandler" />
# <beans:bean class="org.springframework.security.providers.jaas.JaasPasswordCallbackHandler" />
# </beans:list>
# </beans:property>
# <beans:property name="authorityGranters">
# <beans:list>
# <beans:bean class="com.family168.springsecuritybook.ch117.AuthorityGranterImpl" />
# </beans:list>
# </beans:property>
# </beans:bean>
#
# 注意不能在http标签中使用auto-config="true"或是在http标签中包含rememberMe,因为rememberMe需要引用userDetailsService,而在使用 JaasAuthenticationProvider时,用户数据校验是交由LoginModule处理的,不会使用 userDetailsService,所以rememberMe会抛出异常。
#
# 我们将JAAS所需的配置文件放在/WEB-INF/login.config。
#
# JAASTest {
# com.family168.springsecuritybook.ch117.LoginModuleImpl required;
# };
#
# 并在配置文件中指明使用JAASTest作为登陆上下文。
#
# <beans:property name="loginContextName" value="JAASTest" />
#
# 现在要创建LoginModuleImpl用来处理用户登录。
# package com.family168.springsecuritybook.ch117;
# public class LoginModuleImpl implements LoginModule {
#
# private String password;
# private String user;
# private Subject subject;
#
# public boolean abort() throws LoginException {
# return true;
# }
#
# public boolean commit() throws LoginException {
# return true;
# }
#
# public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
# this.subject = subject;
#
# try {
# TextInputCallback textCallback = new TextInputCallback("prompt");
# NameCallback nameCallback = new NameCallback("prompt");
# PasswordCallback passwordCallback = new PasswordCallback("prompt", false);
#
# callbackHandler.handle(new Callback[] {textCallback, nameCallback, passwordCallback});
#
# password = new String(passwordCallback.getPassword());
# user = nameCallback.getName();
# } catch (Exception e) {
# throw new RuntimeException(e);
# }
# }
#
# public boolean login() throws LoginException {
# if (!user.equals("user")) {
# throw new LoginException("Bad User");
# }
#
# if (!password.equals("user")) {
# throw new LoginException("Bad Password");
# }
#
# subject.getPrincipals().add(new Principal() {
# public String getName() {
# return "TEST_PRINCIPAL";
# }
# });
#
# subject.getPrincipals().add(new Principal() {
# public String getName() {
# return "NULL_PRINCIPAL";
# }
# });
#
# return true;
# }
#
# public boolean logout() throws LoginException {
# return true;
# }
# }
#
# 当用户登录成功时,会通过authorityGranters为权限主体授权,这一步也要自己实现AuthorityGranter接口。
#
#
# package com.family168.springsecuritybook.ch117;
#
# import java.security.Principal;
# import java.util.HashSet;
# import java.util.Set;
# import org.springframework.security.providers.jaas.AuthorityGranter;
#
# public class AuthorityGranterImpl implements AuthorityGranter {
# public Set grant(Principal principal) {
# Set rtnSet = new HashSet();
#
# if (principal.getName().equals("TEST_PRINCIPAL")) {
# rtnSet.add("ROLE_USER");
# rtnSet.add("ROLE_ADMIN");
# }
#
# return rtnSet;
# }
# }
#
# 至此,JAAS与Spring Security结合进行认证授权的功能已经完成,每一步都要件功能写死在代码里,让人感觉很不舒服。
#
#
#
# 十六、保存登录之前的请求
# 经常会碰到一种情况,用户花费大量时间编辑信息,但是session超时失效导致用户自动退出系统,安全过滤器会强制用户再次登录,但这也会使用户提交的信息全部丢失。
#
# 为了解决这个问题,Spring Security提供了一种称作SavedRequest功能,可以在未登录用户访问资源时,将用户请求保存起来,当用户登录成功之后SecurityContextHolderAwareRequestFilter会使用之前保存的请求,结合当前用户的请求生成一个新的请求对象,而这个请求对象中就保存了用户登录之前提交的信息。
#
# SavedRequest功能默认就会被Spring Security启用,不需任何配置就可以重用登陆之前请求提交的数据。
#
转自:http://zfsn.iteye.com/blog/506682
# 一 、方式一:用户名密码都在配置文件中。
# <?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-2.0.xsd
# http://www.springframework.org/schema/security
# http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
#
# <http auto-config='true'>
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# </http>
#
# <authentication-provider>
# <user-service>
# <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
# <user name="user" password="user" authorities="ROLE_USER" />
# </user-service>
# </authentication-provider>
#
# </beans:beans>
#
#
# 二、方式二:运用spring security 提供的默认表结构,authentication部分修改如下:
# <authentication-provider>
# <jdbc-user-service data-source-ref="dataSource"/>
# </authentication-provider>
#
# <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
# <beans:property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
# <beans:property name="url" value="jdbc:hsqldb:res:/hsqldb/test"/>
# <beans:property name="username" value="sa"/>
# <beans:property name="password" value=""/>
# </beans:bean>
#
#
# 三、方式三:运用原先本地的数据库,authentication修改如下:
# <authentication-provider>
# <jdbc-user-service data-source-ref="dataSource"
# users-by-username-query="select username,password,status as enabled
# from user
# where username=?"
# authorities-by-username-query="select u.username,r.name as authority
# from user u
# join user_role ur
# on u.id=ur.user_id
# join role r
# on r.id=ur.role_id
# where u.username=?"/>
# </authentication-provider>
#
# 注:
# users-by-username-query为根据用户名查找用户,系统通过传入的用户名查询当前用户的登录名,密码和是否被禁用这一状态。
# authorities-by-username-query为根据用户名查找权限,系统通过传入的用户名查询当前用户已被授予的所有权限。
#
# 四、方式四;在方式三的基础上,修改登录页面
# 在xml中的http标签中添加一个form-login标签。
#
# <http auto-config='true'>
# <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />[1]
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# <form-login login-page="/login.jsp"[2]
# authentication-failure-url="/login.jsp?error=true"[3]
# default-target-url="/" />[4]
# </http>
#
#
# 1、让没登陆的用户也可以访问login.jsp。[1]
#
# 这是因为配置文件中的“/**”配置,要求用户访问任意一个系统资源时,必须拥有ROLE_USER角色,/login.jsp也不例外,如果我们不为/login.jsp单独配置访问权限,会造成用户连登陆的权限都没有,这是不正确的。
#
# 2、login-page表示用户登陆时显示我们自定义的login.jsp。[2]
#
# 这时我们访问系统显示的登陆页面将是我们上面创建的login.jsp。
#
# 3、authentication-failure-url表示用户登陆失败时,跳转到哪个页面。[3]
#
# 当用户输入的登录名和密码不正确时,系统将再次跳转到/login.jsp,并添加一个error=true参数作为登陆失败的标示。
#
#
# 4、default-target-url表示登陆成功时,跳转到哪个页面。[4]
#
# 五、方式五:使用数据配置角色可以访问的资源(控制到URL级别)(可以参见工程005)
# <!--从配置文件上可以看到,Spring Security所需的数据应该是一系列URL网址和访问这些网址所需的权限:
#
# <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
#
# SpringSecurity 所做的就是在系统初始化时,将以上XML中的信息转换为特定的数据格式,而框架中其他组件可以利用这些特定格式的数据,用于控制之后的验证操作。现在这些资源信息都保存在数据库中了,从数据库中取出数据,然后让它等同于从xml文件中取出,需要做以下几步。
# -->
# 1、实现一个符合该功能的类,主要参看该类中1-5步骤,了解即可。
# <!--
#
# public class JdbcFilterInvocationDefinitionSourceFactoryBean
# extends JdbcDaoSupport implements FactoryBean {
# private String resourceQuery;
#
# public boolean isSingleton() {
# return true;
# }
#
# public Class getObjectType() {
# return FilterInvocationDefinitionSource.class;
# }
# //4、 使用urlMatcher和requestMap创建DefaultFilterInvocationDefinitionSource。
# public Object getObject() {
# return new DefaultFilterInvocationDefinitionSource(this
# .getUrlMatcher(), this.buildRequestMap());
# }
# //2、这样我们可以执行它的execute()方法获得所有资源信息。并把它封装到map中。
#
# protected Map<String, String> findResources() {
# ResourceMapping resourceMapping = new ResourceMapping(getDataSource(),
# resourceQuery);
#
# Map<String, String> resourceMap = new LinkedHashMap<String, String>();
#
# for (Resource resource : (List<Resource>) resourceMapping.execute()) {
# String url = resource.getUrl();
# String role = resource.getRole();
#
# if (resourceMap.containsKey(url)) {
# String value = resourceMap.get(url);
# resourceMap.put(url, value + "," + role);
# } else {
# resourceMap.put(url, role);
# }
# }
#
# return resourceMap;
# }
#
#
# /* <intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />*/
#
# //3、使用获得的资源信息组装requestMap。再把第二步封装到map中的方法进一步加工,使它等价于“如spring从上面模式的配置文件中读取一样”
# protected LinkedHashMap<RequestKey, ConfigAttributeDefinition> buildRequestMap() {
# LinkedHashMap<RequestKey, ConfigAttributeDefinition> requestMap = null;
# requestMap = new LinkedHashMap<RequestKey, ConfigAttributeDefinition>();
#
# ConfigAttributeEditor editor = new ConfigAttributeEditor();
#
# Map<String, String> resourceMap = this.findResources();
#
# for (Map.Entry<String, String> entry : resourceMap.entrySet()) {
# RequestKey key = new RequestKey(entry.getKey(), null);
# editor.setAsText(entry.getValue());
# requestMap.put(key,
# (ConfigAttributeDefinition) editor.getValue());
# }
#
# return requestMap;
# }
#
# protected UrlMatcher getUrlMatcher() {
# return new AntUrlPathMatcher();
# }
#
# public void setResourceQuery(String resourceQuery) {
# this.resourceQuery = resourceQuery;
# }
#
# private class Resource {
# private String url;
# private String role;
#
# public Resource(String url, String role) {
# this.url = url;
# this.role = role;
# }
#
# public String getUrl() {
# return url;
# }
#
# public String getRole() {
# return role;
# }
# }
# //1、我们通过定义一个MappingSqlQuery实现数据库操作。
# private class ResourceMapping extends MappingSqlQuery {
# protected ResourceMapping(DataSource dataSource,
# String resourceQuery) {
# super(dataSource, resourceQuery);
# compile();
# }
#
# protected Object mapRow(ResultSet rs, int rownum)
# throws SQLException {
# String url = rs.getString(1);
# String role = rs.getString(2);
# Resource resource = new Resource(url, role);
#
# return resource;
# }
# }
# }
#
# -->
# 2、替换原有功能的切入点
# 在spring中配置我们编写的代码。
#
# <beans:bean id="filterInvocationDefinitionSource"
# class="com.family168.springsecuritybook.ch005.JdbcFilterInvocationDefinitionSourceFactoryBean">
# <beans:property name="dataSource" ref="dataSource"/>
# <beans:property name="resourceQuery" value="
# select re.res_string,r.name
# from role r
# join resc_role rr
# on r.id=rr.role_id
# join resc re
# on re.id=rr.resc_id
# order by priority
# "/>
# </beans:bean>
#
# 下一步使用这个filterInvocationDefinitionSource创建filterSecurityInterceptor,并使用它替换系统原来创建的那个过滤器。
#
# <beans:bean id="filterSecurityInterceptor"
# class="org.springframework.security.intercept.web.FilterSecurityInterceptor" autowire="byType">
# <custom-filter before="FILTER_SECURITY_INTERCEPTOR" />
# <beans:property name="objectDefinitionSource" ref="filterInvocationDefinitionSource" />
# </beans:bean>
#
# 注意这个custom-filter标签,它表示将filterSecurityInterceptor放在框架原来的 FILTER_SECURITY_INTERCEPTOR过滤器之前,这样我们的过滤器会先于原来的过滤器执行,因为它的功能与老过滤器完全一样,所以这就等于把原来的过滤器替换掉了。
#
# 3、完整的配置文件如下所示:
#
# <?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-2.0.xsd
# http://www.springframework.org/schema/security
# http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">
#
# <http auto-config="true"/>
#
# <authentication-provider>
# <jdbc-user-service data-source-ref="dataSource"
# users-by-username-query="select username,password,status as enabled
# from user
# where username=?"
# authorities-by-username-query="select u.username,r.name as authority
# from user u
# join user_role ur
# on u.id=ur.user_id
# join role r
# on r.id=ur.role_id
# where u.username=?"/>
# </authentication-provider>
#
# <beans:bean id="filterSecurityInterceptor"
# class="org.springframework.security.intercept.web.FilterSecurityInterceptor" autowire="byType">
# <custom-filter before="FILTER_SECURITY_INTERCEPTOR" />
# <beans:property name="objectDefinitionSource" ref="filterInvocationDefinitionSource" />
# </beans:bean>
#
# <beans:bean id="filterInvocationDefinitionSource"
# class="com.family168.springsecuritybook.ch05.JdbcFilterInvocationDefinitionSourceFactoryBean">
# <beans:property name="dataSource" ref="dataSource"/>
# <beans:property name="resourceQuery" value="
# select re.res_string,r.name
# from role r
# join resc_role rr
# on r.id=rr.role_id
# join resc re
# on re.id=rr.resc_id
# order by priority
# "/>
# </beans:bean>
#
# <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
# <beans:property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
# <beans:property name="url" value="jdbc:hsqldb:res:/hsqldb/test"/>
# <beans:property name="username" value="sa"/>
# <beans:property name="password" value=""/>
# </beans:bean>
# </beans:beans>
#
#
# 4、允许动态增加某个用户权限。
# 目前存在的问题是,系统会在初始化时一次将所有资源加载到内存中,即使在数据库中修改了资源信息,系统也不会再次去从数据库中读取资源信息。这就造成了每次修改完数据库后,都需要重启系统才能时资源配置生效。
#
# 解决方案是,如果数据库中的资源出现的变化,需要刷新内存中已加载的资源信息时,使用下面代码:
#
# <%@page import="org.springframework.context.ApplicationContext"%>
# <%@page import="org.springframework.web.context.support.WebApplicationContextUtils"%>
# <%@page import="org.springframework.beans.factory.FactoryBean"%>
# <%@page import="org.springframework.security.intercept.web.FilterSecurityInterceptor"%>
# <%@page import="org.springframework.security.intercept.web.FilterInvocationDefinitionSource"%>
# <%
# ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(application);
# FactoryBean factoryBean = (FactoryBean) ctx.getBean("&filterInvocationDefinitionSource");
# FilterInvocationDefinitionSource fids = (FilterInvocationDefinitionSource) factoryBean.getObject();
# FilterSecurityInterceptor filter = (FilterSecurityInterceptor) ctx.getBean("filterSecurityInterceptor");
# filter.setObjectDefinitionSource(fids);
# %>
# <jsp:forward page="/"/>
# 六、为了使用MD5对密码加密,我们需要修改一下配置文件。
# 任何一个正式的企业应用中,都不会在数据库中使用明文来保存密码的,我们在之前的章节中都是为了方便起见没有对数据库中的用户密码进行加密,这在实际应用中是极为幼稚的做法。可以想象一下,只要有人进入数据库就可以看到所有人的密码,这是一件多么恐怖的事情,为此我们至少要对密码进行加密,这样即使数据库被攻破,也可以保证用户密码的安全。
# 最常用的方法是使用MD5算法对密码进行摘要加密,这是一种单项加密手段,无法通过加密后的结果反推回原来的密码明文。
#
# <authentication-provider>
# <password-encoder hash="md5"/>
# <jdbc-user-service data-source-ref="dataSource"/>
# </authentication-provider>
#
# 盐值加密
# <authentication-provider>
# <password-encoder hash="md5">
# <salt-source user-property="username"/>
# </password-encoder>
# <jdbc-user-service data-source-ref="dataSource"/>
# </authentication-provider>
#
# 在password-encoder下添加了salt-source,并且指定使用username作为盐值。
# 盐值的原理非常简单,就是先把密码和盐值指定的内容合并在一起,再使用md5对合并后的内容进行演算,这样一来,就算密码是一个很常见的字符串,再加上用户名,最后算出来的md5值就没那么容易猜出来了。因为攻击者不知道盐值的值,也很难反算出密码原文。
#
# 七、 用户信息缓存
# 介于系统的用户信息并不会经常改变,因此使用缓存就成为了提升性能的一个非常好的选择。Spring Security内置的缓存实现是基于ehcache的,为了启用缓存功能,我们要在配置文件中添加相关的内容。
#
# <authentication-provider>
# <password-encoder hash="md5">
# <salt-source user-property="username"/>
# </password-encoder>
# <jdbc-user-service data-source-ref="dataSource" cache-ref="userCache"/>
# </authentication-provider>
#
# 我们在jdbc-user-service部分添加了对userCache的引用,它将使用这个bean作为用户权限缓存的实现。对userCache的配置如下所示:
#
# <beans:bean id="userCache" class="org.springframework.security.providers.dao.cache.EhCacheBasedUserCache">
# <beans:property name="cache" ref="userEhCache"/>
# </beans:bean>
#
# <beans:bean id="userEhCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
# <beans:property name="cacheManager" ref="cacheManager"/>
# <beans:property name="cacheName" value="userCache"/>
# </beans:bean>
#
# <beans:bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
#
# EhCacheBasedUserCache 是Spring Security内置的缓存实现,它将为jdbc-user-service提供缓存功能。它所引用的userEhCache来自 spring提供的EhCacheFactoryBean和EhCacheManagerFactoryBean,对于userCache的缓存配置放在 ehcache.xml中:
#
# <ehcache>
# <diskStore path="java.io.tmpdir"/>
#
# <defaultCache
# maxElementsInMemory="1000"
# eternal="false"
# timeToIdleSeconds="120"
# timeToLiveSeconds="120"
# overflowToDisk="true"
# />
#
# <cache
# name="userCache"
# maxElementsInMemory="100"
# eternal="false"
# timeToIdleSeconds="600"
# timeToLiveSeconds="3600"
# overflowToDisk="true"
# />
# </ehcache>
#
# cache解释:
# 内存中最多存放100个对象。
# 不是永久缓存。
# 最大空闲时间为600秒。
# 最大活动时间为3600秒。
# 如果内存对象溢出则保存到磁盘。
#
# 八、获取当前用户信息
# 如果只是想从页面上显示当前登陆的用户名,可以直接使用Spring Security提供的taglib。
#
# <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
# <div>username : <sec:authentication property="name"/></div>
#
# 如果想在程序中获得当前登陆用户对应的对象。
#
# UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
# .getAuthentication()
# .getPrincipal();
#
# 如果想获得当前登陆用户所拥有的所有权限。
#
# GrantedAuthority[] authorities = userDetails.getAuthorities();
#
# 关于UserDetails是如何放到SecuirtyContext中去的,以及Spring Security所使用的TheadLocal模式,我们会在后面详细介绍。这里我们已经了解了如何获得当前登陆用户的信息。
#
# 九、自定义访问拒绝页面
# 在我们的例子中,user用户是不能访问/admin.jsp页面的,当我们使用user用户登录系统之后,访问/admin.jsp时系统默认会返回403响应。
# 如果我们希望自定义访问拒绝页面,只需要随便创建一个jsp页面,让后将这个页面的位置放到配置文件中。
#
# 下面创建一个accessDenied.jsp
#
# <%@ page contentType="text/html;charset=UTF-8"%>
# <html>
# <head>
# <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
# <title>Access Denied</title>
# <style type="text/css">
# div.error {
# width: 260px;
# border: 2px solid red;
# background-color: yellow;
# text-align: center;
# }
# </style>
# </head>
# <body>
# <h1>Access Denied</h1>
# <hr>
# <div class="error">
# 访问被拒绝<br>
# ${requestScope['SPRING_SECURITY_403_EXCEPTION'].message}
# </div>
# <hr>
# </body>
# </html>
#
#
# 下一步修改配置文件,添加自定义访问拒绝页面的地址。
#
# <http auto-config='true' access-denied-page="/accessDenied.jsp">
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# </http>
#
# 十、动态管理资源结合自定义登录页面
# 如果想将动态管理资源与自定义登录页面一起使用,最简单的办法就是在数据库中将登录页面对应的权限设置为IS_AUTHENTICATED_ANONYMOUSLY。
#
# 因此在数据库中添加一条资源信息。
#
# INSERT INTO RESC VALUES(1,'','URL','/login.jsp*',1,'')
#
# 这里的/login.jsp*就是我们自定义登录页面的地址。
#
# 然后为匿名用户添加一条角色信息:
#
# INSERT INTO ROLE VALUES(3,'IS_AUTHENTICATED_ANONYMOUSLY','anonymous')
#
# 最后为这两条记录进行关联即可。
#
# INSERT INTO RESC_ROLE VALUES(1,3)
#
# 这样就实现了将动态管理资源与自定义登录页面进行结合。
#
#
# 十一、后登陆的将先登录的踢出系统 vs 后面的用户禁止登陆
# 默认情况下,后登陆的用户会把先登录的用户踢出系统。
# 想测试一下的话,先打开firefox使用user/user登陆系统,然后再打开ie使用user/user登陆系统。这时ie下的user用户会登陆成功,进入登陆成功页面。而firefox下的用户如何刷新页面,就会显示如下信息:
# This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).
# 这是因为先登录的用户已经被强行踢出了系统,如果他再次使用user/user登陆,ie下的用户也会被踢出系统了。
#
#
# 后面的用户禁止登陆
# 如果不想让之前登录的用户被自动踢出系统,需要为concurrent-session-control设置一个参数。
# <http auto-config='true'>
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# <concurrent-session-control exception-if-maximum-exceeded="true"/>
# </http>
#
# 这个参数用来控制是否在会话数目超过最大限制时抛出异常,默认值是false,也就是不抛出异常,而是把之前的session都销毁掉,所以之前登陆的用户就会被踢出系统了。
#
# 现在我们把这个参数改为true,再使用同一个账号同时登陆一下系统,看看会发生什么现象。
# 很好,现在只要有一个人使用user/user登陆过系统,其他人就不能再次登录了。这样可能出现一个问题,如果有人登陆的时候因为某些问题没有进行logout就退出了系统,那么他只能等到session过期自动销毁之后,才能再次登录系统。
#
# 十二、单点登录;略。
#
# 十三、为不同用户显示各自的登陆成功页面
# 一个常见的需求是,普通用户登录之后显示普通用户的工作台,管理员登陆之后显示后台管理页面。这个功能可以使用taglib解决。
# 其实只要在登录成功后的jsp页面中使用taglib判断当前用户拥有的权限进行跳转就可以。
# <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
# <sec:authorize ifAllGranted="ROLE_ADMIN">
# <%response.sendRedirect("admin.jsp");%>
# </sec:authorize>
# <sec:authorize ifNotGranted="ROLE_ADMIN">
# <%response.sendRedirect("user.jsp");%>
# </sec:authorize>
# 当用户拥有ROLE_ADMIN权限时,既跳转到admin.jsp显示管理后台。
# 当用户没有ROLE_ADMIN权限时,既跳转到user.jsp显示普通用户工作台。
# 这里我们只做最简单的判断,只区分当前用户是否为管理员。可以根据实际情况做更加复杂的跳转,当用户具有不同权限时,跳到对应的页面,甚至可以根据用户username跳转到各自的页面
#
# 十四、匿名登录
# 匿名登录,即用户尚未登录系统,系统会为所有未登录的用户分配一个匿名用户,这个用户也拥有自己的权限,不过他是不能访问任何被保护资源的。
#
# 设置一个匿名用户的好处是,我们在进行权限判断时,可以保证SecurityContext中永远是存在着一个权限主体的,启用了匿名登录功能之后,我们所需要做的工作就是从SecurityContext中取出权限主体,然后对其拥有的权限进行校验,不需要每次去检验这个权限主体是否为空了。这样做的好处是我们永远认为请求的主体是拥有权限的,即便他没有登录,系统也会自动为他赋予未登录系统角色的权限,这样后面所有的安全组件都只需要在当前权限主体上进行处理,不用一次一次的判断当前权限主体是否存在。这就更容易保证系统中操作的一致性。
#
# 配置文件
# 在配置文件中使用auto-config="true"就会启用匿名登录功能。在启用匿名登录之后,如果我们希望允许未登录就可以访问一些资源,可以在进行如下配置。
#
# <http auto-config='true'>
# <intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY" />
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# </http>
#
# 在access中指定IS_AUTHENTICATED_ANONYMOUSLY后,系统就知道此资源可以被匿名用户访问了。当未登录时访问系统的“/”,就会被自动赋以匿名用户的身份。我们可以使用taglib获得用户的权限主体信息。
#
# 这里的IS_AUTHENTICATED_ANONYMOUSLY将会交由AuthenticatedVoter处理,内部会依据AuthenticationTrustResolver判断当前登录的用户是否是匿名用户。
#
# <div>
# username : <sec:authentication property="name"/>
# |
# authorities: <sec:authentication property="authorities" var="authorities" scope="page"/>
# <c:forEach items="${authorities}" var="authority">
# ${authority.authority}
# </c:forEach>
# </div>
#
# 当用户访问系统时,就会看到如下信息,这时他还没有进行登录。
# 这里显示的是分配给所有未登录用户的一个默认用户名roleAnonyMous,拥有的权限是ROLE_ANONYMOUS。我们可以看到系统已经把匿名用户当做了一个合法有效的用户进行处理,可以获得它的用户名和拥有的权限,而不需判断SecurityContext中是否为空。
#
# 实际上,我们完全可以把匿名用户像一个正常用户那样进行配置,我们可以在配置文件中直接使用ROLE_ANONYMOUS指定它可以访问的资源。
#
# <http auto-config='true'>
# <intercept-url pattern="/" access="ROLE_ANONYMOUS" />
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# </http>
#
# 不过,为了更明显的将匿名用户与系统中的其他用户区分开,我们推荐在配置时尽量使用IS_AUTHENTICATED_ANONYMOUSLY来指定匿名用户可以访问的资源。
#
#
# 十五、使用JAAS机制
# 可以在Spring Security中使用JAAS机制进行用户的身份认证。
#
# JAAS 即Java Authentication and Authorization Service,它是JDK自带的一套专门用于处理用户认证和授权的标准API,Spring Security中可以使用API作为AuthenticationProvider处理用户认证与授权。
#
# 配置文件中,我们使用JaasAuthenticationProvider作为AuthenticationProvider。
#
# <http>
# <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
# <intercept-url pattern="/**" access="ROLE_USER" />
# <form-login/>
# <logout/>
# </http>
#
# <beans:bean id="jaasAuthenticationProvider"
# class="org.springframework.security.providers.jaas.JaasAuthenticationProvider">
# <custom-authentication-provider/>
# <beans:property name="loginConfig" value="/WEB-INF/login.conf" />
# <beans:property name="loginContextName" value="JAASTest" />
# <beans:property name="callbackHandlers">
# <beans:list>
# <beans:bean class="org.springframework.security.providers.jaas.JaasNameCallbackHandler" />
# <beans:bean class="org.springframework.security.providers.jaas.JaasPasswordCallbackHandler" />
# </beans:list>
# </beans:property>
# <beans:property name="authorityGranters">
# <beans:list>
# <beans:bean class="com.family168.springsecuritybook.ch117.AuthorityGranterImpl" />
# </beans:list>
# </beans:property>
# </beans:bean>
#
# 注意不能在http标签中使用auto-config="true"或是在http标签中包含rememberMe,因为rememberMe需要引用userDetailsService,而在使用 JaasAuthenticationProvider时,用户数据校验是交由LoginModule处理的,不会使用 userDetailsService,所以rememberMe会抛出异常。
#
# 我们将JAAS所需的配置文件放在/WEB-INF/login.config。
#
# JAASTest {
# com.family168.springsecuritybook.ch117.LoginModuleImpl required;
# };
#
# 并在配置文件中指明使用JAASTest作为登陆上下文。
#
# <beans:property name="loginContextName" value="JAASTest" />
#
# 现在要创建LoginModuleImpl用来处理用户登录。
# package com.family168.springsecuritybook.ch117;
# public class LoginModuleImpl implements LoginModule {
#
# private String password;
# private String user;
# private Subject subject;
#
# public boolean abort() throws LoginException {
# return true;
# }
#
# public boolean commit() throws LoginException {
# return true;
# }
#
# public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
# this.subject = subject;
#
# try {
# TextInputCallback textCallback = new TextInputCallback("prompt");
# NameCallback nameCallback = new NameCallback("prompt");
# PasswordCallback passwordCallback = new PasswordCallback("prompt", false);
#
# callbackHandler.handle(new Callback[] {textCallback, nameCallback, passwordCallback});
#
# password = new String(passwordCallback.getPassword());
# user = nameCallback.getName();
# } catch (Exception e) {
# throw new RuntimeException(e);
# }
# }
#
# public boolean login() throws LoginException {
# if (!user.equals("user")) {
# throw new LoginException("Bad User");
# }
#
# if (!password.equals("user")) {
# throw new LoginException("Bad Password");
# }
#
# subject.getPrincipals().add(new Principal() {
# public String getName() {
# return "TEST_PRINCIPAL";
# }
# });
#
# subject.getPrincipals().add(new Principal() {
# public String getName() {
# return "NULL_PRINCIPAL";
# }
# });
#
# return true;
# }
#
# public boolean logout() throws LoginException {
# return true;
# }
# }
#
# 当用户登录成功时,会通过authorityGranters为权限主体授权,这一步也要自己实现AuthorityGranter接口。
#
#
# package com.family168.springsecuritybook.ch117;
#
# import java.security.Principal;
# import java.util.HashSet;
# import java.util.Set;
# import org.springframework.security.providers.jaas.AuthorityGranter;
#
# public class AuthorityGranterImpl implements AuthorityGranter {
# public Set grant(Principal principal) {
# Set rtnSet = new HashSet();
#
# if (principal.getName().equals("TEST_PRINCIPAL")) {
# rtnSet.add("ROLE_USER");
# rtnSet.add("ROLE_ADMIN");
# }
#
# return rtnSet;
# }
# }
#
# 至此,JAAS与Spring Security结合进行认证授权的功能已经完成,每一步都要件功能写死在代码里,让人感觉很不舒服。
#
#
#
# 十六、保存登录之前的请求
# 经常会碰到一种情况,用户花费大量时间编辑信息,但是session超时失效导致用户自动退出系统,安全过滤器会强制用户再次登录,但这也会使用户提交的信息全部丢失。
#
# 为了解决这个问题,Spring Security提供了一种称作SavedRequest功能,可以在未登录用户访问资源时,将用户请求保存起来,当用户登录成功之后SecurityContextHolderAwareRequestFilter会使用之前保存的请求,结合当前用户的请求生成一个新的请求对象,而这个请求对象中就保存了用户登录之前提交的信息。
#
# SavedRequest功能默认就会被Spring Security启用,不需任何配置就可以重用登陆之前请求提交的数据。
#
转自:http://zfsn.iteye.com/blog/506682
相关文章推荐
- [学习小结]Spring_通知的那些事和基于配置文件的方式来配置AOP
- 读取应用程序/类库配置文件(比如***.dll.config)的方法小结
- spring security2配置文件学习小结
- Spring Security 入门(1-8)Spring Security 的配置文件举例
- [总结]读取应用程序/类库配置文件(比如***.dll.config)的方法小结
- Spring Security 结合CAS登入登出配置文件详解
- JSP项目读取配置文件小结
- spring security 概述& 配置文件详解
- struts配置文件中结果类型小结
- 读取应用程序/类库配置文件(比如***.dll.config)的方法小结
- Redis系列-配置文件小结
- SpringBoot通过自己的配置文件或者从数据库spring security动态配置url权限
- Postgresql pg_hba.conf配置文件小结
- Redis系列-配置文件小结
- Redis系列-配置文件小结
- Hibernate核心配置文件小结
- Redis系列-配置文件小结
- linux 增加用户 useradd 用法小结及配置文件说明
- linux 增加用户 useradd 用法小结及配置文件说明
- java--struts.xml配置文件小结