1、Spring Security介绍

认证功能几乎是每个项目都要具备的功能,并且它与业务无关,市面上有很多认证框架,如:Apache Shiro、CAS、Spring Security等。由于本项目基于Spring Cloud技术构建,Spring Security是spring家族的一份子且和Spring Cloud集成的很好,所以本项目选用Spring Security作为认证服务的技术框架。
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架,它是一个专注于为 Java 应用程序提供身份验证和授权的框架。

创建流程

1、创建空白项目
2、导入依赖
3、导入相关数据库资源
4、编写相关接口
image.png

工作原理

SpringSecurity 所解决的时安全访问控制,所谓控制无非就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它锁期望的资源。Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。
当初始化Spring Security时,会创建一个名为SpringSecurityFilterChain的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过虑器链结构图:image.png
FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理。

FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器(AccessDecisionManager)进行处理。
image.png
SecurityContextPersistenceFilter这个Filter是整个拦截过程的入口和出口(也就是第一个和最后一个拦截器),会在请求开始时从配置好的 SecurityContextRepository 中获取 SecurityContext,然后把它设置给 SecurityContextHolder。在请求完成后将 SecurityContextHolder 持有的 SecurityContext 再保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext;
UsernamePasswordAuthenticationFilter用于处理来自表单提交的认证。该表单必须提供对应的用户名和密码,其内部还有登录成功或失败后进行处理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,这些都可以根据需求做相关改变;
FilterSecurityInterceptor是用于保护web资源的,使用AccessDecisionManager对当前用户进行授权访问,前面已经详细介绍过了;
ExceptionTranslationFilter能够捕获来自 FilterChain 所有的异常,并进行处理。但是它只会处理两类异常:AuthenticationException 和 AccessDeniedException,其它的异常它会继续抛出。

Spring Security的执行流程如下:
image.png

  1. 提交信息
  2. 信息封装:用户提交用户名、密码被SecurityFilterChain中的UsernamePasswordAuthenticationFilter过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
  3. 认证信息:封装的信息提交给Manager进行认证
  4. 委托认证:由于数据需要从数据库中查,因此需要委托给Dao层认证
  5. 查询信息
  6. 返回信息UserDetails
  7. 密码校验
  8. 填充信息:SecurityContextHolder.getContext().setAuthentication(…)方法
  9. 返回Authentication
  10. 保存上下文:认证成功后,AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。

    可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个List列表,存放多种认证方式,最终实际的认证工作是由AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终AuthenticationProvider将UserDetails填充至Authentication。

OAuth2

OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时,任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP、JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间,因而OAUTH是简易的。

案例

image.png
1、用户点击微信扫码
用户进入黑马程序的登录页面,点击微信的图标开打微信扫码界面。
image.png
微信扫码的目的是通过微信认证登录黑马程序员官网,黑马程序员网站需要从微信获取当前用户的身份信息才会让当前用户在黑马网站登录成功。
现在搞清楚几个概念:

  • 资源:用户信息,在微信中存储。
  • 资源拥有者:用户是用户信息资源的拥有者。
  • 认证服务:微信负责认证当前用户的身份,负责为客户端颁发令牌。
  • 客户端:客户端会携带令牌请求微信获取用户信息,黑马程序员网站即客户端,黑马网站需要在浏览器打开。

2、用户授权黑马网站访问用户信息
资源拥有者扫描二维码表示资源拥有者请求微信进行认证,微信认证通过向用户手机返回授权页面,如下图
image.png
询问用户是否授权黑马程序员访问自己在微信的用户信息,用户点击“确认登录”表示同意授权,微信认证服务器会颁发一个授权码给黑马程序员的网站。
只有资源拥有者同意微信才允许黑马网站访问资源。
3、黑马程序员的网站获取到授权码
4、携带授权码请求微信认证服务器申请令牌
此交互过程用户看不到。
5、微信认证服务器向黑马程序员的网站响应令牌
此交互过程用户看不到。
6、黑马程序员网站请求微信资源服务器获取资源即用户信息。
黑马程序员网站携带令牌请求访问微信服务器获取用户的基本信息。
7、资源服务器返回受保护资源即用户信息
8、黑马网站接收到用户信息,此时用户在黑马网站登录成功。

