springsecurity 使用浅谈(一)

1. 背景

springsecurity框架主要用于Web应用的认证和授权。所谓认证就是验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户。而授权就是经过认证后判断当前用户是否有权

限进行某个操作。认证和授权也是SpringSecurity作为安全框架的核心功能。

2. 前置知识

在传统的项目中,我们通常使用会话技术来保存用户信息并进行用户认证。会话是客户端和服务器之间连续的请求和响应过程,用于保持用户的状态信息。然而,HTTP协议本身是无状态的,服务器无法直接识别每个请求的来源,因此需要一种机制来标识和关联请求。

在会话管理中,有两种常见的实现方式:session(会话)和cookie(Cookie)。Session通过在服务器端记录信息来确定用户身份和状态,但这会增加服务器的存储压力。当客户端浏览器访问服务器时,服务器会在服务器端创建一个会话,并为该会话分配一个唯一的标识符,即sessionID。服务器将sessionID发送给客户端浏览器,通常通过设置一个名为"sessionID"的Cookie,将其存储在浏览器中。

一旦客户端浏览器保存了该Cookie,它会在后续的请求中自动发送给服务器,从而实现会话的持续。服务器通过sessionID可以在服务器端找到对应的会话数据,并根据其中的用户状态进行处理。如果浏览器禁用了Cookie,也可以通过URL重写的方式将sessionID作为查询参数发送给服务器。

另一方面,Cookie是服务器在HTTP响应中附带的一个小型文本文件,它存储在客户端浏览器中。一旦浏览器保存了某个Cookie,它会在后续的请求中自动将该Cookie发送给服务器。通过在Cookie中存储一些用户标识或其他数据,服务器可以在不使用session的情况下实现状态保持和用户认证。

最近,使用基于令牌(token)的身份验证方式逐渐流行起来,逐渐取代了传统的session和cookie方式。令牌是一种加密的字符串,它包含了与用户相关的信息,并可以通过在每个请求的头部或参数中携带令牌来实现身份验证。与session和cookie不同,令牌不需要在服务器端存储用户状态,这降低了服务器的存储压力,并且可以更好地支持分布式系统和跨域访问。

总结一下,传统的会话管理中使用了session和cookie来实现用户认证和状态保持。Session通过在服务器端记录信息,而cookie则是在客户端浏览器中存储会话标识符。然而,随着基于令牌的身份验证方式的兴起,令牌逐渐取代了session和cookie,提供了更灵活和可扩展的身份验证机制。令牌通过在请求中携带加密的字符串来验证用户身份,不需要在服务器端存储状态信息,从而提供了更好的性能和安全性。

Session的弊端包括以下几点:

  1. 服务器压力增大:通常情况下,Session是存储在服务器的内存中的,每个用户认证通过后,其Session数据都会保存在服务器的内存中。当用户数量增大时,服务器的内存压力也会增大。

  2. CSRF跨站伪造请求攻击:Session通常是基于Cookie进行用户识别的,如果Cookie被截获,用户容易受到跨站请求伪造(CSRF)攻击。即使不使用Cookie,而是通过URL重写方式发送SessionID,也容易被截获敏感信息。

  3. 扩展性不强:在多个服务器部署的情况下,如果用户登录到一台服务器A上,而下次访问另一台服务器B,B上并没有A的Session信息。为了解决这个问题,需要将Session保存到数据库中,这增加了服务器的存储压力。

相比之下,Token是一种令牌,由服务端生成的加密字符串,作为客户端请求的标识。当用户登录后,服务器生成一个Token并返回给客户端浏览器,以后客户端只需携带这个Token进行请求即可,无需再次携带用户名和密码。

浏览器将接收到的Token存储在本地存储(Local Storage)中。当浏览器再次访问服务器时,服务器对浏览器传来的Token进行解密,并进行用户数据查询。如果查询成功,则认证通过,实现状态保持。因此,即使有多台服务器,服务器只需解密Token和查询用户数据,而无需在服务器端保存用户的认证信息或会话信息。这就为应用的扩展提供了便利,同时也减少了服务器的存储压力。Token的出现解决了Session的弊端,成为Session的替代品。

