1.Spring Security介绍
一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限 。
在认识Spring Security之前,所有的权限验证逻辑都混杂在业务逻辑中,用户的每个操作以前可能都需要对用户是否有进行该项操作的权限进行判断,来达到认证授权的 目的。类似这样的权限验证逻辑代码被分散在系统的许多地方,难以维护。AOP(Aspect Oriented Programming)和Spring Security为我们的应用程序很好的解决了此类问题,正如系统日志,事务管理等这些系统级的服务一样,我们应该将它作为系统一个单独的“切面”进行管 理,以达到业务逻辑与系统级的服务真正分离的目的,Spring Security将系统的安全逻辑从业务中分离出来。且对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。
spring security是为基于Spring的应用程序提供声明式安全保护的安全性框架。spring security提供了完整的安全性的解决方案,它能够在web请求级别和方法调用级别处理身份验证和授权。
2.使用Spring Security
? 2.1 Spring Security3.0包含8个模块,我们应用程序至少要包含核心和配置这两个模块?
模块 | 描述 |
ACL | 支持通过访问控制列表为域对象提供安全性 |
CAS客户端 | 提供与JA-SIG的中心认证服务进行集成的功能 |
配置 | 包含了对Spring Security XML命令空间的支持 |
核心 | 提供了Spring Security基本库 |
LDAP | 支持基于轻量目录访问协议进行认证 |
OpenID | 支持分散式OpenID标准 |
Tag Library | 包含了一组JSP标签来实现视图级别的安全性 |
Web | 提供了Spring security基于过滤器的web安全性支持 |
? 2.2使用 Spring Security配置命名空间?
好处:使用命令空间可以将安全性配置从成百行的xml减少到10行。因为很多东西都给你做了。
方法一:
<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.表示开启表达式. see:http://www.family168.com/tutorial/springsecurity3/html/el-access.html --> <security:http auto-config="true" use-expressions="true" access-denied-page="/auth/denied" > <security:intercept-url pattern="/auth/login" access="permitAll"/> <security:intercept-url pattern="/main/admin" access="hasRole(‘ROLE_ADMIN‘)"/> <security:intercept-url pattern="/main/common" access="hasRole(‘ROLE_USER‘)"/> <security:form-login login-page="/auth/login" authentication-failure-url="/auth/login?error=true" default-target-url="/main/common"/> <security:logout invalidate-session="true" logout-success-url="/auth/login" logout-url="/auth/logout"/> </security:http> <!-- 指定一个自定义的authentication-manager :customUserDetailsService --> <security:authentication-manager> <security:authentication-provider user-service-ref="userServiceImpl"> <!-- <security:password-encoder ref="passwordEncoder"/> --> </security:authentication-provider> </security:authentication-manager> </beans>
注意在这个配置文件里面不能扫包,就是如下语句
<context:component-scan base-package="com.kedacom.platform.security.service"> </context:component-scan>
方法二:将安全性的命令空间作为默认的命名空间
<beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <!-- Spring-Security 的配置 --> <!--之前就是这个一直没有扫进来,所以创建bean有错误 注意有些可能不带支持这个扫包,就把头给换成现在这个可以扫包的 --> <context:component-scan base-package="com.kedacom.service"> </context:component-scan> <!-- 注意开启use-expressions.表示开启表达式. see:http://www.family168.com/tutorial/springsecurity3/html/el-access.html --> <!-- auot-config表示会把那7个过滤器的关系自动弄好 access-denied-page 表示没有权限发送这个url--> <http auto-config="true" use-expressions="true" access-denied-page="/auth/denied" > <intercept-url pattern="/auth/login" access="permitAll"/> <intercept-url pattern="/main/admin" access="hasRole(‘ROLE_ADMIN‘)"/> <intercept-url pattern="/main/common" access="hasRole(‘ROLE_USER‘)"/> <!-- 一般我们不提供登录页面,他自己会提供登录页面,所有我们要指定自己的登录页面,就在login-page里面指定其访问的url --> <!-- authentication-failure-url 表示失败之后去的页面 --> <!-- default-target-url 表示成功之后去的页面--> <form-login login-page="/auth/login" authentication-failure-url="/auth/login?error=true" default-target-url="/main/common"/> <!--logout-success-url 表示退出成功之后去的页面 --> <!--表示退出要发的url --> <logout invalidate-session="true" logout-success-url="/auth/login" logout-url="/auth/logout"/> </http> <!-- 指定一个自定义的authentication-manager :customUserDetailsService --> <authentication-manager> <authentication-provider user-service-ref="userServiceImpl"> <!-- <security:password-encoder ref="passwordEncoder"/> --> </authentication-provider> </authentication-manager> </beans:beans>
这种命令空间,就可以避免为每种元素添加那令人讨厌的 "security: " 前缀了,且还可以扫包,免得要去注册自己实现的那个类的bean
2.3基于内存用户存储进行认证管理
2.3.1 实例1,spring security的最简单的helloworld程序,用配置文件,将用户、权限、资源(url)硬编码在xml文件中,最简单的spring security的例子来讲解spring security怎么使用??
环境:spring mvc3.2.5 ,maven3.2.1,spring security3.1
1>新建maven项目,加入如下依赖的jar包??
对应的pom.xml文件内容如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.kedacom.webmvc</groupId> <artifactId>securityfirstdemo</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>securityfirstdemo Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!-- 将jsp编译成servlet,没有的话,jsp会报错 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <!--spring security安全框架 , 进行登录验证和授权验证的jar包 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.0.3</version> </dependency> </dependencies> <properties> <spring.security.version>3.1.0.RELEASE</spring.security.version> <log4j.version>1.2.12</log4j.version> </properties> <build> <finalName>securityfirstdemo</finalName> </build> </project>
2>.在web.xml 里面添加spring security过滤器,添加代码如下:
<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>
我们使用了DelegatingFilterProxy过滤器, 然后拦截了所有的请求(因为是/*)
3>在web.xml将spring security的配置加入到spring的上下文中, spring-security的配置文件名为:applicationContext-Security.xml,在web.xml添加的代码如下:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/applicationContext-Security.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
4>一个最简单的spring security配置的如下,配置在applicationContext-Security.xml文件中,代码如下:
<?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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> <!--auto-config等于true,他会自己给你一个登录页面,http基本认证和退出功能 --> <!--拦截所有的请求,当我们具备了ROLE_SPITTER角色才可以进行访问 --> <http auto-config="true"> <intercept-url pattern="/**" access="ROLE_SPITTER" /> </http> <!--配置内存用户存储 --> <!--通过user-service元素来创建一个用户服务 --> <user-service id="userService"> <user name="zhangsan" password="12" authorities="ROLE_SPITTER" /> <user name="lisi" password="14" authorities="ROLE_AA" /> </user-service> <!--将用户服务装配到认证管理器中 --> <authentication-manager> <authentication-provider user-service-ref="userService"></authentication-provider> </authentication-manager> </beans:beans>
所有的认证和权限控制全部通过配置文件来实现,这里面的内容才是spring security的主要所在。
对上面的配置的稍作讲解:
a.将auto-config属性配置为true好处,
我们可以得到他的免费的一些赠品,配置为true之后sping security会为我们提供一个额外的登录页,HTTP基本认证和退出功能,实际上,将auto-config属性配置为true等价于下面这样显示的配置的特性:
<http> <form-login/> <http-basic/> <logout /> <intercept-url pattern="/**" access="ROLE_SPITTER" /> </http>
b.<intercept-url>元素
<intercept-url>元素是实现请求级别的一道防线,他的pattern属性定义了对传入的请求要进行匹配的URL模式,如果请求匹配这个模式的话,<intercept-url>的安全规则就会启用。
就是表示访问某个请求,必须要某种权限,值得注意的是,在请求匹配上多个的时候,会从下往下进行匹配,知道匹配到第一个匹配的角色,就进行验证。
假设该应用有一些特定区域,只有管理员才能访问。为了实现这个点,我们可以在之前的那个条记录前插入如下的<intercept-url>:
<http auto-config="true"> <intercept-url pattern="/admin/** access="ROLE_ADMIN"" /> <intercept-url pattern="/**" access="ROLE_SPITTER" /> </http>
如果访问的是 "/admin/a.jsp"请求,其实两个都可以匹配的上,但是从上往下,由于先匹配到第一个,所有必须要"ROLE_ADMIN"角色才可以访问,虽然第二个可以匹配上,但是他不再往下匹配了。
c.为了方便期间直接在spring-security里的配置文件里面声明用户详细信息,用户的详细信息声明在<user-service>之中,每个能够登录的用户都会有一个<user>元素。属性name和password分别为指定了登录名和密码,同时authorities属性用于设置逗号分隔的权限列表--即允许用户做的事。从这spring-seccurity的配置中可以看得到,zhangsan可以对所有的URL有权限访问,而lisi没有。
d.<authentication-manager>元素会注册一个认证管理器。更确切的讲,他将注册一个ProviderManager实例。然后再将用户服务装配进来。
2.3.2实例二,重写登录页面, 退出,且根据登录用户权限的不同,返回不同的首页
在前面的例子中,我们可能会觉得得到了spring security提供的登录表单是赚了个大便宜,但是这个表单太简单了。且不美观,且我们改变不了其样式,所以一般我们会将其替换为自己设计的登录页面。
1.重写登录页面
为了设置自己的登录页面,我们需要配置<form-login>元素取代默认的行为:
<form-login login-page="/login" default-target-url="/employee/list" authentication-failure-url="/login?error=true" />
登录页面通过“/login”进行登录。
登录成功后默认url访问/employee/list
登录失败后url访问/login?error=true
2.添加退出,让其退出到自己定义的页面去
<!--退出成功后调转的页面 --> <logout invalidate-session="true" logout-success-url="/login" />
让其的session失效。
注销成功后转向:/login。
3.更加登录用户的权限不同返回不同的页面:
在登录成功之后,去取出当前登录用户的角色。然后判断其中有什么角色,更加角色来返回不同的页面,这样,不同的角色就只能够看到自己对应权限的页面。
@RequestMapping("/employee") public class EmployeeController { @Autowired EmployeeService employeeService; /* * 查看所有的员工的信息 */ @RequestMapping("/list") public String getallemployee(Model model) { List<Employee> lists = employeeService.findall(); // 注意:用的是字符串,对应对象的形式,就是如下形式 // model.addAttribute(string arg0, Object arg1) model.addAttribute("list", lists); //获得当前登录用户的角色列表 Set<String> roles = AuthorityUtils.authorityListToSet(SecurityContextHolder.getContext().getAuthentication().getAuthorities()); if(roles.contains("ROLE_ADMIN")){ return "home"; } else if(roles.contains("ROLE_USER")) { return "user/home"; } else{ //这儿有一个 ROLE_ANONYMOUS 系统提供的匿名身份的用户 return "redirect:/login"; } } }
下面这个语句是获得当前登录用户拥有的权限,是一个数组
SecurityContextHolder.getContext().getAuthentication().getAuthorities()
下面语句是将当前用户的权限转成集合
AuthorityUtils.authorityListToSet(userAuthorities)
2.4基于数据库的管理用户和角色来使用spring security(企业一般是用这种)
在上面我们所讲的两个例子中,我们的用户信息和权限信息都存在xml中,这是为了演示如何使用最小的配置就可以使用spring security。而实际开发中,用户基本信息和权限信息通常都是存储在数据库中,为此下面我们通过如下实例来说明企业是怎么使用spring security的。
实例三,用户信息存储在数据库中,不同的角色访问的资源不一样,管理员对所有的有访问权限,而普通用户只对基本的有访问权限,
一般会重写系统提供的UserDetailsService接口,然后根据前端传过来的用户名去数据库查询出基本信息根据数据库其权限给其添加权限
其余都与上面一致就不再此说明了,只写这个接口的代码:
@Service("userServiceImpl") public class UserServiceImpl implements UserService, UserDetailsService { @Autowired private UserDao userdao; public List<DBUser> userquery(String username) { return userdao.userquery(username); } public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDetails deuser = null; try { // 搜索数据库以匹配用户登录名. // 我们可以通过dao使用JDBC来访问数据库 List<DBUser> luser=userdao.userquery(username); if(luser == null ) return deuser; DBUser user = luser.get(0); // 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是userDetail包里面的类 //而dbuser也是userDetail包里面的类 deuser = new User(user.getUsername(), user.getPassword().toLowerCase(), true, true, true, true,getAuthorities(user.getAccess())); } catch (Exception e) { throw new UsernameNotFoundException("Error in retrieving user"); } return deuser; } @SuppressWarnings("deprecation") public Collection<GrantedAuthority> getAuthorities(Integer access) { List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2); // 所有的用户默认拥有ROLE_USER权限 authList.add(new GrantedAuthorityImpl("ROLE_USER")); // 如果参数access为1.则拥有ROLE_ADMIN权限 if (access.compareTo(1) == 0) { authList.add(new GrantedAuthorityImpl("ROLE_ADMIN")); } //返回权限列表,像管理员账号,zhangsan。返回的authList的值为 :[ROLE_USER, ROLE_ADMIN],而普通用户返回的值为[ROLE_USER] return authList; } }
这里要注意:这儿我们从数据库查询出来该用户的基本信息和权限信息之后,将其构建了一个user
deuser = new User(user.getUsername(), user.getPassword().toLowerCase(), true, true, true, true,getAuthorities(user.getAccess()));
这个user实现了UserDetails接口,主要存储了当前用户的用户名,密码,权限之类的信息,后面当用户访问对应的url的时候,会去这儿取这些数据,然后比较该用户是否拥有这个权限。这些是spring security框架做的事。
3.spring security的工作执行原理
1)web容器启动加载系统资源和权限列表
2)用户发出请求
3)过滤器拦截
4)取得请求资源所需权限
5)匹配用户拥有权限和请求权限,如果用户没有权限执行第六步,否则执行第七步
6)登录
7)验证并授权
其执行工作原理图如下:
明天下午写完
4.用spring security防止session固化欺骗
Session fixation attack(会话固定攻击)是利用服务器的session不变机制,借他人之手获得认证和授权,然后冒充他人。如果应用程序在用户首次访问它时为每一名用户建立一个匿名会话,这时往往就会出现会话固定漏洞。然后,一旦用户登录,该会话即升级为通过验证的会话。最初,会话令牌并未被赋予任何访问权限,但在用户通过验证后,这个令牌也具有了该用户的访问权限。
防止会话固定攻击,可以在用户登录成功后重新创建一个session id,并将登录前的匿名会话强制失效。Spring Security默认即可防止会话固定攻击。