# spring_security **Repository Path**: mr_sen/spring_security ## Basic Information - **Project Name**: spring_security - **Description**: SpringSecutity学习仓库 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-09-29 - **Last Updated**: 2022-05-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #SpringSecurity 分析 ## 用户相关类 - interface UserDetailsService 通过用户名查询用户 - UserDetails loadUserByUsername(String username) - interface UserDetails 封装用户的信息,如用户名,密码,状态是否可用等 - class User implements UserDetails 是UserDetails的实现 ## 密码相关类 - interface PasswordEncoder 密码编码的接口 - BCryptPasswordEncoder 是PasswordEncoder的实现,官方强推 ## 总结如何进行认证 ### 1、整合数据源 该部分略过,只要能查数据库就行 ### 2、实现UserDetailsService ```java /* * 就把他当作普通的service就好, * 也很符合我们平时用的service * */ @Service("userDetailsService") public class MyUserDetailService implements UserDetailsService { @Autowired UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser user_name = userMapper.selectOne(new QueryWrapper().eq("user_name", username)); if (user_name == null) { throw new UsernameNotFoundException("没这个人"); } String userPwd = user_name.getUserPwd(); PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); final String encode = passwordEncoder.encode(userPwd); ; List auths = AuthorityUtils.createAuthorityList("admin", "system", "ROLE_manager"); return new User(user_name.getUserName(), encode, auths); } } ``` ### 3、指定认证过程用的是哪个UserDetailsService(该service就是从数据库中查询有没有该用户),哪个PasswordEncoder) > 为什么要指定UserDetailsService呢? 因为去验证用户身份时,可以是在内存中的信息,可以是yml中的配置信息。 > 说白了你可以实现多个UserDetailsService,在不同的时机指定用哪个就可以 > 同理PasswordEncoder,也需要指定密码使用哪个加密方式加密的。 ```java /* * 1、编写一个配置类继承WebSecurityConfigurerAdapter ,@Configuration * 2、重写WebSecurityConfigurerAdapter的configure方法,就是在这个方法指定上述两个内容 * */ @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserDetailsService userDetailsService; @Autowired PasswordEncoder passwordEncoder; // 顺手把PasswordEncoder放到ioc中 @Bean PasswordEncoder passwordEncoder() { // 密码加密 return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } @Override public UserDetailsService userDetailsServiceBean() throws Exception { return super.userDetailsServiceBean(); } } ``` ## Security 原理分析 SpringSecurity 采用的是责任链的设计模式,它有一条很长的**过滤器链**。现在对这条过滤器链的各个进行说明: - WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。 - SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中, 然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中, 然后将 SecurityContextHolder 中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。 HeaderWriterFilter:用于将头信息加入响应中。 - CsrfFilter:用于处理跨站请求伪造。 - LogoutFilter:用于处理退出登录。 - UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。 默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password, 这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。 - DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。 - BasicAuthenticationFilter:检测和处理 http basic 认证。 - RequestCacheAwareFilter:用来处理请求的缓存。 - SecurityContextHolderAwareRequestFilter:主要是包装请求对象request。 - AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在 Authentication 对象, 如果不存在为其提供一个匿名 Authentication。 - SessionManagementFilter:管理 session 的过滤器 - ExceptionTranslationFilter:处理 AccessDeniedException 和 AuthenticationException 异常。 - FilterSecurityInterceptor:可以看做过滤器链的出口。 - RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。 ## SpringSecurity 流程图 ![p.jpg](https://www.cdnjson.com/images/2021/08/13/p.jpg) ## 流程说明 客户端发起一个请求,进入 Security 过滤器链。 1.当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler , 如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。 2.当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作, 如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理, 如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。 3.当到 FilterSecurityInterceptor 的时候会拿到 uri , 根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。 ## Security 配置 在 WebSecurityConfigurerAdapter 这个类里面可以完成上述流程图的所有配置 ### 配置类伪代码 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder()); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/resources/**/*.html", "/resources/**/*.js"); } @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin() .loginPage("/login_page") .passwordParameter("username") .passwordParameter("password") .loginProcessingUrl("/sign_in") .permitAll() .and().authorizeRequests().antMatchers("/test").hasRole("test") .anyRequest().authenticated().accessDecisionManager(accessDecisionManager()) .and().logout().logoutSuccessHandler(new MyLogoutSuccessHandler()) .and().csrf().disable(); http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class); http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler()); http.addFilterAfter(new MyFittler(), LogoutFilter.class); } } ``` ### 配置简介 - configure(AuthenticationManagerBuilder auth) > AuthenticationManager 的建造器,配置 AuthenticationManagerBuilder 会让Security 自动构建一个 AuthenticationManager(该类的功能参考流程图); > 如果想要使用该功能你需要配置一个 UserDetailService 和 PasswordEncoder。 > UserDetailsService 用于在认证器中根据用户传过来的用户名查找一个用户, PasswordEncoder 用于密码的加密与比对, > 我们存储用户密码的时候用PasswordEncoder.encode() 加密存储,在认证器里会调用 PasswordEncoder.matches() 方法进行密码比对。 > 如果重写了该方法,Security 会启用 DaoAuthenticationProvider 这个认证器,该认证就是先调用 UserDetailsService.loadUserByUsername > 然后使用 PasswordEncoder.matches() 进行密码比对,如果认证成功成功则返回一个 Authentication 对象。 - configure(WebSecurity web) > 这个配置方法用于配置静态资源的处理方式,可使用 Ant 匹配规则。 - configure(HttpSecurity http) > 这个配置方法是最关键的方法,也是最复杂的方法 ```java /* 这是配置登录相关的操作从方法名可知, 配置了登录页请求路径,密码属性名,用户名属性名,和登录请求路径, permitAll()代表任意用户可访问。 */ http .formLogin() .loginPage("/login_page") .passwordParameter("username") .passwordParameter("password") .loginProcessingUrl("/sign_in") .permitAll() ``` ```java /* * 以下配置是权限相关的配置,配置了一个 /test url 该有什么权限才能访问, * anyRequest() 表示所有请求,authenticated() 表示已登录用户才能访问, * accessDecisionManager() 表示绑定在 url 上的鉴权管理器 */ http .authorizeRequests() .antMatchers("/test").hasRole("test") .anyRequest().authenticated() .accessDecisionManager(accessDecisionManager()); ``` ### 登出相关配置 ```java http .logout() .logoutUrl("/logout") .logoutSuccessHandler(new MyLogoutSuccessHandler()) ``` 登出相关配置,这里配置了登出 url 和登出成功处理器: ```java http .exceptionHandling() .accessDeniedHandler(new MyAccessDeniedHandler()); ``` 上面代码是配置鉴权失败的处理器。 http.addFilterAfter(new MyFittler(), LogoutFilter.class); http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class); 上面代码展示如何在过滤器链中插入自己的过滤器,addFilterBefore 加在对应的过滤器之前, addFilterAfter 加在对应的过滤器之后,addFilterAt 加在过滤器同一位置, 事实上框架原有的 Filter 在启动 HttpSecurity 配置的过程中,都由框架完成了其一定程度上固定的配置,是不允许更改替换的。 根据测试结果来看,调用 addFilterAt 方法插入的 Filter ,会在这个位置上的原有 Filter 之前执行。