Spring-Security

Posted 是也不是

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring-Security相关的知识,希望对你有一定的参考价值。

SpringSecurity

原理

  • 一款控制基于SpringAOP或者Servlet过滤器的安全框架
  • 在web应用开发中,安全无疑是十分重要的,选择Spring Security来保护web应用是一个非常好的选择。
  • Spring Security 是spring项目之中的一个安全模块,可以非常方便与spring项目无缝集成。特别是在spring boot项目中加入spring security更是十分简单。

支持的认证方式

  • 可以通过 form 表单来认证
  • 可以通过 HttpBasic 来认证

核心组件

SecurityContext、SecurityContextHolder、Authentication、Userdetails 和 AuthenticationManager

  • SecurityContext 安全上下文,用户通过Spring Security 的校验之后,验证信息存储在SecurityContext中
  • SecurityContextHolder 其作用就是存储当前认证信息。

    • 看名知义,是一个holder,用来hold住SecurityContext实例的。在典型的web应用程序中,用户登录一次,然后由其会话ID标识。服务器缓存持续时间会话的主体信息。在Spring Security中,在请求之间存储SecurityContext的责任落在SecurityContextPersistenceFilter上,默认情况下,该上下文将上下文存储为HTTP请求之间的HttpSession属性。它会为每个请求恢复上下文SecurityContextHolder,并且最重要的是,在请求完成时清除SecurityContextHolder。SecurityContextHolder是一个类,他的功能方法都是静态的(static)。
    • SecurityContextHolder可以设置指定JVM策略(SecurityContext的存储策略),这个策略有三种:

      • MODE_THREADLOCAL:SecurityContext 存储在线程中。
      • MODE_INHERITABLETHREADLOCAL:SecurityContext 存储在线程中,但子线程可以获取到父线程中的 SecurityContext。
      • MODE_GLOBAL:SecurityContext 在所有线程中都相同。

      SecurityContextHolder默认使用MODE_THREADLOCAL模式,即存储在当前线程中。在spring security应用中,我们通常能看到类似如下的代码:

       Copy SecurityContextHolder.getContext().setAuthentication(token);

  • Authentication

    • authentication 直译过来是“认证”的意思,在Spring Security 中Authentication用来表示当前用户是谁,一般来讲你可以理解为authentication就是一组用户名密码信息。
  • UserDetails

    • 看命知义,是用户信息的意思。其存储的就是用户信息,
  • UserDetailsService

    • 提到了UserDetails就必须得提到UserDetailsService, UserDetailsService也是一个接口,且只有一个方法loadUserByUsername,他可以用来获取UserDetails。
    • 通常在spring security应用中,我们会自定义一个CustomUserDetailsService来实现UserDetailsService接口,并实现其public UserDetails loadUserByUsername(final String login);方法。我们在实现loadUserByUsername方法的时候,就可以通过查询数据库(或者是缓存、或者是其他的存储形式)来获取用户信息,然后组装成一个UserDetails,(通常是一个org.springframework.security.core.userdetails.User,它继承自UserDetails) 并返回
  • AuthenticationManager

    • 用来处理身份认证的类,也称之为认证管理器。
    • 他的最常用的实现类事 ProviderManager (认证管理中心)
    • AuthenticationManager 是一个接口,它只有一个方法,接收参数为Authentication,其定义如下:

      •  public interface AuthenticationManager {
         Authentication authenticate(Authentication authentication)
          throws AuthenticationException;
         }
    • AuthenticationManager 的作用就是校验Authentication,如果验证失败会抛出AuthenticationException异常。AuthenticationException是一个抽象类,因此代码逻辑并不能实例化一个AuthenticationException异常并抛出,实际上抛出的异常通常是其实现类,如DisabledException,LockedException,BadCredentialsException等。BadCredentialsException可能会比较常见,即密码错误的时候。
    • ProviderManager
      • AuthenticationManager的实现类
      • 管理AuthenticationProvider列表,每一个AuthenticationProvider都是一个认证器
      • 不同的认证器处理不同的Authentication对象的认证。providerManager相当于代理了多个认证器

三大configure()

  • HttpSecurity

    • 这个厉害了,做的大部分配置都是基于他来配置的
    • HttpSecurityBuilder 看名字就是用来构建 HttpSecurity 的。
    • 属性

       web.ignoring() 用来配置忽略掉的 URL 地址,一般对于静态文件,我们可以采用此操作。
       ​
       and 方法表示结束当前标签
       ​
       permitAll 表示登录相关的页面/接口不要被拦截。
       ​
       antMatchers 过滤条件,范围小的放在前面,范围大的放在后面
        .antMatchers("/login").permitAll()
        .antMatchers("/user/**").hasRole("admin")
        .antMatchers("/pubnews/**").hasRole("admin")

  • SecurityBuilder ,即WebSecurity

    • SecurityBuilder 就是用来构建过滤器链的
  • AuthenticationManagerBuilder

    • 用来构建 AuthenticationManager 的

