链接:https://pan.baidu.com/s/1pG9Puiak88oYeTAtrg5AuA
提取码:embi
OAuth2 是什么
OAuth 2.0 是目前最流行的授权机制,用来授权第三方应用,获取用户数据。
OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的规范标准。与以往的授权方式不同之处是
OAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 OAuth 是开放的安全的。
业界提供了 OAuth 的多种实现,如 Java、PHP、Ruby 等各种语言开发包,大大节约了程序员的时间,因而OAuth是简易的。很多大公司如 阿里、腾讯、 Google,Yahoo,Microsoft等都提供了 OAuth 认证服务,这些都足以说明 OAuth 标准逐渐成为开放资源授权的标准。
OAuth 协议1.0版本过于复杂,目前发展到2.0版本,2.0版本已得到广泛应用。
OAuth 2.0参考:https://oauth.net/2/
中文版参考:https://github.com/jeansfish/RFC6749.zh-cn/blob/master/SUMMARY.md
OAuth2 要解决的问题
比如:进入某网站时,不想注册帐号,而是希望通过微信进行登录。
如果微信用户同意提供帐号和密码去获取微信信息,进行登录网站,这样存在很大的安全问题:
- 提供账号和密码给第三方应用程序后,应用可以访问用户在微信上的所有数据(有什么群,好友)。
- 用户只有修改密码后,才可以收回权限。但是如果一样将用户名和密码授权给了其他第三方应用,当你修改密码后,那么所有第三方应用程序都会被收回权限。
- 密码泄露的可能性大大提高。因为无法控制接入应用对用户数据保护的安全系数,只要有一个接入的第三方应用遭到破解,那么用户的密码就会泄露,意味着用户的信息全部会被泄露,后果不堪设想。
OAuth 就是为了解决上面这些问题而诞生的。OAuth 协议规范了授权方式,不采用授权帐号和密码的方式,而是使用令牌方式(Token)来解决授权问题,应用通过令牌去获取被授权的用户信息,从而可以避免上面存在的安全问题。
OAuth2 涉及的角色
-
资源所有者(Resource Owner): 通常为 "用户"(user),如昵称、头像等这些资源的拥有者(用户只是
将这些资源放到了服务提供商的资源服务器中)。 -
第三方应用(Third-party application): 又称为客户端(Client),比如 梦学谷官网想要使用微信的资源
(昵称、头像等),梦学谷官网对于QQ、微信等系统来说是第三者,我们称梦学谷官网为第三方应用。 - **认证服务器(Authorization server): **专门用来对资源所有者的身份进行认证、对要访问的资源进行授
权、产生令牌的服务器。想访问资源,需要通过认证服务器由资源所有者授权后才可访问。 -
资源服务器(Resource server): 存储用户的资源(昵称、头像等)、验证令牌有效性。比如: 微信的资源
服务器存储了微信的用户信息,淘宝的资源服务器存储了淘宝的用户信息等。
注意:认证服务器 和资源服务器 虽然是两个解决,但其实他们可以是同一台服务器、同一个应用。 - 服务提供商(Service Provider): 如 QQ、微信等 (包含认证和资源服务器)。
OAuth2 认证流程
OAuth 在 "第三方应用" 与 "服务提供商" 之间,设置了一个授权层(authorization layer)。"第三方应用" 不能直接登录 "服务提供商",只能通过授权层将 "第三方应用" 与用户区分开来。"第三方应用" 通过授权层获取令牌(token), 获取令牌后拿令牌去访问服务提供商。令牌和用户的密码不同,可以指定授权层令牌的权限范围和有效期,"服务提供商" 根据令牌的权限范围和有效期,向 "第三方应用" 开放用户对应的资源。
OAuth2 协议的授权模式
- 授权码模式(Authorization Code):功能最完整,流程最严密的授权模式。国内各大服务提供商(微信、QQ、微博、淘宝、百度)都采用此模式进行授权。可以确定是用户真正同意授权;而且令牌是认证服务器发放给第三方应用的服务器,而不是浏览器上。
- 简化模式(Implicit):令牌是发放给浏览器的,oauth客户端运行在浏览器中,通过JS脚本去申请令牌。而不是发放给第三方应用的服务器。
- 密码模式(Resource Owner Password Credentials):将用户名和密码传过去,直接获取 access_token 。用户同意授权动作是在第三方应用上完成,而不是在认证服务器上。第三方应用申请令牌时,直接带着用户名密码去向认证服务器申请令牌。这种方式认证服务器无法断定用户是否真的授权了,用户名密码可能是第三方应用盗取来的。
- 客户端证书模式(Client credentials):用得少。当一个第三应用自己本身需要获取资源(而不是以用户的名义),而不是获取用户的资源时,客户端模式十分有用。
授权码模式流程
简化模式流程
密码模式流程
- 用户向客户端直接提供认证服务器平台的用户名和密码。
- 客户端将用户名和密码发给认证服务器,向后者请求令牌。
- 认证服务器确认无误后,向客户端提供访问令牌。
客户端模式流程
- 客户端向认证服务器进行身份认证,并要求一个访问令牌。
-
认证服务器确认无误后,向客户端提供访问令牌。
Spring Security OAuth2 认证服务器
概述
Spring Seucrity 登录信息存在Session中,每次访问服务时,会去查看浏览器中的 Cookie 中是否存在JSESSIONID,如果不存在会新建一个Session, 将新建的SessionID 保存到Cookie 中。每次发请求会通过浏览器的SessionID查找到对应的Session对象,从而获取用户信息。
前后端分离,前端部署在单独Web服务器,后台部署在另外一台应用服务器,浏览器先访问Web服务器,Web服务器再发送请求到应用服务器中。这样采用Cookie存储就不太合适,原因:
- 开发复杂
- 安全性差
- 客户体验差
- 有些前端技术 不支持Cookie, 如:小程序
解决:
采用令牌方式进行认证就可以上面的问题,就可以使用 OAuth2 协议标准中的实现 Spring Security OAuth2 解决。
添加依赖
<!-- Spring Security、OAuth2 和JWT等 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
配置 application.yml
注意:配置了项目的上下文路径 /auth ,请求时要加上,即 http://localhost:8090/auth 作为请求前缀
server
port: 8090
servlet:
context-path: /auth # 上下文路径,请求时要加上,后面网关时有用
配置认证服务器-授权码模式
创建认证服务器配置类
作用:
- 配置被允许访问此认证服务器的客户端信息, 没有在此配置的客户端是不允许访问的
- 管理令牌:
- 配置令牌管理策略(如:JDBC/ Redis/JWT)
- 配置令牌生成策略
- 配置令牌端点
- 令牌端点的安全配置
步骤:
- 创建 AuthorizationServerConfig 类继承AuthorizationServerConfigurerAdapter
- 在类上添加注解:
@Configuration 标识配置类
@EnableAuthorizationServer 开启 OAuth2 认证服务器功能, - 配置说明:
withClient:允许访问此认证服务器的客户端id , 如:PC、APP、小程序各不同的的客户端id。
secret:客户端密码,要加密存储,不然获取不到令牌一直要求登录,, 而且一定不能被泄露。
authorizedGrantTypes: 授权类型, 可同时支持多种授权类型:
可配置:"authorization_code", "password", "implicit","client_credentials","refresh_token"
scopes:授权范围标识,如指定微服务名称,则只能访问指定的微服务。
autoApprove:false 跳转到授权页面手动点击授权,true 不用手动授权,直接响应授权码。
@Configuration
@EnableAuthorizationServer // 开启了认证服务器
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private DataSource dataSource;
@Bean
public ClientDetailsService jdbcClientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
/**
* 配置被允许访问此认证服务器的客户端信息
* 1.内存方式
* 2.数据库方式
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 内存方式管理客户端信息
clients.inMemory()
.withClient("mengxuegu-pc") // 客户端id
.secret(passwordEncoder.encode("mengxuegu-secret")) // 客户端密码(加密)
.resourceIds("product-server") // 资源id,针对的是微服务名称(如商品管理)。没有配置默认每个微服务都能访问
/**
* 授权码模式(Authorization Code):【常用】
* 简化模式(Implicit): 【无后台应用】
* 密码模式(Resource Owner Password Credentials):【公司内部搭建认证服务器】
* 客户端证书模式(Client credentials):【微服务之间调用】
* 刷新临牌【过期自动重新刷新获取】
*/
.authorizedGrantTypes("authorization_code", "password", "implicit", "client_credentials", "refresh_token")//指定多种类型,客户端就能通过多种类型访问
.scopes("all") // 授权范围标识,哪部分资源可访问(all只是标识,不是说所有资源)【具体资源如修改/查看】
.autoApprove(false) // false 跳到一个授权页面手动点击授权,true不需要手动点授权,直接响应一个授权码
.redirectUris("http://www.mengxuegu.com/")// 客户端回调地址【认证服务器响应授权码后,客户端将授权码附着在这个回调地址后面,进行请求 申请令牌】
.accessTokenValiditySeconds(60*60*8) //访问令牌有效时长 默认为12小时
.refreshTokenValiditySeconds(60*60*24*60) // 刷新令牌有效时长,默认是30天
;
}
创建安全配置类
概要
指定要进行认证用户的用户名和密码,这个用户名和密码是资源所有者的。
和上面指定的客户端id和密码是不一样的,客户端的id和密码是应用系统的标识,每个应用系统就对应一个客户端ip和密码。
而这里指定的用户名和密码是客户的,就是每个应用系统的用户,即资源所有者。
步骤
- 创建SpringSecurityConfig 类继承WebSecurityConfigurerAdapter
- 在类上添加注解:@EnableWebSecurity , 它包含了 @Configuration 注解所以不用加
/**
* 安全配置类
* springSecurity的身份认证
*/
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsService customUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//比如简单的内存认证 账号密码
// auth.inMemoryAuthentication()
// .withUser("admin").password(passwordEncoder.encode("1234"))
// .authorities("product");
//不使用内存方式,就自定义CustomUserDetailsService【数据库查对应用户名密码匹配验证】,然后这里作为参数传入认证。
auth.userDetailsService(customUserDetailsService);
}
}
运行认证服务器
令牌访问端点
Spring Security 对 OAuth2 默认提供了可直接访问端点,即URL:
- /oauth/authorize :申请授权码 code, 涉及的类 AuthorizationEndpoint
- /oauth/token :/获取令牌 token, 涉及的类 TokenEndpoint
- /oauth/check_token :用于资源服务器请求端点来检查令牌是否有效, 涉及的类 CheckTokenEndpoint
- /oauth/confirm_access :用户确认授权提交, 涉及的类 WhitelabelApprovalEndpoint
- /oauth/error :授权服务错误信息, 涉及的类 WhitelabelErrorEndpoint
- /oauth/token_key :提供公有密匙的端点,使用 JWT 令牌时会使用 , 涉及的类 TokenKeyEndpoint
发送请求获取授权码
- 请求如下地址申请授权码(注意:路径加上下文路径 /auth )
http://localhost:8090/auth/oauth/authorize?client_id=mengxuegu-pc&response_type=code
- 当请求到达授权中心的
AuthorizationEndpoint
后,授权中心将会要求资源所有者做身份验证
注意:
- 此处输入的用户名、密码是在认证服务器输入的(看端口8090),而不是在客户端上输入的,这样更加
安全,因为客户端不知道用户的用户名和密码。 -
而密码模式中,输入的用户名、密码不是在认证服务器(不是8090端口)上输入的,而是在客户端(第
三方应用)输入的,这样客户端知识了就不太安全。
-
输入用户名密码后,登录后会重新跳转授权页面,询问资源所有者:是否将受保护的资源授权给 mengxuegu�pc 客户端:
-
选择 Approve 后,点击 authorize 同意授权 scope.all 资源后,会跳转到指定的 redirect_uri ,回调路径携
带了带着一个授权码( code=V3ENnC ) ,如下图:
获取到授权码(code) 后,就可以通过它来获取访问令牌(access_token)。
通过授权码获取令牌 token
POST 方式请求:http://localhost:8090/auth/oauth/token
-
请求头: Authorization: Basic bWVuZ3h1ZWd1LXBjOm1lbmd4dWVndS1zZWNyZXQ=
是将 client_id:client_secret 通过 Base64 编码
-
post 方式,请求体中指定 授权方式 和 授权码
-
每个授权码申请令牌后就会失效了,需要重新发送请求获取授权码再去认证, 如下再请求就失败了