本文共 8936 字,大约阅读时间需要 29 分钟。
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 提供自己的一组认证功能。具体而言,Spring Security 目前支持所有这些技术集成的身份验证:
Spring Security5.x官方文档地址如下:
SecurityContextPersistenceFilter:
LogoutFilter:
AbstractAuthenticationProcessingFilter:
DefaultLoginPageGeneratingFilter:
BasicAuthenticationFilter:
SecurityContextHolderAwareRequestFilter:
RememberMeAuthenticationFilter:
AnonymousAuthenticationFilter:
ExceptionTranslationFilter:
SessionManagementFilter:
FilterSecurityInterceptor:
以上就是Spring Security常用的11个权限拦截器,那么这些拦截器是按什么样的顺序执行的呢?这就需要先了解一下FilterChainProxy这个过滤器链代理类了:
打开IDEA,创建一个SpringBoot项目:
勾选相应的模块:
在项目中新建一个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@EnableWebSecuritypublic 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 **/@RestControllerpublic class DemoController { @GetMapping("/") public String demo(){ return "Hello Spring Security"; } @GetMapping("/hello") public String hello(){ return "Hello World"; }}
启动项目,访问根目录,正常输出了相应的字符串:
而访问/hello
,就会跳转到登录页面,需要进行验证,这就代表SpringSecurity的配置:
Case1、简单的登录:
SpringSecurity自带有一套基于内存的验证,这样我们仅需要实现简单的登录功能的时候,就不需要额外去创建数据库了。在 SpringSecurityConfig 类中,加入如下方法:
@Configuration@EnableWebSecuritypublic 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"); // 自定义该用户的角色 } ...}
重启项目,当访问受控制的资源时,就会跳转到如下登录页面,输入设定好的用户名和密码:
访问成功:
访问logout接口可以退出登录:
Case2、有指定的角色,每个角色有指定的权限:
即便是简单的登录,也可能会遇到有一些资源需要管理员角色才能访问。所以我们来看看如何限定一个资源只能被管理员用户访问。在configure方法中,增加一个普通用户,代码如下:
@Overrideprotected 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"; } ...}
重启项目,登录非admin角色的用户:
访问roleAuth接口,会返回403错误:
登录admin用户,访问roleAuth接口成功:
@PreAuthorize里的表达式可以使用 and 、or这种运算符,例如:
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_ROOT')")
除了@PreAuthorize注解外,还有:
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方法的代码如下:
@Overrideprotected 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";}
优点:
缺点:
转载于:https://blog.51cto.com/zero01/2171944