SpringSecurity简介:
权限管理中的相关概念
主体 principal:
使用系统的用户或设备或从其他系统远程登录的用户等等,简单说就是谁使用系统谁就是主体。
认证 authentication:
权限管理系统确认一个主体的身份,允许主体进入系统。简单说就是“主体”证明自己是谁。
授权 authorization:
将操作系统的“权力”“授予”“主体”,这样主体就具备了操作系统中特定功能的能力。 所以简单来说,授权就是给用户分配权限。
SpringSecurity本质是过滤器链:
客户端发起一个请求,在请求到达Controller前Security通过一系列的过滤处理,完成对用户的认证授权等相关处理。需要注意的是在传统业务系统开发中,我们一般会在UserController中实现一个login接口来处理用户登录,但在使用Security时不需要在Controller实现login接口,它帮助我们在过滤器中实现了用户密码登录。默认通过UsernamePasswordAuthenticationFilter过滤器实现,从表单中读取用户名密码进行认证登录。
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
主要过滤器
ExceptionTranslationFilter:异常处理过滤器,凡是在过滤器环节出现的错误都或转到该过滤器进行统一处理
UsernamePasswordAuthenticationFilter:默认的认证过滤器,从请求中读取表单用户名密码数据进行校验,如果是JSON提交需要对其进行重写
FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部
自定义添加过滤器
除了SpringSecurity默认的过滤器,我们还可以添加自己的过滤器来进行自定义认证或授权。在SpringBoot开发中,SpringSecurity自动配置会向容器中自动注入相关过滤器,因此如果自己也有过滤器的情况下回导致过滤器顺序混乱,建议通过SpringSecurity统一管理过滤器。
如下,我们在自己的配置类中重写configure(HttpSecurity http)配置方法,自定义添加自己的过滤器
1 @Configuration //告诉SpringBoot该类是一个配置类,自动装配到IOC容器
2 @EnableWebSecurity //全局开启SpringSecurity
3 @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) //开启权限注解,之后会介绍
4 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
5 @Override
6 protected void configure(HttpSecurity http) throws Exception {
7 //添加自定义过滤器在某过滤器前执行
8 http.addFilterBefore(wrapperFilter, UsernamePasswordAuthenticationFilter.class);
9 //添加自定义过滤器在某过滤器之后
10 http.addFilterAfter(filter,UsernamePasswordAuthenticationFilter.class);
11 //添加过滤器在最后
12 http.addFilter(filter);
13 }
14 }
配置常用组件:
WebSecurityConfigurerAdapter(Security配置适配器)
UsernamePasswordAuthentcationFilter(用户名密码认证过滤器)
UserDetailService(用户权限数据查询服务)
TokenRepository(记住我Token Dao)
PasswordEncoder(NoOpPasswordEncoder、BCryptPasswordEncoder加密方式)
HttpSecutity常用配置:
Remember(记住我):
1 .rememberMe()
2 .tokenRepository(tokenRepository) //记住我的token管理Dao,操作数据库的记住我缓存
3 .tokenValiditySeconds(60) //token的有效时长
formLogin(表单登录):
1 .formLogin()
2 .loginPage("/login_page") //登陆页面
3 .loginProcessingUrl("/login") //登陆请求处理接口,Spring security默认的处理登录接口是/login这个自带的接口
4 .usernameParameter("name") //指定用户名参数名称
5 .passwordParameter("passwd") //指定密码参数名称
6 .permitAll() //将登录操作url放行
authorizeRequests(url请求权限):
.authorzeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN") //具备特定角色可访问
.antMatchers("/user/**")
.access("hasAnyRole('ADMIN','USER')") //参数以表达式方式书写,多个以 and 连接
//除了.permitAll()的url,其余所有URL需要认证后访问
.anyRequest()
.authenticated()
logout(登出):
1 .logout() //开启注销登陆
2 .logoutUrl("/logout") //注销登陆请求url
3 .clearAuthentication(true) //清除身份信息
4 .invalidateHttpSession(true) //session失效
5 .addLogoutHandler(new LogoutHandler() { //注销处理
6 @Override
7 public void logout(HttpServletRequest req,
8 HttpServletResponse resp,
9 Authentication auth) {
10
11 }
12 })
13 .logoutSuccessHandler(new LogoutSuccessHandler() { //注销成功处理
14 @Override
15 public void onLogoutSuccess(HttpServletRequest req,
16 HttpServletResponse resp,
17 Authentication auth)
18 throws IOException {
19 resp.sendRedirect("/login_page"); //跳转到自定义登陆页面
20 }
21 })
csrf(跨站脚本伪造):
1 .csrf().disabl(); //前后端分离时一般关闭
RemenberMe记住我(自动登录)原理:
总结配置流程
- 认证用户实现UserDetails接口
- 用户来源的Service实现UserDetailsService接口,实现loadUserByUsername()方法,从数据库中获取数据
- 实现自己的过滤器继承UsernamePasswordAuthenticationFilter,重写attemptAuthentication()和successfulAuthentication()方法实现自己的逻辑
- Spring Security的配置类继承自WebSecurityConfigurerAdapter,重写里面的两个config()方法
- 如果使用RSA非对称加密,就准备好RSA的配置类,然后在启动类中加入注解将其加入IOC容器
CSRF跨站请求伪造
攻击者利用已经登录(认证)某网站的浏览器,向网站发起恶意请求
原理:攻击者利用某种方式让受害者浏览器发起请求访问正常登录过的网站地址,此时由于浏览器保存着网站Cookie,网站接收请求并处理操作。攻击者可以利用这种方式发起任何正常网站用户可以进行的操作。
要完成一次CSRF攻击,受害者必须依次完成两个步骤:
1.登录受信任网站A,并在本地生成Cookie。
2.在不登出A的情况下,访问危险网站B。
从Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF 攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT和DELETE方法进行防护。
注解说明
权限校验
@EnableGlobalMethodSecurity:开启方法注解权限校验
参数:
- securedEnable=true 开启@Securd注解
- prePostEnable=true 开启@Preauthorize@PostAuthorize注解
@Securd("ROLE_xxx"):用户具有某个权限(角色)才能访问
@Preauthorize("hashAnyRole('ROLE_ADMIN')"):进入方法之前进行权限严重
1 @Service
2 public class MethodService {
3 @Secured("ROLE_ADMIN") //访问此方法需要ADMIN角色
4 public String admin() {
5 return "hello admin";
6 }
7 @PreAuthorize("hasRole('ADMIN') and hasRole('DBA')") //访问此方法需要ADMIN且DBA
8 public String dba() {
9 return "hello dba";
10 }
11 @PreAuthorize("hasAnyRole('ADMIN','DBA','USER')") //三个都行
12 public String user() {
13 return "user";
14 }
15 }
参数过滤
@PostFilter:对返回值进行过滤
@PreFilter:对方法参数进行过滤
用法:
@PostFilter("filterObject.属性==xx"):过滤对象属性,当表达式为true时允许数据通过,为false的数据会被过滤掉
开起security
注解 @EnableWebSecurity在 Spring boot 应用中使用 Spring Security,用到了 @EnableWebSecurity注解,官方说明为,该注解和 @Configuration 注解一起使用, 注解 WebSecurityConfigurer 类型的类,或者利用@EnableWebSecurity 注解继承 WebSecurityConfigurerAdapter的类,这样就构成了 Spring Security 的配置。
单点登录相关
SpringSecurity总结理解:
SpringSecurity的配置非常灵活可拓展性很强,因此使用该安全组件可以完成很多自定义配置。首先分析项目中的几个要素:认证路径(登录处理)、需要放行的路径,需要授权的路径、是否使用session、是否禁用csrf、配置跨域过滤器、是否自定义认证成功后失败的逻辑处理、用户认证失败或权限不足的响应逻辑。
就以我现在的项目分析:
对于前后端分离的项目首先要考虑到禁用session和csrf因为我们用jwt token来解决http的无状态性,需要注意的是禁用session后SecurityContextHolder会失效,因为它默认依赖session来进行上下文处理。
然后对于静态资源的url放行处理,和对公开访问接口的放行处理,例如登录处理接口、注册接口、验证码获取接口等。做完这些放行处理之后,再对剩下所有的url进行认证拦截处理。
再然后就需要做对于token认证授权的一些适配配置问题进行自定义处理,比如登录成功后我们需要将用户的token存入redis,那么这个逻辑可以通过实现UsernamePasswordAuthenticationFilter然后重写successfulAuthentication和unsuccessfulAuthentication方法,在successfulAuthentication方法中我们可以自定义登录成功后的处理逻辑,如token处理和security上下文处理等。unsuccessfulAuthentication一般就返回失败就行了。注意我们自己实现的UsernamePasswordAuthenticationFilter想让他生效需要在配置中让他替换原来的通过http.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
还有一种方法通过http.formLogin().successHandler().failureHandler();来设置认证成功和失败的处理器;
注意,如果我们使用token来维护状态,那么每个请求都是需要通过token来鉴权的,所以我们还需要写一个过滤器来维护token校验逻辑,写一个过滤器获取请求中的token做校验逻辑,校验处理成功后我们还可以将用户数据添加到SecurityContextHolder中,以便在后续处理中获取当前请求用户数据信息。
还有一个需要注意的地方,关于用户的认证校验逻辑,一般我们通过实现UsernamePasswordAuthenticationFilter并重写attemptAuthentication方法进行认证校验。还有一种方法是不实现UsernamePasswordAuthenticationFilter,而是提供一个我们的身份提供者,一个实现了AuthenticationProvider接口的类,然后将认证校验逻辑卸载authenticate()方法中,并且通过authenticationProvider()方式进行配置。
由过滤器链组成,通过配置可以开启或关闭特定过滤器,其中认证过滤器最终都会创建一个对应的XXXToken,例如UsernamePasswordAuthentication会创建一个UsernamePasswordAuthenticationToken。创建好了之后通过实现了authenticationManager接口的ProviderManager遍历所有实现了AuthenticationProvider身份提供者进行身份校验逻辑。注意每个ProviderManager都只处理自己关联的Token,如RememberMeAuthenticationProvider只处理RememberMeAuthenticationToken。当遍历到其中一个ProviderManager能够成功验证时用户就认证成功了。