Spring Security权限管理框架介绍
Spring Security 提供了基于javaEE的企业应用软件全面的安全服务。这里特别强调支持使用Spring框架构件的项目,Spring框架是企业软件开发javaEE方案的领导者。如果你还没有使用Spring来开发企业应用程序,我们热忱的鼓励你仔细的看一看。熟悉Spring特别是一来注入原理两帮助你更快更方便的使用Spring Security。
人们使用Spring Secruity的原因有很多,单大部分都发现了javaEE的Servlet规范或EJB规范中的安全功能缺乏典型企业应用场景所需的深度。提到这些规范,重要的是要认识到他们在WAR或EAR级别无法移植。因此如果你更换服务器环境,这里有典型的大量工作去重新配置你的应用程序员安全到新的目标环境。使用Spring Security 解决了这些问题,也为你提供许多其他有用的,可定制的安全功能。
正如你可能知道的两个应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。“认证”,是建立一个他声明的主题的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。“授权”指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的目的,主体的身份已经有认证过程建立。这个概念是通用的而不只在Spring Security中。
在身份验证层,Spring Security 的支持多种认证模式。这些验证绝大多数都是要么由第三方提供,或由相关的标准组织,如互联网工程任务组开发。另外Spring Security 提供自己的一组认证功能。具体而言,Spring Security 目前支持所有这些技术集成的身份验证:
- HTTP BASIC 认证头 (基于 IETF RFC-based 标准)
- HTTP Digest 认证头 ( IETF RFC-based 标准)
- HTTP X.509 客户端证书交换 ( IETF RFC-based 标准)
- LDAP (一个非常常见的方法来跨平台认证需要, 尤其是在大型环境)
- Form-based authentication (用于简单的用户界面)
- OpenID 认证
- Authentication based on pre-established request headers (such as Computer Associates Siteminder) 根据预先建立的请求有进行验证
- JA-SIG Central Authentication Service (CAS,一个开源的SSO系统 )
- Transparent authentication context propagation for Remote Method Invocation (RMI) and HttpInvoker (Spring远程协议)
- Automatic "remember-me" authentication (你可以勾选一个框以避免预定的时间段再认证)
- Anonymous authentication (让每一个未经验证的访问自动假设为一个特定的安全标识)
- Run-as authentication (在一个访问应该使用不同的安全标识时非常有用)
- Java Authentication and Authorization Service (JAAS)
- JEE container autentication (所以如果愿你以可以任然使用容器管理的认证)
- Kerberos
- Java Open Source Single Sign On (JOSSO) *
- OpenNMS Network Management Platform *
- AppFuse *
- AndroMDA *
- Mule ESB *
- Direct Web Request (DWR) *
- Grails *
- Tapestry *
- JTrac *
- Jasypt *
- Roller *
- Elastic Path *
- Atlassian Crowd *
- Your own authentication systems (see below)
Spring Security5.x官方文档地址如下:
https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/reference/htmlsingle/
https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/api/
Spring Security常用的11个权限拦截器
SecurityContextPersistenceFilter:
- 这个过滤器位于顶端,是第一个起作用的过滤器
- 验证用户session是否存在,存在则放到SecurityContextHolder中,不存在则创建后到SecurityContextHolder中
- 另一个作用是在过滤器执行完毕后清空SecurityContextHolder中的内容
LogoutFilter:
- 在用户发出注销请求时,清除用户的session以及SecurityContextHolder中的内容
AbstractAuthenticationProcessingFilter:
- 处理from表单登录的过滤器
DefaultLoginPageGeneratingFilter:
- 用来生成一个默认的登录页面
BasicAuthenticationFilter:
- 用于Basic验证
SecurityContextHolderAwareRequestFilter:
- 用于包装用户的请求
- 目的是为后续的程序提供一些额外的数据
RememberMeAuthenticationFilter:
- 当用户cookie中存在RememberMe标记时,会根据标记自动实现用户登录,并创建SecurityContext,授予相应的权限
AnonymousAuthenticationFilter:
- 保证操作统一性,当用户没有登录时,默认为用户分配匿名用户的权限,可以选择关闭匿名用户
ExceptionTranslationFilter:
- 处理FilterSecurityInterceptor中所抛出的异常,然后将请求重定向到相应的页面,或响应错误信息。也就是作为一个处理全局异常的Filter
SessionManagementFilter:
- 用于防御会话伪造×××,会销毁用户的session,并重新生成一个session
FilterSecurityInterceptor:
- 用户的权限控制都包含在这里
- 如果用户未登陆就会抛出用户未登陆的异常
- 如果用户已登录但是没有访问当前资源的权限,就会抛出拒绝访问异常
- 如果用户已登录并具有访问当前资源的权限,则放行
以上就是Spring Security常用的11个权限拦截器,那么这些拦截器是按什么样的顺序执行的呢?这就需要先了解一下FilterChainProxy这个过滤器链代理类了:
- FilterChainProxy可以按照指定的顺序调用一组Filter,使这组Filter既能完成验证授权的本职工作,又能享用Spring IOC的功能,来方便的得到其他依赖的资源
基于SpringBoot的SpringSecurity环境快速搭建
在项目中新建一个config包,在该包下创建 SpringSecurityConfig 配置类,用于配置Spring Security的拦截规则。代码如下:
package org.zero.security.securitydemo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @program: security-demo
* @description: Spring Security 配置类
* @author: 01
* @create: 2018-08-29 23:20
**/
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 定义一个简单的访问规则
http.authorizeRequests()
.antMatchers("/").permitAll() // 允许任意访问根路径
.anyRequest().authenticated() // 除根路径以外的请求都需要验证
.and().logout().permitAll() // 允许任意访问注销路径
.and().formLogin(); // 允许表单登录
// 禁用默认的csrf验证
http.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
// 不拦截静态资源的访问
web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
}
}
然后新建一个controller包,在该包中创建 DemoController 控制器类,用于开启一些接口进行测试。代码如下:
package org.zero.security.securitydemo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @program: security-demo
* @description: spring security demo
* @author: 01
* @create: 2018-08-29 23:14
**/
@RestController
public class DemoController {
@GetMapping("/")
public String demo(){
return "Hello Spring Security";
}
@GetMapping("/hello")
public String hello(){
return "Hello World";
}
}
而访问/hello
,就会跳转到登录页面,需要进行验证,这就代表SpringSecurity的配置:
基于SpringSecurity权限管理Case实操
Case1、简单的登录:
SpringSecurity自带有一套基于内存的验证,这样我们仅需要实现简单的登录功能的时候,就不需要额外去创建数据库了。在 SpringSecurityConfig 类中,加入如下方法:
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication(). // 使用基于内存的认证方式
passwordEncoder(new BCryptPasswordEncoder()). // 设置密码的加密方式
withUser("admin"). // 设置用户名称
password(new BCryptPasswordEncoder().encode("123456")). // 设置密码
roles("ADMIN"); // 自定义该用户的角色
}
...
}
重启项目,当访问受控制的资源时,就会跳转到如下登录页面,输入设定好的用户名和密码:
Case2、有指定的角色,每个角色有指定的权限:
即便是简单的登录,也可能会遇到有一些资源需要管理员角色才能访问。所以我们来看看如何限定一个资源只能被管理员用户访问。在configure方法中,增加一个普通用户,代码如下:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
passwordEncoder(new BCryptPasswordEncoder()).
withUser("admin").
password(new BCryptPasswordEncoder().encode("123456")).
roles("ADMIN");
auth.inMemoryAuthentication().
passwordEncoder(new BCryptPasswordEncoder()).
withUser("user").
password(new BCryptPasswordEncoder().encode("user")).
roles("USER");
}
在DemoController类中增加一个接口,并指定这个接口只能被admin角色的用户访问。代码如下:
@RestController
// 开启验证
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class DemoController {
// 指定该接口只能被ADMIN角色的用户访问,ROLE_这个前缀是固定要写的
@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/roleAuth")
public String role(){
return "admin auth";
}
...
}
@PreAuthorize里的表达式可以使用 and 、or这种运算符,例如:
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_ROOT')")
除了@PreAuthorize注解外,还有:
- @PostAuthorize:方法执行完后再进行角色验证
- @PreFilter:方法执行前进行验证,用于过滤集合类型的参数或返回值
- @PostFilter:方法执行后进行验证,用于过滤集合类型的参数或返回值
Case3、自定义密码加密:
我们可以自定义自己的加密方式去做密码的加密及匹配,我这里使用MD5作为演示。首先创建一个 MyPasswordEncoder 类并实现 PasswordEncoder 接口。具体代码如下:
package org.zero.security.securitydemo.encoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.DigestUtils;
/**
* @program: security-demo
* @description: 自定义密码加密器
* @author: 01
* @create: 2018-09-07 21:43
**/
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
// 使用Spring自带的工具进行MD5加密
return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
// 验证密码加密后是否一致
return encode(rawPassword).equals(encodedPassword);
}
}
使用我们自己自定义的密码加密器,修改configure方法的代码如下:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
passwordEncoder(new MyPasswordEncoder()).
withUser("admin").
password(new MyPasswordEncoder().encode("123456")).
roles("ADMIN");
auth.inMemoryAuthentication().
passwordEncoder(new MyPasswordEncoder()).
withUser("user").
password(new MyPasswordEncoder().encode("user")).
roles("USER");
}
Case4、参数验证:
通过@PreAuthorize
注解,我们可以在方法执行前,进行权限参数的验证。例如我要验证id小于时,且username参数的值和当前登录的用户名一致。代码如下:
@PreAuthorize("#id<10 and principal.username.equals(#username)")
@GetMapping("/check_info")
public String checkInfo(Integer id, String username) {
return "success";
}
如果参数是一个对象也可以进行校验,代码如下:
@PreAuthorize("#user.username.equals('admin')")
@GetMapping("/check_user")
public String checkUser(User user) {
return "success";
}
总结
优点:
- 提供了一套安全框架,而且这个框架是可以用的
- 提供了很多用户认证功能,实现相关接口即可,节约大量开发工作
- 基于Spring,使得它易于集成到Spring项目中,且封装了许多方法
缺点:
- 配置文件多,角色被 “编码” 到配置文件或源文件中,RBAC不明显
- 对于系统中用户、角色、权限之间的关系,没有可操作的界面
- 大数据量的情况下,几乎不可用