被继承或实现的类

  • 实现UserDetails 类,他的作用就是存储用户的登录信息,包含用户名,密码以及用户角色、说白了就是存储
  • 有个方法是getAuthorities(),里面获得UserDetailsService实现类传递的角色信息

    • 我之前还以为这个类有什么大用,纳闷把角色传给他干啥,他难道会自己验证吗,哈哈,他的名字就告诉了我们,他就是获得用户角色用来鉴权的。登录成功与否不看他
  •  UserDetails 默认提供了:

         用户的权限集, 默认需要添加ROLE_ 前缀
         用户的加密后的密码, 不加密会使用{noop}前缀
         应用内唯一的用户名
         账户是否过期
         账户是否锁定
         凭证是否过期
         用户是否可用
       
  • 实现UserDetailsService类

    • 这个类厉害了,看名字,用户信息服务类。上面的Details是为了存储,他就是为了把需要存储的对象交给实现了UserDetail的类。核心方法 loadUserByUsername,通过用户名加载用户,查不到返回空,查到返回用户,并把用户的角色权限一并返回给实现了UserDetail的类

  • 实现PasswordEncoder类

    • 这个类用来自定义密码的加密解密,之前一直登陆错误的问题就出在这,默认是加密后验证数据库密码,但是数据库明明是明文密码。所以密码一直匹配不上

遇到的错误

  • java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

    在Spring Security中密码的存储格式是“{id}…………”。前面的id是加密方式,id可以是bcrypt、sha256等,后面跟着的是加密后的密码。也就是说,程序拿到传过来的密码的时候,会首先查找被“{”和“}”包括起来的id,来确定后面的密码是被怎么样加密的,如果找不到就认为id是null。这也就是为什么我们的程序会报错:There is no PasswordEncoder mapped for the id “null”。官方文档举的例子中是各种加密方式针对同一密码加密后的存储形式,原始密码都是“password”。

自定义系列

  • 如果想要自定义PasswordEncoder,通过下列方式导入配置文件

    •   /AuthenticationProvider 定义了 Spring Security 中的验证逻辑/
        @Bean
        public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider au = new DaoAuthenticationProvider();
        //对默认的UserDetailsService进行覆盖
        au.setUserDetailsService(userService);
       ​
        //下面是为了修改默认加密方式,用自己的
        // au.setPasswordEncoder(webPasswordEncoder);
        return au;
        }

实现表单登录

  • pom.xml导入依赖

    • <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
  • 实现表单登录

    • 不做任何配置的话,spring security采用默认的账号密码登录,账号user,密码在项目启动时会在控制台打印出来。
    • 关闭security.basic ,使用form表单页面登录。我们在实际项目中不可能会使用,http-basic方式的弹窗来让用户完成登录,而是会有一个登录页面。
  • 自定义WebSecurityConfigurerAdapter(创建 MySecurityConfig 并且继承WebSecurityConfigurerAdapter,重写它的configure(HttpSecurity http))

    •  @EnableWebSecurity
       public class MySecurityConfig extends WebSecurityConfigurerAdapter {
       
        @Override
        protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        .anyRequest().authenticated()//所有请求需登录
        .and()
        .formLogin()
        .and()
        .logout().logoutUrl("/logout");
        }
       }
  • 从数据库读取用户数据(创建一个服务实现UserDetailsService里的loadUserByUsername即可)

    •  @Service
       public class UserAuthService implements UserDetailsService {
        @Autowired
        private UserService userService;
       
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserEntity userEntity = userService.getByUsername(username);
        if (userEntity == null) {
        throw new UsernameNotFoundException("用户不存在!");
        }
        return new User(userEntity.getUsername(), userEntity.getPassword(), new ArrayList<>());
        }
       }
  • 将创建好的User服务注入到配置文件中MySecurityConfig

    • @Autowired
      private UserAuthService userAuthService;

      @Override
      protected void configure(AuthenticationManagerBuilder auth)throws Exception{

      auth.userDetailsService(userAuthService)

      }

  • 最后MySecurityConfig配置为:

    • @EnableWebSecurity
      public class MySecurityConfig extends WebSecurityConfigurerAdapter {

      @Autowired
      private UserAuthService userAuthService;
      
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http.authorizeRequests()
                  .anyRequest().authenticated()//所有请求需登录
                  .and()
                  .formLogin()
                  .and()
                  .logout().logoutUrl("/logout");
      }
      
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
          auth.userDetailsService(userAuthService).passwordEncoder(new BCryptPasswordEncoder());
      }

      }

以上是关于Spring-Security的主要内容,如果未能解决你的问题,请参考以下文章

了解 spring-security 上的 requestMatchers()

Spring-Security:认证后调用方法

如何使用spring-security通过用户名和密码登录?

Spring-security-3 浏览器后退按钮问题

Spring-security 的 CORS 问题

spring-security框架