授权流程

OAuth在”第三方应用”与”服务提供商”之间,设置了一个授权层。“第三方应用”不能直接登录”服务提供商”,只能登录授权层,以此将用户与客户端区分开来。”第三方应用”登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。”第三方应用”登录授权层以后,”服务提供商”根据令牌的权限范围和有效期,向”第三方应用”开放用户储存的资料。

image.png :::tips

  1. 用户打第三方开客户端后,第三方客户端要访问服务提供方,要求用户给予授权;
  2. 用户同意给予第三方客户端访问服务提供方的授权,并返回一个授权凭证Code;
  3. 第三方应用使用第2步获取的授权凭证Code和身份认证信息(appid、appsecret),向授权认证服务器申请授权令牌(token);
  4. 授权认证服务器验证三方客户端的授权凭证Code码和身份通过后,确认无误,同意授权,并返回一个资源访问的令牌(Access Token);
  5. 第三方客户端使用第4步获取的访问令牌Access Token)向资源服务器请求相关资源;
  6. 资源服务器验证访问令牌(Access Token)通过后,将第三方客户端请求的资源返回,同意向客户端开放资源; :::

授权模式

授权码模式(authorization code) 简化模式(implicit) 密码模式(resource owner passwordcredentials) 客户端模式(client credentials)

1、授权码模式

第三方应用先申请获取一个授权码,然后再使用该授权码获取令牌,最后使用令牌获取资源;授权码模式是工能最完整、流程最严密的授权模式。它的特点是通过客户端的后台服务器,与服务提供商的认证服务器进行交互。
image.png
A [步骤1,2]:用户访问客户端,需要使用服务提供商(微信)的数据,触发客户端相关事件后,客户端拉起或重定向到服务提供商的页面或APP。
B [步骤3]:用户选择是否给予第三方客户端授权访问服务提供商(微信)数据的权限;
C [步骤4]:用户同意授权,授权认证服务器将授权凭证code码返回给客户端,并且会拉起应用或重定(redirect_uri)向到第三方网站;
D [步骤5,6]:客户端收到授权码后,将授权码code和ClientId或重定向URI发送给自己的服务器,客户端服务器再想认证服务器请求访问令牌access_token;
E [步骤7,8]:认证服务器核对了授权码和ClientId或重定向URI,确认无误后,向客户端服务器发送访问令牌(access_token)和更新令牌(refresh_token),然后客户端服务器再发送给客户端;
F [步骤9,10]:客户端持有access_token和需要请求的参数向客户端服务器发起资源请求,然后客户端服务器再向服务提供商的资源服务器请求资源(web API);
G [步骤11,12,13]:服务提供商的资源服务器返回数据给客户端服务器,然后再回传给客户端使用

测试

httpclient脚本如下: :::tips

