现在很多企业和开发团队都使用了SSH2(Struts 2 +Spring 2.5 +Hibernate)框架来进行开发, 我们或许已经习惯了强大的Spring Framework 全局配置管理,不可否认,Sping是一个很优秀的开源框架,但是由于Spring3.0版本后强大的的注解式bean的诞生,Spring MVC框架这匹黑马正悄然杀起,但今天Spring MVC不是主角,今天我和大家分享一个同样隶属于SpringSource 的安全框架——Spring Security, 下面的基于Spring MVC给大家分享一下Spring Security 的使用。虽然对它的接触时间不长,参考了一些网上朋友的做法,但也按照我的理解把这个框架介绍介绍,不是很专业,还请大家不要介意 。
我们知道,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中 的合法主体,也就是说用户能否访问该系统。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个 资源来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
首先,我们看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"> <!-- 编码统一最好放最上面,最先加载,防止乱码--> <filter> <filter-name>Set Character Encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value><!-- 强制进行转码 --> </init-param> </filter> <filter-mapping> <filter-name>Set Character Encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 然后接着是SpringSecurity必须的filter 优先配置,让SpringSecurity先加载,防止SpringSecurity拦截失效--> <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> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <!-- spring需要加载的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> WEB-INF/classes/applicationContext.xml, WEB-INF/spring3-servlet.xml, WEB-INF/spring-security.xml </param-value> </context-param> <listener> <listener-class> <!-- 所以,要在web.xml下面配置好监听,让服务器启动时就初始化改类,可以得到request --> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 默认所对应的配置文件是WEB-INF下的{servlet-name}-servlet.xml,这里便是:spring3-servlet.xml --> <servlet> <servlet-name>spring3</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring3</servlet-name> <!-- 这里可以用 / 但不能用 /* ,拦截了所有请求会导致静态资源无法访问,所以要在spring3-servlet.xml中配置mvc:resources --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
注释已经写了挺多,还是稍微解释一下要注意的地方,一个是UTF-8编码转换,这个最好加在最前面,让它先生效,我在调试的时候就出过这种情 况,web.xml里的其他配置都正常生效了,但是编码死活不行,一中文就乱码,郁闷了老半天,然后突发奇想,是不是web.xml里先声明的配置先生 效,后声明的后生效?接着实践,果然不出我所料,把编码转换加在前面,一切正常。。。。我那个晕。。。
关于Spirng MVC的就不说了,那些数据访问、业务和控制层那些的东东就自个研究去了吧。。这不是今天的重点。
接着是spring-security.xml
<?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:security="http://www.springframework.org/schema/security" 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.0.xsd"> <!-- Spring-Security 的配置 --> <!-- 注意use-expressions=true.表示开启表达式,否则表达式将不可用. see:http://www.family168.com/tutorial/springsecurity3/html/el-access.html --> <security:http auto-config="true" use-expressions="false" access-denied-page="/user/login_failure.html"> <!--允许所有人访问--> <!-- <security:intercept-url pattern="/**" access="permitAll" />--> <!--允许ROLE_ADMIN权限访问--> <security:intercept-url pattern="/user/findAll.html" access="ROLE_ADMIN" /> <!--允许ROLE_ADMIN权限访问--> <security:intercept-url pattern="/user/**" access="ROLE_ADMIN" /> <!--允许ROLE_USER权限访问--> <security:intercept-url pattern="/success.jsp" access="ROLE_USER,ROLE_ADMIN" /> <!--允许IS_AUTHENTICATED_ANONYMOUSLY匿名访问--> <security:intercept-url pattern="/anonymously.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" /> <!-- filters="none" 不过滤这些资源--> <security:intercept-url pattern="/js/**" filters="none" /> <security:intercept-url pattern="/index.jsp" filters="none" /> <!-- login-page:默认指定的登录页面. authentication-failure-url:出错后跳转页面. default-target-url:成功登陆后跳转页面 --> <security:form-login login-page="/index.jsp" authentication-failure-url="/user/login_failure.html" default-target-url="/success.jsp" /> <!-- invalidate-session:指定在退出系统时是否要销毁Session。logout-success-url:退出系统后转向的URL。logout-url:指定了用于响应退出系统请求的URL。其默认值为:/j_spring_security_logout。 --> <security:logout invalidate-session="true" logout-success-url="/index.jsp" logout-url="/j_spring_security_logout" /> <!-- max-sessions:允许用户帐号登录的次数。范例限制用户只能登录一次。exception-if-maximum-exceeded: 默认为false,此值表示:用户第二次登录时,前一次的登录信息都被清空。当exception-if-maximum-exceeded="true"时系统会拒绝第二次登录。 --> <security:session-management> <security:concurrency-control error-if-maximum-exceeded="true" max-sessions="1" /> </security:session-management> </security:http> <!-- 指定一个自定义的authentication-manager :customUserDetailsService --> <security:authentication-manager> <security:authentication-provider user-service-ref="customUserDetailsService"> <security:password-encoder ref="passwordEncoder" /> </security:authentication-provider> </security:authentication-manager> <!-- 对密码进行MD5编码 --> <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" /> <!-- 通过 customUserDetailsService,Spring会控制用户的访问级别. 也可以理解成:以后我们和数据库操作就是通过customUserDetailsService来进行关联. --> <bean id="customUserDetailsService" class="org.yzsoft.springmvcdemo.util.CustomUserDetailsService" /> <!-- 自定义登陆错误提示,可以取出mymessages.properties的国际化消息--> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:org/yzsoft/springmvcdemo/mymessages" /> </bean> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver" /> </beans>
这个多解释一下,首先
<security:intercept-url pattern="/findAll.html" access="hasRole('ROLE_ADMIN')" /> <security:intercept-url pattern="/user/**" access="hasRole('ROLE_ADMIN')" /> <security:intercept-url pattern="/anonymously.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />
这个是权限控制,声明了拥有什么权限可以访问哪些资源,这个配置的是有ROLE_ADMIN权限的才可以访问/findAll.html,至于这 个ROLE_ADMIN从哪来,呆会再解释。或者像第二句一样配置也可以:拥有ROLE_ADMIN权限的才可以访问/user/下的所有资源,否则都会抛出AccessDeniedException 。
IS_AUTHENTICATED_ANONYMOUSLY就是匿名访问的意思,这个相信都懂的。。。
然后是登陆和安全退出
<security:form-login login-page="/index.jsp" authentication-failure-url="/user/login_failure.html"default-target-url="/user/findAll.html" /> <security:logout invalidate-session="true" logout-success-url="/index.jsp" logout-url="/user/login_failure.html" />
解释下上面一句,相信看也能看出来了的,login-page:默认指定的登录页面. authentication-failure-url:出错后跳转页面(包括那些个啥用户名密码错误吖。。啥啥啥的。). default-target-url:成功登陆后跳转页面 (这里我直接跳到的控制器去查数据列表)。
接着:invalidate-session:指定在退出系统时是否要销毁Session。logout-success-url:退出系统后转 向的URL。logout-url:指定了用于响应退出系统请求的URL。其默认值为:/j_spring_security_logout。
接下来是一个比较不错的功能:是否允许同一用户多处登陆
<security:session-management> <security:concurrency-control error-if-maximum-exceeded="true" max-sessions="1" /> </security:session-management>
exception-if-maximum-exceeded:
默认为false,此值表示:用户第二次登录时,前一次的登录信息都被清空。当error-if-maximum-exceeded="true"时系统会拒绝第二次登录。
max-sessions:允许用户帐号登录的次数,这里我们允许一次登陆。这里我们做个实验吧,看看是不是真的生效了,请看图
这里我们看到,当同一个账号多处登陆时,就会报出Maximum sessions of 1 for this principal exceeded 的错误,当然,正式使用我们换成mymessages.properties里的我们自定义的国际化消息,这样人性化一点。
好,我们往下看,接着就是应用我们实际项目里的自定义用户权限了
<security:authentication-manager> <security:authentication-provider user-service-ref="customUserDetailsService"> <security:password-encoder ref="passwordEncoder" /> </security:authentication-provider> </security:authentication-manager> <!-- 对密码进行MD5编码 --> <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" /> <bean id="customUserDetailsService" class="org.yzsoft.springmvcdemo.util.CustomUserDetailsService" />
首先是<security:authentication-manager>是指定我们自定义的身份验证策略,这里我们用 customUserDetailsService这个bean,就是指向我们CustomUserDetailsService.java这个类。然 后<security:password-encoder>指定我们密码使用MD5进行编码,调用Spring Security自带的MD5加密类。当然,还有加盐MD5或我们自己写的加密算法等安全性更加高的密码策略。这个按项目实际使用配置吧。
然后看到我们的CustomUserDetailsService.java
package org.yzsoft.springmvcdemo.util; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.GrantedAuthorityImpl; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.yzsoft.springmvcdemo.serviceimpl.UsersServiceImpl; import org.yzsoft.springmvcdemo.vo.TUsers; /** * 一个自定义的类用来和数据库进行操作. 即以后我们要通过数据库保存权限.则需要我们继承UserDetailsService * * @author * */ public class CustomUserDetailsService implements UserDetailsService { protected static Logger logger = Logger.getLogger("service");//log4j,不用解释了吧。。 @Autowired private UsersServiceImpl usersService; public UsersServiceImpl getUsersService() { return usersService; } public void setUsersService(UsersServiceImpl usersService) { this.usersService = usersService; } public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { UserDetails user = null; try { // 搜索数据库以匹配用户登录名. // 我们可以通过dao使用Hibernate来访问数据库 System.out.println(username + " 用户页面输入的用户名"); TUsers tusers = this.usersService.findByUsername(username); System.out.println(tusers.getUsername() + " 数据库取出的用户名"); // Populate the Spring User object with details from the dbUser // Here we just pass the username, password, and access level // getAuthorities() will translate the access level to the correct // role type // 用户名、密码、是否启用、是否被锁定、是否过期、权限 user = new User(tusers.getUsername(), tusers.getPassword().toLowerCase(), true, true, true, true, getAuthorities(Integer.parseInt(tusers.getRole()))); } catch (Exception e) { logger.error("用户信息错误!"); throw new UsernameNotFoundException("异常处理:检索用户信息未通过!"); } return user; } /** * 获得访问角色权限列表 * * @param access * @return */ public Collection<GrantedAuthority> getAuthorities(Integer role) { System.out.println("取得的权限是 :" + role); List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(); // 所有的用户默认拥有ROLE_USER权限 if (role == 0) { System.out.println("普通用户"); logger.debug("取得普通用户权限-->"); authList.add(new GrantedAuthorityImpl("ROLE_USERS")); } // 如果参数role为1.则拥有ROLE_ADMIN权限 if (role == 1) { logger.debug("取得ADMIN用户权限-->"); authList.add(new GrantedAuthorityImpl("ROLE_ADMIN")); } System.out.println(authList.size()+" 权限列表长度"); return authList; } }
这里就是把我们从数据库里面取得的用户权限和Spring Security的配置进行桥接,还记得上面配置文件里的ROLE_ADMIN吧,就是从这里来的,很奇怪的是,这个必须设置成ROLE_ 开头才有效。。郁闷。。。
这里我刚开始有一个疑惑,我们看这2句
TUsers tusers = this.usersService.findByUsername(username); user = new User(tusers.getUsername(), tusers.getPassword().toLowerCase(), true, true, true, true, getAuthorities(Integer.parseInt(tusers.getRole())));
这里根本不需要用户输入的密码,只要了用户名,然后直接根据用户名去取权限,就直接设置进Spring Security的User对象里面去,我不禁一身冷汗,这不相当于说有了用户名就直接去查数据库么,而且是不用密码的。。。。
但经过查看官方文档和网上的解释,这才放心,原来是这样的,Spring Security的确是直接根据用户名去查,但是查得出来的Spring Security User对象之后,它会根据这个对象的属性值去数据库查询与这个对象匹配的数据,我们这里设置的是(用户名,密码,是否启用、是否被锁定、是否过期、权 限。。。),那么如果数据库存在这个对象,就返回真,否则返回假,这样也就不用担心了,完全可靠。就是我们在前台要做好限制,不能给用户不输密码就访问, 不然挤爆你数据库连接。。。。。
最后登陆页面index.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <base href="<%=basePath%>"> <title>My JSP 'index.jsp' starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> </head> <body> 用户登陆 <br> ${SPRING_SECURITY_LAST_EXCEPTION.message} <form action="j_spring_security_check" method="post"> USERNAME:<input type="text" name="j_username" value="${sessionScope['SPRING_SECURITY_LAST_USERNAME']}" /><br/> PASSWORD:<input type="password" name="j_password" value="" /><br/> <input type="checkbox" name="_spring_security_remember_me" />两周之内不必登陆(这个功能没有做的)<br/> <input type="submit"> </form> </body> </html>
这里我还是使用Spring Security默认的j_username和j_password,表单目标也用默认的j_spring_security_check,会默认跳到Spring Security进行拦截。其他的应该不用解释了吧。。。。
最后 ,我们整理一下Spring Security的整个控制过程:
——>用户登陆
——> <security:authentication-manager> 拦截
——>交给customUserDetailsService处理,并且声明密码采用MD5策略
——>根据输入的用户名去数据库查这条记录,验证身份
——>取出该条记录的用户权限(锁定、禁用、过期和实际权限等)
——>根据取得的权限列表去security:intercept-url匹配、授权,然后判断是否放行。
这就完成了一整个的权限控制流程。
接下来我们来测试一下看是否真的生效了:
1、测试匿名访问页面,直接地址栏访问:
2、普通用户登陆
然后访问后台管理页面,跳回了登陆页
3、管理员用户登陆
退出后成功跳转回登陆页,点击后退再执行其他操作,这时候session已经注销了的,不能执行,又跳回了登陆页。是我们想要的效果,OK,成功了。
好了,Spring Security的简单使用就讲到这里,其实这只是Spring Security的一小部分,而且这里我还没有用权限表对用户权限进行专门的管理,很多东西还是用Spring Security 默认的,还有Spring Security CAS (单点登陆)以及更加高级的权限控制和更完善的Spring Security 配置,以后我们再慢慢去研究吧。发现Spring Security 这个技术不仅简化了我们的用户权限管理,要知道我们做管理系统的时候这是个大问题,也差不多颠覆了我一贯以来用户权限管理的观念,但是掌握了这种思维之 后,又发现,其实,程序并不是只有一种实现方式,它激发了我写程序时要去寻找多种解决方案的想法。学习的路上,就是要不断推翻自己固有的思维,去见识多种 新事物,才能有进步。
最后是项目文件,比较大,分卷了,还没有Maven的版本,以后奉上~~~
相关推荐
Spring Security 演讲PPT(演讲嘉宾:张明星) WebSphere技术专家沙龙在广州圆满举办,WSC超级版主Fastzch(张明星)担任本次沙龙的演讲嘉宾,他给广州的WebSphere技术专家带来了以“Spring Security ”为主题的...
Spring Security三份资料,实战Spring Security 3.x.pdf;Spring Security 3.pdf;Spring Security使用手册.pdf
spring security spring security 中文文档
springsecurity是一个功能强大且高度可定制的身份验证和访问控制框架。springsecurity是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring安全性的真正威力在于它可以很容易地扩展以...
Spring Security in Action
SpringSecurity框架的权限认证流程原理,请求到来时SpringSecurity如果调用层层过滤器来完成认证;
很多独立软件供应商,因为灵活的身份验证模式二选择Spring Security。这样做允许他们快速的集成到他们的终端客户需求的解决方案而不用进行大量工程或者改变客户的环境。如果上面的验证机制不符合你的需求,Spring ...
1.本项目为SpringCloud Gateway的微服务框架,整合了SpringSecurity,微服务间使用Redis来获取登陆的用户信息。 2.由于Gat
Spring Security:spring家族一员。是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转...
SpringSecurity学习总结源代码
Spring Security 3.pdf Spring Security 3.pdf Spring Security 3.pdf Spring Security 3.pdf
三更springsecurity学习笔记
SpringSecurity入门到进阶到高级,是我们老师给我们讲课用的,我们都照着配就没有问题,可以跑通,
该资源是基本Spring Security实战七篇文档中组织的源码,详情如下: ssecurity项目是Spring Security实战(一和二)的源码; ssecurity-db项目是Spring Security实战(三)的源码; ssceurity-page项目是Spring ...
Spring Security。 官网 Spring Security API(Spring Security 开发文档).CHM
spring security2.5 jar 和spring security2.5 整合必须的jar包
SpringSecurity课程文档下载 pdf 教学
Spring Security OAuth2.0学习笔记 什么是认证、授权、会话。 Java Servlet为支持http会话做了哪些事儿。 基于session认证机制的运作流程。 基于token认证机制的运作流程。 理解Spring Security的工作原理,Spring ...
spring security3 中文版本