JWT

JWT其实就是一种被广泛使用的token,它的全称是JSON Web Token,它通过数字签名的方式,以JSON对象为载体,在不同的服务终端之间安全地传输信息。

JWT最常见的使用场景就是授权认证,一旦用户登录,后续每个请求都将包含JWT,系统在每次处理用户请求之前,都要先进行JWT安全校验,通过之后再进行处理。

JWT由三部分组成,它们通过点号(.)进行分隔:

  1. Header(头部):包含了令牌的类型(即JWT)和所使用的签名算法(例如HMAC、RSA等)。

    示例:{"alg": "HS256", "typ": "JWT"}

  2. Payload(负载):包含了一些声明信息,用于在令牌中传递有关用户或其他实体的信息。声明分为三种类型:注册声明(Registered Claims)、公共声明(Public Claims)和私有声明(Private Claims)。注册声明是一组预定义的声明,例如iss(签发者)、exp(过期时间)等。公共声明是自定义的声明,但建议遵循一些已定义的命名规范。私有声明是用户自定义的声明,用于满足应用程序的特定需求。

    示例:{"sub": "user123", "name": "John Doe", "admin": true}

  3. Signature(签名):使用密钥对Header和Payload进行签名,以验证令牌的完整性和真实性。签名可以防止令牌被篡改或伪造。签名通常使用密钥和指定的签名算法进行计算。

    示例:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

JWT的工作流程如下:

  1. 用户通过提供有效的凭据(如用户名和密码)进行认证。

  2. 服务器验证凭据,并生成一个JWT。

  3. 服务器将JWT作为响应的一部分返回给客户端。

  4. 客户端将JWT保存在本地,通常存储在Local Storage或Session Storage中。

  5. 客户端在后续的请求中将JWT作为身份验证凭据进行发送。

  6. 服务器接收到请求后,通过验证JWT的签名和有效性来验证用户身份。

  7. 如果JWT验证成功,服务器使用其中的信息来处理请求。

JWT具有以下优点:

  1. 无需在服务器端存储会话信息:JWT是基于令牌的认证机制,服务器不需要在存储中保存会话信息,从而提高了可扩展性。

  2. 跨域支持:由于JWT是通过在请求中携带令牌进行验证,因此可以轻松支持跨域请求。

  3. 自包含性:JWT中包含了所有必要的信息,因此服务器不需要在数据库中进行查询,从而提高了性能。

  4. 可扩展性:JWT的负载中可以包含自定义的声明,以满足应用程序的特定需求。

3. springsecurity原理

Spring Security的核心原理是基于过滤器链(Filter Chain)的工作机制。当一个请求进入应用程序时,Spring Security会通过一系列的过滤器来处理该请求,进行认证和授权操作。

下面是Spring Security的一般工作流程:

  1. 客户端发送请求到应用程序,请求被Spring Security的过滤器链拦截。

  2. 过滤器链中的第一个过滤器是UsernamePasswordAuthenticationFilter,它负责处理基于用户名和密码的认证请求。如果请求携带了用户名和密码,该过滤器将尝试对用户进行认证。

  3. 认证过程中,Spring Security使用AuthenticationManager来进行实际的认证操作。AuthenticationManager是一个接口,用于管理不同的认证提供者(Authentication Provider)。

  4. 认证提供者根据请求中的用户名和密码,从用户存储(如数据库)中获取用户信息,并进行密码匹配验证。如果验证成功,认证提供者将返回一个Authentication对象,其中包含了用户的身份信息和权限信息。

  5. 如果认证成功,UsernamePasswordAuthenticationFilter会调用AuthenticationSuccessHandler来处理认证成功的逻辑,例如生成并返回一个令牌(如JWT),或者重定向到某个页面。

  6. 如果认证失败,UsernamePasswordAuthenticationFilter会调用AuthenticationFailureHandler来处理认证失败的逻辑,例如返回一个错误消息或重定向到登录页面。

  7. 认证成功后,请求继续经过过滤器链的其他过滤器,例如CsrfFilter用于防止跨站请求伪造攻击,CorsFilter用于处理跨域资源共享。

  8. 在请求到达受保护的资源时,Spring Security会使用AccessDecisionManager来进行授权判断。AccessDecisionManager是一个接口,用于根据用户的身份和权限信息,决定是否允许访问该资源。

  9. 如果用户被授权访问资源,请求将被传递到相应的控制器或处理器进行处理。