授权码模式
### 第一步申请授权码(浏览器请求)/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.51xuecheng.cn
### 第二步申请令牌
POST {{auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=authorization_code&code=CTvCrB&redirect_uri=http://www.51xuecheng.cn

::: • client_id:客户端准入标识。
• client_secret:客户端秘钥。
• grant_type:授权类型,填写authorization_code,表示授权码模式
• code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。
redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致

申请令牌成功如下所示:

  1. {
  2. "access_token": "368b1ee7-a9ee-4e9a-aae6-0fcab243aad2",
  3. "token_type": "bearer",
  4. "refresh_token": "3d56e139-0ee6-4ace-8cbe-1311dfaa991f",
  5. "expires_in": 7199,
  6. "scope": "all"
  7. }

1、access_token,访问令牌,用于访问资源使用。
2、token_type,bearer是在RFC6750中定义的一种token类型,在携带令牌访问资源时需要在head中加入bearer 空格令牌内容
3、refresh_token,当令牌快过期时使用刷新令牌可以再次生成令牌。
4、expires_in:过期时间(秒)
5、scope,令牌的权限范围,服务端可以根据令牌的权限范围去对令牌授权。

2、密码模式

如果你高度信任某个应用,允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为”密码模式” :::tips 使用用户名/密码作为授权方式从授权服务器上获取令牌,一般不支持刷新令牌。这种方式风险很大,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向”服务商提供商”索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。 ::: image.png

1、资源拥有者提供账号和密码 2、客户端向认证服务申请令牌,请求中携带账号和密码 3、认证服务校验账号和密码正确颁发令牌。

开始测试:

1、POST请求获取令牌 :::tips /oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=shangsan&password=123 ::: 参数列表如下:
• client_id:客户端准入标识。
• client_secret:客户端秘钥。
• grant_type:授权类型,填写password表示密码模式
• username:资源拥有者用户名。
• password:资源拥有者密码。

2、授权服务器将令牌(access_token)发送给client
使用httpclient进行测试

POST {{auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=zhangsan&password=123

返回示例:

{
“access_token”: “368b1ee7-a9ee-4e9a-aae6-0fcab243aad2”,
“token_type”: “bearer”,
“refresh_token”: “3d56e139-0ee6-4ace-8cbe-1311dfaa991f”,
“expires_in”: 6806,
“scope”: “all”
}

这种模式十分简单,但是却意味着直接将用户敏感信息泄漏给了client,因此这就说明这种模式只能用于client是我们自己开发的情况下。

本项目的应用方式

通过演示授权码模式和密码模式,授权码模式适合客户端和认证服务非同一个系统的情况,所以本项目使用授权码模式完成微信扫码认证。本项目采用密码模式作为前端请求微服务的认证方式。

JWT

简介

JSON Web Token(JWT)是一种使用JSON格式传递数据的网络令牌技术,它是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任,它可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止内容篡改。官网:https://jwt.io/
使用JWT可以实现无状态认证,什么是无状态认证?
传统的基于session的方式是有状态认证,用户登录成功将用户的身份信息存储在服务端,这样加大了服务端的存储压力,并且这种方式不适合在分布式系统中应用。
如下图,当用户访问应用服务,每个应用服务都会去服务器查看session信息,如果session中没有该用户则说明用户没有登录,此时就会重新认证,而解决这个问题的方法是Session复制、Session黏贴。
image.png
如果是基于令牌技术在分布式系统中实现认证则服务端不用存储session,可以将用户身份信息存储在令牌中,用户认证通过后认证服务颁发令牌给用户,用户将令牌存储在客户端,去访问应用服务时携带令牌去访问,服务端从jwt解析出用户信息。这个过程就是无状态认证。
image.png

特点

优点

1、jwt基于json,非常方便解析。
2、可以在令牌中自定义丰富的内容,易扩展。
3、通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
4、资源服务使用JWT可不依赖认证服务即可完成授权。

缺点

1、JWT令牌较长,占存储空间比较大

构成

  1. Header

头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
一个例子如下:
下边是Header部分的内容

{
“alg”: “HS256”,
“typ”: “JWT”
}

将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。

  1. Payload

第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的信息字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
一个例子:

JSON
{
“sub”: “1234567890”,
“name”: “456”,
“admin”: true
}
  1. Signature
    第三部分是签名,此部分用于防止jwt内容被篡改。
    这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明的签名算法进行签名。
    一个例子:
JSON
HMACSHA256(
base64UrlEncode(header) + “.” +
base64UrlEncode(payload),
secret)

base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:签名所使用的密钥。
为什么JWT可以防止篡改?
第三部分使用签名算法对第一部分和第二部分的内容进行签名,常用的签名算法是 HS256,常见的还有md5,sha 等,签名算法需要使用密钥进行签名,密钥不对外公开,并且签名是不可逆的,如果第三方更改了内容那么服务器验证签名就会失败,要想保证验证签名正确必须保证内容、密钥与签名前一致。

测试

第5章 认证授权v3.1.docx