Spring Security 提供了基于javaEE的企业应用软件全面的安全服务。这里特别强调支持使用Spring框架构件的项目,Spring框架是企业软件开发javaEE方案的领导者。若是你尚未使用Spring来开发企业应用程序,咱们热忱的鼓励你仔细的看一看。熟悉Spring特别是一来注入原理两帮助你更快更方便的使用Spring Security。css
人们使用Spring Secruity的缘由有不少,单大部分都发现了javaEE的Servlet规范或EJB规范中的安全功能缺少典型企业应用场景所需的深度。提到这些规范,重要的是要认识到他们在WAR或EAR级别没法移植。所以若是你更换服务器环境,这里有典型的大量工做去从新配置你的应用程序员安全到新的目标环境。使用Spring Security 解决了这些问题,也为你提供许多其余有用的,可定制的安全功能。html
正如你可能知道的两个应用程序的两个主要区域是“认证”和“受权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。“认证”,是创建一个他声明的主题的过程(一个“主体”通常是指用户,设备或一些能够在你的应用程序中执行动做的其余系统)。“受权”指肯定一个主体是否容许在你的应用程序执行一个动做的过程。为了抵达须要受权的目的,主体的身份已经有认证过程创建。这个概念是通用的而不仅在Spring Security中。java
Spring Security主要的组件图:程序员
在身份验证层,Spring Security 的支持多种认证模式。这些验证绝大多数都是要么由第三方提供,或由相关的标准组织,如互联网工程任务组开发。另外Spring Security 提供本身的一组认证功能。具体而言,Spring Security 目前支持全部这些技术集成的身份验证:web
Spring Security5.x官方文档地址以下:spring
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/数据库
SecurityContextPersistenceFilter:api
LogoutFilter:安全
AbstractAuthenticationProcessingFilter:bash
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 @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的配置:
Case一、简单的登陆:
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"); // 自定义该用户的角色 } ... }
重启项目,当访问受控制的资源时,就会跳转到以下登陆页面,输入设定好的用户名和密码:
访问成功:
访问logout接口能够退出登陆:
Case二、有指定的角色,每一个角色有指定的权限:
即使是简单的登陆,也可能会遇到有一些资源须要管理员角色才能访问。因此咱们来看看如何限定一个资源只能被管理员用户访问。在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"; } ... }
重启项目,登陆非admin角色的用户:
访问roleAuth接口,会返回403错误:
登陆admin用户,访问roleAuth接口成功:
@PreAuthorize里的表达式可使用 and 、or这种运算符,例如:
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_ROOT')")
除了@PreAuthorize注解外,还有:
Case三、自定义密码加密:
咱们能够自定义本身的加密方式去作密码的加密及匹配,我这里使用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"); }
Case四、参数验证:
经过@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"; }
优势:
缺点: