欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Security4 学习笔记——01

程序员文章站 2022-06-04 23:41:10
...

Spring Security4 学习笔记

Spring Security是什么?

  • Spring Security提供了基于Java EE的企业应用软件全面的安全服务。
  • 它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能。

基础说明

  • 1.注解 @EnableWebSecurity
    • 该注解和 @Configuration 注解一起使用, 注解 WebSecurityConfigurer 类型的类,或者利用@EnableWebSecurity 注解继承 WebSecurityConfigurerAdapter的类,这样就构成了 Spring Security 的配置。
    • @EnableWebSecurity 注解将会启用Web安全功能。
  • 2.抽象类 WebSecurityConfigurerAdapter
    • WebSecurityConfigurerAdapter 提供了一种便利的方式去创建 WebSecurityConfigurer的实例,只需要重写 WebSecurityConfigurerAdapter 的方法,即可配置拦截什么URL、设置什么权限等安全控制。
    • 创建类SecurityConfiguration继承WebSecurityConfigurerAdapter,来对我们应用中所有的安全相关的事项(所有url,验证用户名密码,表单重定向等)进行控制。
    •  
      configure(WebSecurity) 通过重载,配置Spring Security的Filter链
      configure(HttpSecurity) 通过重载,配置如何通过拦截器保护请求
      configure(AuthenticationManagerBuilder) 通过重载,配置user-detail服务
  • 3.方法 configure(AuthenticationManagerBuilder auth)configure(HttpSecurity http)
  • 4.类 AuthenticationManagerBuilder
    • AuthenticationManagerBuilder 用于创建一个 AuthenticationManager,让我能够轻松的实现内存验证、LADP验证、基于JDBC的验证、添加UserDetailsService、添加AuthenticationProvider。

基于内存的认证In-Memory Authentication

  • @Bean 
    public UserDetailsService userDetailsService() throws Exception {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("user").password("password").roles("USER").build());
    manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build());
    return manager;
    }