3. springsecurity 认证流程

在Spring Security中,认证流程涉及多个组件和步骤,主要包括以下几个关键步骤:

  1. 用户发送认证请求:用户在应用程序的登录页面输入用户名和密码,并发送认证请求。

  2. 过滤器链拦截请求:Spring Security的过滤器链拦截认证请求,拦截器链中的第一个过滤器是UsernamePasswordAuthenticationFilter,它负责处理基于用户名和密码的认证请求。

  3. AuthenticationManager处理认证:UsernamePasswordAuthenticationFilter将认证请求交给AuthenticationManager处理。AuthenticationManager是Spring Security的认证管理器,负责实际的认证操作。

  4. 获取用户信息:认证管理器使用UserDetailsService来获取用户信息。UserDetailsService是一个接口,定义了获取用户信息的方法。它可以从数据库、LDAP、内存或其他数据源中获取用户信息。

  5. 构建Authentication对象:认证管理器根据获取到的用户信息,构建一个Authentication对象,其中包含了用户的身份信息和权限信息。

  6. 进行密码匹配验证:认证管理器会使用PasswordEncoder接口来进行密码匹配验证。PasswordEncoder是一个接口,定义了密码加密和验证的方法。它用于比较用户输入的密码和存储的密码是否匹配。

  7. 认证成功:如果密码匹配验证成功,认证管理器将返回一个经过认证的Authentication对象。

  8. AuthenticationProvider处理认证结果:认证管理器会将认证结果传递给AuthenticationProvider进行进一步处理。AuthenticationProvider是一个接口,定义了对认证结果的处理方法。

  9. 调用认证成功处理器或认证失败处理器:根据认证结果,AuthenticationProvider将调用相应的认证成功处理器(AuthenticationSuccessHandler)或认证失败处理器(AuthenticationFailureHandler)。

  10. 认证成功处理:如果认证成功,认证成功处理器将根据配置的策略执行相应的操作,如生成并返回令牌(如JWT)或重定向到指定页面。

  11. 认证失败处理:如果认证失败,认证失败处理器将根据配置的策略执行相应的操作,如返回错误消息或重定向到登录页面。

4. springsecurity 自定义实现思路

当访问某个业务接口时,会被Security的login接口拦截,但是如果我们不想使用Security默认的登录页面,那么怎么办呢,还有,springsecurity的校验,默认是从内存中查找,而我们希望是根据数据库来做校验,那么怎么实现呢。根据上边的认证流程图可知,我们需要从不满足我们需求的地方去重新实现其方法,按照我们需求实现:

【登录】

①、自定义登录接口。然后自己去调用ProviderManager的方法进行认证 如果认证通过生成jwt,然后把用户信息存入redis中,从而实现无状态的会话管理和快速的用户信息访问。

②、自定义UserDetailsService接口的实现类。在这个实现类中自己重写相应方法,方法去查询数据库,来做校验

【校验】

①、定义Jwt认证过滤器。用于获取token,然后解析token获取其中的userid,还需要从redis中获取用户信息,然后存入SecurityContextHolder。SecurityContextHolder是Spring Security提供的一个类,用于管理当前用户的安全上下文信息。它主要用于在整个请求处理过程中存储和获取当前认证的用户信息