弹簧安全是一个功能强大和高度可自定义的身份验证和访问控制框架。它是在事实上标准确保基于弹簧的应用程序
弹簧安全是最成熟和广泛使用的弹簧项目之一。成立 2003 年和积极维护的 SpringSource 自今日 (星期三) 它用于安全的许多苛刻的环境,包括机构、 军事应用程序和银行。它是根据一个 Apache 2.0 许可证释放,以便您可以放心地使用它在您的项目中。
弹簧安全也很容易了解、 部署和管理。我们专注于的安全命名空间提供了允许的 XML 只用几行中的完整的应用程序安全的最常见操作的指令。我们还提供了完整的工具集成的 SpringSource 工具套件,再加上我们 春罗 快速应用程序开发框架。春季 论坛 和 SpringSource 提供各种免费和付费支持服务。弹簧安全还结合其他许多弹簧技术,包括 春 Web 流量,春服务,SpringSource 企业,SpringSource 应用管理套件 和 SpringSource tc 服务器。
二、 配置applicationContext-security.xml
2.1 FilterChainProxy过滤器链
FilterChainProxy会按顺序来调用一组filter,使这些filter即能完成验证授权的本质工作,又能享用Spring Ioc的功能来方便的得到其它依赖的资源。FilterChainProxy配置如下:
1 2 3 4 PATTERN_TYPE_APACHE_ANT /**=httpSessionContextIntegrationFilter,logoutFilter, 5 authenticationProcessingFilter, securityContextHolderAwareRequestFilter, 6 rememberMeProcessingFilter,anonymousProcessingFilter, exceptionTranslationFilter, filterSecurityInterceptor 8 ]]> 9 10
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON 定义URL在匹配之前必须先转为小写,PATTERN_TYPE_APACHE_ANT 定义了使用Apache ant的匹配模式,/**定义的将等号后面的过滤器应用在那些URL上,这里使用全部URL过滤,每个过滤器之间都适用逗号分隔,它们按照一定的顺序排列。
提示: 特别需要注意的是,即使你配置了系统提供的所有过滤器,这个过滤器链会很长,但是千万不要使用换行,否则它们不会正常工作, 容器甚至不能正常启动。 |
首先是通道处理过滤器,如果你需要使用HTTPS,这里我们就使用HTTP进行传输,所以不需要配置通道处理过滤器,然后是集成过滤器,配置如下:
1 httpSessionContextIntegrationFilter是集成过滤器的一个实现,在用户的一个请求过程中,用户的认证信息通过SecurityContextHolder(使用ThreadLoacl实现)进行传递的,所有的过滤器都是通过SecurityContextHolder来获取用户的认证信息,从而在一次请求中所有过滤器都能共享Authentication(认证),减少了HttpRequest参数的传送,下面的代码是从安全上下文的获取Authentication对象的方法: 1 SecurityContext context = SecurityContextHolder.getContext(); 3 Authentication authentication = context.getAuthentication(); 但是,ThreadLoacl不能跨越多个请求存在,所以,集成过滤器在请求开始时从Http会话中取出用户认证信息并创建一个SecurityContextHolder将Authentication对象保存在其中,在请求结束之后,在从SecurityContextHolder中获取Authentication对象并将其放回Http会话中,共下次请求使用,从而达到了跨越多个请求的目的。集成过滤器还有其它的实现,可以参考相关文档。 集成过滤器必须在其它过滤器之前被使用。 1 5 7 9 19 21 LogoutFilter的构造函数需要两个参数,第一个是退出系统后系统跳转到的URL,第二个是一个LogoutHandler类型的数组,这个数组里的对象都实现了LogoutHandler接口,并实现了它的logout方法,用户在发送退出请求后,会一次执行LogoutHandler数组的对象并调用它们的 logout方法进行一些后续的清理操作,主要是从SecurityContextHolder对象中清楚所有用户的认证信息(Authentication对象),将用户的会话对象设为无效,这些都时由SecurityContextLogoutHandler来完成。LogoutFilter还会清除Cookie记录,它由另外一个Bean来完成(RememberMeServices)。 三.程序中常用的功能 3.1 同步Session控制 ==》 一个用户只能登陆程序一次 如果你希望单个用户只能登录到你的程序一次,Spring Security通过添加下面简单的部分支持这个功能。 首先,你需要把下面的添加到你的web.xml文件里,让Spring Security获得session生存周期事件: org.springframework.security.ui.session.HttpSessionEventPublisher 四、类说明: 1. Authentication对象才是Spring Security使用的进行安全访问控制用户信息安全对象。 2.GrantedAuthority是用户权限信息对象 3.DaoAuthenticationProvider,可以从数据库中读取用户信息,也可从一个用户属性文件中读取, 4.AuthenticationManager(认证管理器))中还配置了一个名为sessionController的Bean,这个Bean可以阻止用户在进行了一次成功登录以后在进行一次成功的登录。在 applicationContext-security.xml配置文件添加sessionController的配置: 5 p:exceptionIfMaximumExceeded="true" 6 p:sessionRegistry-ref="sessionRegistry"/> 7 maximumSessions属性配置了只允许同一个用户登录系统一次,exceptionIfMaximumExceeded属性配置了在进行第二次登录是是否让第一次登录失效。这里设置为true不允许第二次登录。要让此功能生效,我们还需要在web.xml文件中添加一个,以让Spring Security能获取Session的生命周期事件,配置如下: 1 2 3 org.springframework.security.ui.session.HttpSessionEventPublisher 4 5 1 5 p:authenticationManager-ref="authenticationManager" 6 p:authenticationFailureUrl="/login.jsp?login_error=1" 7 p:defaultTargetUrl="/default.jsp" 8 p:filterProcessesUrl="/j_spring_security_check" 9 p:rememberMeServices-ref="rememberMeServices"/> 下面列出了认证过程过滤器配置中各个属性的功能: 1.authenticationManager 认证管理器 2.authenticationFailureUrl 定义登录失败时转向的页面 3.defaultTargetUrl 定义登录成功时转向的页面 4.filterProcessesUrl 定义登录请求的地址(在web.xml中配置过) 5.rememberMeServices 在验证成功后添加cookie信息 一些常用的密码编码器(这些编码器都位于org.springframework.secu rity.providers.encoding包下): PlaintextPasswordEncoder(默认)——不对密码进行编码,直接返回未经改变的密码; Md4PasswordEncoder ——对密码进行消息摘要(MD4)编码; Md5PasswordEncoder ——对密码进行消息摘要(MD5)编码; ShaPasswordEncoder ——对密码进行安全哈希算法(SHA)编码。 五、配置文件的说明 新建文件spring-security.xml,可以在applicationContext.xml中载入,也可以在web.xml随applicationContext.xml 一起加载 . 5.1 spring-security.xml 主要部分讲解 ==》 http节点的配置 authentication-failure-url="/index.jsp?login_error=1" default-target-url="/main.action"/> 5.1.1 http节点中的标签属性说明: lowercase-comparisons:表示URL比较前先转为小写。 path-type:表示使用Apache Ant的匹配模式。 access-denied-page:访问拒绝时转向的页面。 access-decision-manager-ref:指定了自定义的访问策略管理器。当系统角色名的前缀不是默认的ROLE_时,需要自定义访问策略管理器。 login-page:指定登录页面。 login-processing-url:指定了客户在登录页面中按下 Sign In 按钮时要访问的 URL。与登录页面form的action一致。其默认值为:/j_spring_security_check。 authentication-failure-url:指定了身份验证失败时跳转到的页面。 default-target-url:指定了成功进行身份验证和授权后默认呈现给用户的页面。 always-use-default-target:指定了是否在身份验证通过后总是跳转到default-target-url属性指定的URL。 logout-url:指定了用于响应退出系统请求的URL。其默认值为:/j_spring_security_logout。 logout-success-url:退出系统后转向的URL。 invalidate-session:指定在退出系统时是否要销毁Session。 max-sessions:允许用户帐号登录的次数。范例用户只能登录一次。 exception-if-maximum-exceeded: 默认为false,此值表示:用户第二次登录时,前一次的登录信息都被清空。 当exception-if-maximum-exceeded="true"时系统会拒绝第二次登录。 5.1.2 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lilp_ndsc/archive/2010/11/25/6034420.aspx 总结(过滤器): 1.HttpSessionContextIntegrationFilter 位于过滤器顶端,第一个起作用的过滤器。 用途一,在执行其他过滤器之前,率先判断用户的session中是否已经存在一个SecurityContext了。如果存在,就把SecurityContext拿出来,放到SecurityContextHolder中,供Spring Security的其他部分使用。如果不存在,就创建一个SecurityContext出来,还是放到SecurityContextHolder中,供Spring Security的其他部分使用。 用途二,在所有过滤器执行完毕后,清空SecurityContextHolder,因为SecurityContextHolder是基于ThreadLocal的,如果在操作完成后清空ThreadLocal,会受到服务器的线程池机制的影响。 2./j_spring_security_logout ===》LogoutFilter 只处理注销请求,用途是在用户发送注销请求时,销毁用户session,清空 SecurityContextHolder,然后重定向到注销成功页面。可以与rememberMe之类的机制结合,在注销的同时清空用户cookie。 3./j_spring_security_check ==》 AuthenticationProcessingFilter用户登陆 : /j_spring_security_check:是Spring Security默认的进行表单验证的过滤地址,你也可以修改为别的名称,但是需要和applicationContext-security.xml中相对应,当然还会涉及到其它一些默认值(可能是一个成员变量,也可能是别的请求地址),在下文我们将看到,建议你在阅读此文的同时,应该参照Spring Security项目的源代码,便于你更好的理解。 可以直接把这个参数设置为一个checkbox,无需设置value,Spring Security会自行判断它是否被选中。 4./spring_security_login ===》 DefaultLoginPageGeneratingFilter自动登陆 此过滤器用来生成一个默认的登录页面,默认的访问地址为/spring_security_login,这个默认的登录页面虽然支持用户输入用户名,密码,也支持rememberMe功能,但是因为太难看了,只能是在演示时做个样子,不可能直接用在实际项目中。 自定义登陆页面 5.BasicProcessingFilter 此过滤器用于进行basic验证,功能与AuthenticationProcessingFilter类似,只是验证的方式不同。 添加basic认证,去掉auto-config="true",并加上 6.SecurityContextHolderAwareRequestFilter 此过滤器用来包装客户的请求。目的是在原始请求的基础上,为后续程序提供一些额外的数据。比如getRemoteUser()时直接返回当前登陆的用户名之类的。 7.RememberMeProcessingFilter 此过滤器实现RememberMe功能,当用户cookie中存在rememberMe的标记,此过滤器会根据标记自动实现用户登陆,并创建SecurityContext,授予对应的权限。 在配置文件中使用auto-config="true"就会自动启用rememberMe实际上,Spring Security中的rememberMe是依赖cookie实现的,当用户在登录时选择使用rememberMe,系统就会在登录成功后将为用户生成一个唯一标识,并将这个标识保存进cookie中,我们可以通过浏览器查看用户电脑中的cookie。 8.AnonymousProcessingFilter 为了保证操作统一性,当用户没有登陆时,默认为用户分配匿名用户的权限。 在配置文件中使用auto-config="true"就会启用匿名登录功能。在启用匿名登录之后,如果我们希望允许未登录就可以访问一些资源,可以在进行如下配置。 设置成 ROLE_ANONYMOUS 也可以。 filters="none"表示当我们访问“/”时,是不会使用任何一个过滤器去处理这个请求的,它可以实现无需登录即可访问资源的效果,但是因为没有使用过滤器对请求进行处理,所以也无法利用安全过滤器为我们带来的好处,最简单的,这时SecurityContext内再没有保存任何一个权限主体了,我们也无法从中取得主体名称以及对应的权限信息。 9.ExceptionTranslationFilter 此过滤器的作用是处理中FilterSecurityInterceptor抛出的异常,然后将请求重定向到对应页面,或返回对应的响应错误代码。 10.SessionFixationProtectionFilter 防御会话伪造攻击。 解决session fix的问题其实很简单,只要在用户登录成功之后,销毁用户的当前session,并重新生成一个session就可以了。 session-fixation-protection的值共有三个可供选择,none,migrateSession和newSession。默认使用的是migrationSession 11.FilterSecurityInterceptor 用户的权限控制都包含在这个过滤器中。 功能一:如果用户尚未登陆,则抛出AuthenticationCredentialsNotFoundException“尚未认证异常”。 功能二:如果用户已登录,但是没有访问当前资源的权限,则抛出AccessDeniedException“拒绝访问异常”。 功能三:如果用户已登录,也具有访问当前资源的权限,则放行。 七、其他 可以直接通过SecurityContextHolder获得当前线程中的SecurityContext。 SecurityContext securityContext = SecurityContextHolder.getContext(); 默认情况下,SecurityContext的实现基于ThreadLocal,系统会在每次用户请求时将SecurityContext与当前Thread进行绑定,、 7.1 修改系统使用的策略 也可以调用SecurityContextHolder的setStrategyName()方法来修改系统使用的策略。 SecurityContextHolder.setStrategyName("MODE_GLOBAL"); 如果用户尚未通过认证,那么SecurityContext.getAuthenticaiton()方法就会返回null。可以使用Authentication接口中定义的几个方法,获得当前权限实体的信息 从接口的方法可以看出,用户可以通过SecurityContext存放和读取票据信息(Authentication)。 SecurityContext又存放在SecurityContextHolder。 SecurityContextHolder定义了SecurityContex的相关操作,如初始化,清空,读取等。SecurityContextHolder并没有直接实现这些操作,而是使用了策略模式,由一个SecurityContextHolderStrategy接口,来完成真正的逻辑。 十、自定义用户权限认证 7.1 自己定义用户权限认证步骤: import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetailsService; class MySecurityJdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService { 1.在自己项目中添一个类继承JdbcDaoSupport抽像类,实现UserDetailsService接口。 2.在applicationContext-security.xml文件中配置: 以下的标签书写在 2.1 2.2 p:dataSource-ref="dataSource" p:debug="true" p:usersByUsernameQuery="select userName, passWord, enabled, userId, email from users where userName=?" p:authoritiesByUsernameQuery="select u.userName,r.roleName from users u,roles r,users_roles ur where u.userId=ur.userId and r.roleId=ur.roleId and u.userName=?"/> 类的实例: public class MySecurityJdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService { public static final String DEF_USERS_BY_USERNAME_QUERY = "SELECT userName, passWord, enabled, userId, email FROM users WHERE userName=?"; public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "SELECT u.userName, r.roleName FROM users u, roles r, users_roles ur " + "WHERE u.userId = ur.userId AND r.roleId = ur.roleId " + "AND u.userName=?"; protected MappingSqlQuery authoritiesByUsernameMapping; protected MappingSqlQuery usersByUsernameMapping; private String authoritiesByUsernameQuery; private String usersByUsernameQuery; private String rolePrefix = ""; private boolean usernameBasedPrimaryKey = true; private boolean debug = false; public MySecurityJdbcDaoImpl() { this.usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY; this.authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY; } protected void initDao() throws ApplicationContextException { initMappingSqlQueries(); } protected void initMappingSqlQueries() { this.usersByUsernameMapping = new UsersByUsernameMapping( getDataSource()); this.authoritiesByUsernameMapping = new AuthoritiesByUsernameMapping( getDataSource()); } @SuppressWarnings("unchecked") protected void addCustomAuthorities(String username, List authorities) { } /** (non-Javadoc) * @see * org.springframework.security.userdetails.jdbc.JdbcDaoImpl#loadUserByUsername * (java.lang.String) */ @SuppressWarnings("unchecked") public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { if (debug) { // TODO Auto-generated method stub System.out.println("用户<" + username + ">登陆"); } List users = usersByUsernameMapping.execute(username); if (users.size() == 0) { throw new UsernameNotFoundException("User Not Found..."); } MyUserDetails user = (MyUserDetails) users.get(0); if (debug) { System.out.println("用户基本信息<" + user.getUserId() + "><" + user.getUsername() + "><" + user.getPassword() + "><" + user.getEmail() + ">"); } // Set dbAuthsSet = new HashSet(); // dbAuthsSet.addAll(authoritiesByUsernameMapping.execute(user.getUsername())); // List dbAuths = new ArrayList(dbAuthsSet); List dbAuths = authoritiesByUsernameMapping.execute(user.getUsername()); if (debug) { System.out.println("<" + user.getUsername() + ">有<" + dbAuths.size() + ">个权限"); } if (dbAuths.size() == 0) { throw new UsernameNotFoundException("User Has no GrantedAuthority"); } addCustomAuthorities(user.getUsername(), dbAuths); GrantedAuthority[] authorities = (GrantedAuthority[]) dbAuths .toArray(new GrantedAuthority[dbAuths.size()]); user.setAuthorities(authorities); if (!usernameBasedPrimaryKey) { user.setUsername(username); } return user; } protected class AuthoritiesByUsernameMapping extends MappingSqlQuery { protected AuthoritiesByUsernameMapping(DataSource ds) { super(ds, authoritiesByUsernameQuery); declareParameter(new SqlParameter(Types.VARCHAR)); compile(); } /** (non-Javadoc) * @see org.springframework.jdbc.object.MappingSqlQuery#mapRow(java.sql.ResultSet * , int) */ protected Object mapRow(ResultSet rs, int rownum) throws SQLException { // TODO Auto-generated method stub String roleName = rolePrefix + rs.getString(2); GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName); if (debug) { System.out.println("权限名:<" + roleName + ">"); } return authority; } } protected class UsersByUsernameMapping extends MappingSqlQuery { protected UsersByUsernameMapping(DataSource ds) { super(ds, usersByUsernameQuery); declareParameter(new SqlParameter(Types.VARCHAR)); compile(); } /* (non-Javadoc) * @see org.springframework.jdbc.object.MappingSqlQuery#mapRow(java.sql.ResultSet * , int) */ protected Object mapRow(ResultSet rs, int rownum) throws SQLException { // TODO Auto-generated method stub String userName = rs.getString(1); String passWord = rs.getString(2); boolean enabled = rs.getBoolean(3); Integer userId = rs.getInt(4); String email = rs.getString(5); MyUserDetails user = new MyUser(userName,passWord,enabled,true,true,true, new GrantedAuthority[] { new GrantedAuthorityImpl("HOLDER") }); user.setEmail(email); user.setUserId(userId); return user; } } /** @return the authoritiesByUsernameQuery */ public String getAuthoritiesByUsernameQuery() { return authoritiesByUsernameQuery; } /** @param authoritiesByUsernameQuery * the authoritiesByUsernameQuery to set */ public void setAuthoritiesByUsernameQuery(String authoritiesByUsernameQuery) { this.authoritiesByUsernameQuery = authoritiesByUsernameQuery; } /** @return the usersByUsernameQuery */ public String getUsersByUsernameQuery() { return usersByUsernameQuery; } /**@param usersByUsernameQuery * the usersByUsernameQuery to set */ public void setUsersByUsernameQuery(String usersByUsernameQuery) { this.usersByUsernameQuery = usersByUsernameQuery; } /** @return the rolePrefix */ public String getRolePrefix() { return rolePrefix; } /* @param rolePrefix * the rolePrefix to set */ public void setRolePrefix(String rolePrefix) { this.rolePrefix = rolePrefix; } /**@return the usernameBasedPrimaryKey */ public boolean isUsernameBasedPrimaryKey() { return usernameBasedPrimaryKey; } /**@param usernameBasedPrimaryKey * the usernameBasedPrimaryKey to set */ public void setUsernameBasedPrimaryKey(boolean usernameBasedPrimaryKey) { this.usernameBasedPrimaryKey = usernameBasedPrimaryKey; } /** * @return the debug */ public boolean isDebug() { return debug; } /** * @param debug * the debug to set */ public void setDebug(boolean debug) { this.debug = debug; } 总结来源:http://www.family168.com下载本文
2.1.1 logoutFilter(退出过滤器) ,退出登录操作:提示: j_username: 输入登陆名的参数名称。 j_password: 输入密码的参数名称 _spring_security_remember_me: 选择是否允许自动登录的参数名称。