基于数据库表用户存储认证

  • 通常我们都会将用户数据存储在关系型数据库中,并通过jdbc进行访问。spring security使用以jdbc为支撑的用户存储,我们可以使用下面的方式进行配置。
  •  
    /**
    • 配置user-detail服务
    • @param auth
    • @throws Exception
      */
      @Override
      public void configure(AuthenticationManagerBuilder auth)throws Exception{
      //基于数据库的用户存储、认证
      auth.jdbcAuthentication().dataSource(dataSource)
      .usersByUsernameQuery(“select account,password,true from user where account=?”)
      .authoritiesByUsernameQuery(“select account,role from user where account=?”);
      }
  • @Autowired 
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
    .jdbcAuthentication()
    .dataSource(dataSource)
    .withDefaultSchema()
    .withUser("user").password("password").roles("USER").and()
    .withUser("admin").password("password").roles("USER", "ADMIN");
    }
  • 配置自定义的用户存储认证

    • 这种方式更为灵活,更适合在生产环境使用。这种方式不在局限于存储环境。自定义的方式也很简单。只需要提供一个UserDetailService接口实现即可。
    •  
      @Service
      public class SecurityUserDetailsService implements UserDetailsService{

      @Autowired
      private UserDao userDao;

      /**

      • 校验用户
      • @param username
      • @return
      • @throws UsernameNotFoundException
        */
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user = userDao.queryByUserName(username);
        if(user == null)throw new UsernameNotFoundException(“user not found”);

        return new org.springframework.security.core.userdetails.User(user.getAccount(),user.getPassword(),userDao.getUserGrantedAuthoritys(user.getId()));
        }
        }

    密码加密策略

    • 通常我们在存储密码的时候都是进行加密的,spring security默认提供了三种密码存储方式,同时也可以使用自定义的加密方式:

      • NoOpPasswordEncoder 明文方式保存
      • BCtPasswordEncoder 强hash方式加密
      • StandardPasswordEncoder SHA-256方式加密
      • 实现PasswordEncoder接口 自定义加密方式
    •  
      /**
      • 配置user-detail服务
      • @param auth
      • @throws Exception
        */
        @Override
        public void configure(AuthenticationManagerBuilder auth)throws Exception{
        //基于数据库的用户存储、认证
        auth.jdbcAuthentication().dataSource(dataSource)
        .usersByUsernameQuery(“select account,password,true from user where account=?”)
        .authoritiesByUsernameQuery(“select account,role from user where account=?”)
        .passwordEncoder(NoOpPasswordEncoder.getInstance());
        }

    请求拦截策略

    • spring security的请求拦截匹配有两种风格,ant风格和正则表达式风格。编码方式是通过重载configure(HttpSecurity)方法实现。
    •  
      /**
      • 拦截请求
      • @param http
      • @throws Exception
        */
        @Override
        public void configure(HttpSecurity http)throws Exception{
        http.authorizeRequests()
        .antMatchers(“/”,”/css/“,”/js/“).permitAll() //任何人都可以访问
        .antMatchers(“/admin/**”).access(“hasRole(‘ADMIN’)”) //持有user权限的用户可以访问
        .antMatchers(“/user/**”).hasAuthority(“ROLE_USER”);
        }
  • 保护请求方法
  •  
    access(String) 如果给定的SpEL表达式计算结果为true,就允许访问
    anonymous() 允许匿名用户访问
    authenticated() 允许认证过的用户访问
    denyAll() 无条件拒绝所有访问
    fullyAuthenticated() 如果用户是完整认证的话(不是通过Remember-me功能认证的),就允许访问
    hasAnyAuthority(String...) 如果用户具备给定权限中的某一个的话,就允许访问
    hasAnyRole(String...) 如果用户具备给定角色中的某一个的话,就允许访问
    hasAuthority(String) 如果用户具备给定权限的话,就允许访问
    hasIpAddress(String) 如果请求来自给定IP地址的话,就允许访问
    hasRole(String) 如果用户具备给定角色的话,就允许访问
    not() 对其他访问方法的结果求反
    permitAll() 无条件允许访问
    rememberMe() 如果用户是通过Remember-me功能认证的,就允许访问
  • 通常我们都是使用http发送数据,这种方式是不安全的。对于敏感信息我们通常都是通过https进行加密发送。spring security对于安全性通道也提供了一种方式。我们可以在配置中添加requiresChannel()方法使url强制使用https。
  •  
    /**

    • 拦截请求
    • @param http
    • @throws Exception
      */
      @Override
      public void configure(HttpSecurity http)throws Exception{
      http.authorizeRequests()
      .antMatchers(“/”,”/css/“,”/js/“).permitAll() //任何人都可以访问
      .antMatchers(“/admin/**”).access(“hasRole(‘ADMIN’)”) //持有user权限的用户可以访问
      .antMatchers(“/user/**”).hasAuthority(“ROLE_USER”)
      .and()
      .requiresChannel().antMatchers(“/admin/info”).requiresSecure();
      }

      • 只要是对“/admin/info”的请求,spring security都认为需要安全性通道,并自动将请求重定向到https上。

      • 与之相反,如果有些请求不需要https传送,可以使用requiresInsecure()替代requiresSecure(),将请求声明为始终使用http传送。

  • 防止CSRF
    • spring security从版本3.2开始,默认就会启用CSRF防护。spring security通过一个同步token的方式来实现CSRF防护功能。它会拦截状态变化的请求,并检查CSRF token。如果请求中不包含CSRF token的话,或者token不能与服务器端的token匹配,请求就会失败,并抛出CsrfException异常。
    • 这样就spring security就会自动生成csrf token。如果想关闭csrf防护,需要作的也很简单,只需要调用一下csrf().disable();即可。
    •  
      /**
      • 拦截请求
      • @param http
      • @throws Exception
        */
        @Override
        public void configure(HttpSecurity http)throws Exception{
        http.authorizeRequests()
        .antMatchers(“/”,”/css/“,”/js/“).permitAll() //任何人都可以访问
        .antMatchers(“/admin/**”).access(“hasRole(‘ADMIN’)”) //持有user权限的用户可以访问
        .antMatchers(“/user/**”).hasAuthority(“ROLE_USER”)
        .and().csrf().disable();
        }
  • remember-me功能
    • remember-me是一个很重要的功能,用户肯定不希望每次都输入用户名密码进行登录。spring security提供的remember-me功能使用起来非常简单。启用这个功能只需要调用rememberMe()方法即可。
    •  
      /**
      • 拦截请求
      • @param http
      • @throws Exception
        */
        @Override
        public void configure(HttpSecurity http)throws Exception{
        http.authorizeRequests()
        .antMatchers(“/”,”/css/“,”/js/“).permitAll() //任何人都可以访问
        .antMatchers(“/admin/**”).access(“hasRole(‘ADMIN’)”) //持有user权限的用户可以访问
        .antMatchers(“/user/**”).hasAuthority(“ROLE_USER”)
        .and().rememberMe().key(“abc”).rememberMeParameter(“remember_me”).rememberMeCookieName(“my-remember-me”).tokenValiditySeconds(86400);
        }
  • 自定义登录页面

    • spring security会提供一个默认的登录页面,如果你想使用自己的登录页面,可以这样设置。
      •  
        /**
        • 拦截请求
        • @param http
        • @throws Exception
          */
          @Override
          public void configure(HttpSecurity http)throws Exception{
          http.authorizeRequests()
          .antMatchers(“/”,”/css/“,”/js/“).permitAll() //任何人都可以访问
          .antMatchers(“/admin/**”).access(“hasRole(‘ADMIN’)”) //持有user权限的用户可以访问
          .antMatchers(“/user/**”).hasAuthority(“ROLE_USER”)
          .and().formLogin()
          .loginPage(“/login”).usernameParameter(“username”).passwordParameter(“password”)
          .and().exceptionHandling().accessDeniedPage(“/loginfail”);
          }
    • 通过formLogin()方法来设置使用自定义登录页面,loginPage是登录页面地址,accessDeniePage登录失败跳转地址。

    handling Logouts

    • 与配置登录功能类似,您也有各种选项来进一步定制您的注销需求:
    • 注销后:
      • 使HTTP会话无效
      • 清理已配置的任何RememberMe身份验证
      • 清除 SecurityContextHolder
      • 重定向到 /login?logout
    •  
      protected void configure(HttpSecurity http) throws Exception {
      http
      .logout()
      .logoutUrl("/my/logout")
      .logoutSuccessUrl("/my/index")
      .logoutSuccessHandler(logoutSuccessHandler)
      .invalidateHttpSession(true)
      .addLogoutHandler(logoutHandler)
      .deleteCookies(cookieNamesToClear)
      .and()
      ...
      }
    • 注意:
      • 提供注销支持。使用时会自动应用WebSecurityConfigurerAdapter。
      • 触发注销的URL(默认为/logout)。如果启用了CSRF保护(默认),则该请求也必须是POST。
      • 注销后重定向到的URL。默认是/login?logout。
      • 我们指定一个自定义LogoutSuccessHandler。如果指定了,logoutSuccessUrl()则忽略。
      • 指定HttpSession在注销时是否使其无效。默认情况下这是真的。
      • 添加一个LogoutHandler。默认情况下SecurityContextLogoutHandler添加为最后一个LogoutHandler。
      • 允许指定在注销成功时删除的cookie的名称。
    • LogoutHandler实现指示能够参与注销处理的类
    • LogoutSuccessHandler 被成功注销后调用LogoutFilter,来处理如重定向或转发到相应的目的地。
      • 提供以下实现:
        • SimpleUrlLogoutSuccessHandler
          • 无需SimpleUrlLogoutSuccessHandler直接指定。相反,流畅的API通过设置提供快捷方式logoutSuccessUrl()。这将设置SimpleUrlLogoutSuccessHandler封底。发生注销后,提供的URL将重定向到。默认是/login?logout。
        • HttpStatusReturningLogoutSuccessHandler
          • HttpStatusReturningLogoutSuccessHandler可以在REST API类型场景有趣。成功注销后,LogoutSuccessHandler 您可以提供要返回的纯HTTP状态代码,而不是重定向到URL 。如果未配置,则默认返回状态代码200。

    AuthenticationProvider认证提供者

    • 可以通过将自定义公开AuthenticationProvider为bean 来定义自定义身份验证。
    • @Bean 
      public SpringAuthenticationProvider springAuthenticationProvider() {
      return new SpringAuthenticationProvider();
      }

    UserDetailsService用户详情服务

    • 可以通过将自定义公开UserDetailsService为bean 来定义自定义身份验证。
    • 例如,以下将自定义身份验证,假设SpringDataUserDetailsService实现UserDetailsService:
    • @Bean 
      public SpringDataUserDetailsService springDataUserDetailsService() {
      return new SpringDataUserDetailsService();
      }
    • 您还可以通过将PasswordEncoderbean 公开为bean 来自定义密码的编码方式。例如,如果使用bcrypt,则可以添加bean定义,如下所示:
    • @Bean 
      public BCryptPasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
      }

    Multiple HttpSecurity 多重HttpSecurity

    • 我们可以配置多个HttpSecurity实例,就像我们可以有多个块一样。关键是要WebSecurityConfigurerAdapter多次扩展。例如,以下是对以URL开头的不同配置的示例/api/。
    • @EnableWebSecurity
      public class MultiHttpSecurityConfig {
      @Bean
      public UserDetailsService userDetailsService() throws Exception {
          InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
          manager.createUser(User.withUsername("user").password("password").roles("USER").build());
          manager.createUser(User.withUsername("admin").password("password").roles("USER","ADMIN").build());
          return manager;
      }
      
      @Configuration
      @Order(1)                                                     // 1
      public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
          protected void configure(HttpSecurity http) throws Exception {
              http
                  .antMatcher("/api/**")                               // 2
                  .authorizeRequests()
                      .anyRequest().hasRole("ADMIN")
                      .and()
                  .httpBasic();
          }
      }
      
      @Configuration                                                   // 3
      public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
      
      
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http
              .authorizeRequests()
                  .anyRequest().authenticated()
                  .and()
              .formLogin();
      }
      
      } }
      • 1.创建一个WebSecurityConfigurerAdapter包含的实例,@Order以指定WebSecurityConfigurerAdapter应首先考虑哪个实例。
        1. 这些http.antMatcher状态HttpSecurity仅适用于以。
      • 3.创建另一个实例WebSecurityConfigurerAdapter。如果URL未/api/以此配置启动,则将使用此配置。之后考虑此配置,ApiWebSecurityConfigurationAdapter因为它具有之后的@Order值1(无@Order默认为最后)。

    方法安全

    • EnableGlobalMethodSecurity
      • 可以@EnableGlobalMethodSecurity在任何@Configuration实例上使用注释启用基于注释的安全性。
      • 例如,以下内容将启用Spring Security的@Secured注释。
        • @EnableGlobalMethodSecurity(securedEnabled = true) 
          public class MethodSecurityConfig {
          // ...
          }
      • 然后,在方法(类或接口)上添加注释会相应地限制对该方法的访问。Spring Security的本机注释支持为该方法定义了一组属性。这些将传递给AccessDecisionManager,以便做出实际决定:
        • public interface BankService { 
          @Secured("IS_AUTHENTICATED_ANONYMOUSLY")
          public Account readAccount(Long id);
          @Secured("IS_AUTHENTICATED_ANONYMOUSLY")
          public Account[] findAccounts();
          @Secured("ROLE_TELLER")
          public Account post(Account account, double amount);
          }
      • 允许应用简单的基于角色的约束
        • @EnableGlobalMethodSecurity(prePostEnabled = true) 
          public class MethodSecurityConfig {
          // ...
          }
    • GlobalMethodSecurityConfiguration
      • 可能需要执行比@EnableGlobalMethodSecurity注释允许更复杂的操作。对于这些实例,您可以扩展GlobalMethodSecurityConfiguration确保@EnableGlobalMethodSecurity子类上存在注释。
        • @EnableGlobalMethodSecurity(prePostEnabled = true) 
          public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
          @Override
          protected MethodSecurityExpressionHandler createExpressionHandler() {
          // ... create and return custom MethodSecurityExpressionHandler ...
          return expressionHandler;
          }
          }

    后处理配置对象

    • ObjectPostProcessor可用于修改或替换Java Configuration创建的许多Object实例。例如,如果要配置filterSecurityPublishAuthorizationSuccess属性,FilterSecurityInterceptor可以使用以下命令:
      • @Override 
        protected void configure(HttpSecurity http) throws Exception {
        http
        .authorizeRequests()
        .anyRequest().authenticated()
        .withObjectPostProcessor(new ObjectPostProcessor() {
        public O postProcess(
        O fsi) {
        fsi.setPublishAuthorizationSuccess(true);
        return fsi;
        }
        });
        }

    获取关于当前用户的信息

    • 在SecurityContextHolder我们内部存储当前与应用程序交互的主体的详细信息。Spring Security使用Authentication对象来表示此信息。您通常不需要自己创建Authentication对象,但用户查询Authentication对象是相当常见的。您可以使用以下代码块(从应用程序的任何位置)获取当前经过身份验证的用户的名称,例如:
      • Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 
        if (principal instanceof UserDetails) {
        String username = ((UserDetails)principal).getUsername();
        } else {
        String username = principal.toString();
        }

        • 调用返回的对象getContext()是SecurityContext接口的实例。这是保存在线程本地存储中的对象。
    • Spring Security 安全拦截器和安全对象模型
      • Spring Security4 学习笔记——01
      • 1.AbstractSecurityInterceptor 为处理安全对象请求提供了一致的工作流程,通常:
        • 查找与当前请求关联的“配置属性”
          • “配置属性”可以被认为是对所使用的类具有特殊含义的String AbstractSecurityInterceptor。
          • 它们由ConfigAttribute框架内的接口表示。它们可能是简单的角色名称,也可能具有更复杂的含义,具体取决于实现的复杂程度AccessDecisionManager。
          • 在AbstractSecurityInterceptor配置了SecurityMetadataSource它用来查找属性的安全对象。
        • 将安全对象,当前Authentication和配置属性提交AccessDecisionManager给授权决策
        • (可选)更改Authentication调用发生的位置
        • 允许安全对象调用继续(假设已授予访问权限)
        • 调用AfterInvocationManager如果配置,一旦调用返回。如果调用引发了异常,AfterInvocationManager则不会调用该异常。
      • 2.RunAsManager
        • 假设AccessDecisionManager决定允许请求,AbstractSecurityInterceptor通常只会继续请求。
      • 3.AfterInvocationManager
        • 在安全对象调用继续进行然后返回 - 这可能意味着方法调用完成或过滤器链继续进行 - AbstractSecurityInterceptor获得最后一次机会来处理调用。