Spring Security4 学习笔记——01

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应首先考虑哪个实例。
  • 2.这些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<FilterSecurityInterceptor>() {
        		public <O extends FilterSecurityInterceptor> 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 安全拦截器和安全对象模型
    • 1.AbstractSecurityInterceptor 为处理安全对象请求提供了一致的工作流程,通常:
      • 查找与当前请求关联的“配置属性”
        • “配置属性”可以被认为是对所使用的类具有特殊含义的String AbstractSecurityInterceptor。
        • 它们由ConfigAttribute框架内的接口表示。它们可能是简单的角色名称,也可能具有更复杂的含义,具体取决于实现的复杂程度AccessDecisionManager。
        • 在AbstractSecurityInterceptor配置了SecurityMetadataSource它用来查找属性的安全对象。
      • 将安全对象,当前Authentication和配置属性提交AccessDecisionManager给授权决策
      • (可选)更改Authentication调用发生的位置
      • 允许安全对象调用继续(假设已授予访问权限)
      • 调用AfterInvocationManager如果配置,一旦调用返回。如果调用引发了异常,AfterInvocationManager则不会调用该异常。
    • 2.RunAsManager
      • 假设AccessDecisionManager决定允许请求,AbstractSecurityInterceptor通常只会继续请求。
    • 3.AfterInvocationManager
      • 在安全对象调用继续进行然后返回 - 这可能意味着方法调用完成或过滤器链继续进行 - AbstractSecurityInterceptor获得最后一次机会来处理调用。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值