参考文章:
OAuth2.0 实现单点登录_oauth2.0单点登录-CSDN博客
一、OAuth2.0 实现单点登录
我们发现就算实现了 Session 共享,依然存在一些问题,由于我们每个服务都有自己的验证模块,实际上整个系统是存在冗余功能的、同时还有我们上面出现的问题,那么能否实现只在一个服务进行登录,就可以访问其他的服务呢?
实际上之前的登录模式称为多点登录,而我们希望的是实现单点登陆,因此,我们得找一个更好的解决方案。
这里我们首先需要了解一种全新的登录方式:OAuth 2.0。我们经常看到一些网站支持第三方登录,比如淘宝、咸鱼我们就可以使用支付宝进行登录,腾讯游戏可以用QQ或是微信登陆,以及微信小程序都可以直接使用微信进行登录。我们知道它们并不是属于同一个系统,比如淘宝和咸鱼都不属于支付宝这个应用,但是由于需要获取支付宝的用户信息,这时我们就需要使用 OAuth2.0 来实现第三方授权,基于第三方应用访问用户信息的权限(本质上就是给别人调用自己服务接口的权限),那么它是如何实现的呢?
二、四种授权模式
我们还是从理论开始讲解,OAuth 2.0一共有四种授权模式:
客户端模式(Client Credentials)
这是最简单的一种模式,我们可以直接向验证服务器请求一个 Token(这里可能有些小伙伴对Token的概念不是很熟悉,Token 相当于是一个令牌,我们需要在验证服务器拿到令牌之后,才能去访问资源,比如用户信息、借阅信息等,这样资源服务器才能知道我们是谁以及是否成功登录了)
当然,这里的前端页面只是一个例子,它还可以是其他任何类型的客户端,比如 App、小程序甚至是第三方应用的服务。
虽然这种模式比较简便,但是已经失去了用户验证的意义,压根就不是给用户校验准备的,而是更适用于服务内部调用的场景。
密码模式(Resource Owner Password Credentials)
密码模式相比客户端模式,就多了用户名和密码的信息,用户需要提供对应账号的用户名和密码,才能获取到 Token。
虽然这样看起来比较合理,但是会直接将账号和密码泄露给客户端,需要后台完全信任客户端不会拿账号密码去干其他坏事,所以这也不是我们常见的。
隐式授权模式(Implicit Grant)
首先用户访问页面时,会重定向到认证服务器,接着认证服务器给用户一个认证页面,等待用户授权,用户填写信息完成授权后,认证服务器返回 Token。
授权码模式(Authrization Code)
这种模式是最安全的一种模式,也是推荐使用的一种,比如我们手机上的很多 App 都是使用的这种模式。
相比隐式授权模式,它并不会直接返回 Token,而是返回授权码,真正的 Token 是通过应用服务器访问验证服务器获得的。在一开始的时候,应用服务器(客户端通过访问自己的应用服务器来进而访问其他服务)和验证服务器之间会共享一个 secret,这个东西没有其他人知道,而验证服务器在用户验证完成之后,会返回一个授权码,应用服务器最后将授权码和 secret 一起交给验证服务器进行验证,并且 Token 也是在服务端之间传递,不会直接给到客户端。
这样就算有人中途窃取了授权码,也毫无意义,因为,Token 的获取必须同时携带授权码和 secret ,但是 secret 第三方是无法得知的,并且 Token 不会直接丢给客户端,大大减少了泄露的风险。
但是乍一看,OAuth 2.0 不应该是那种第三方应用为了请求我们的服务而使用的吗,而我们这里需要的只是实现同一个应用内部服务之间的认证,其实我也可以利用 OAuth2.0 来实现单点登录,只是少了资源服务器这一角色,客户端就是我们的整个系统,接下来就让我们来实现一下。
三、搭建验证服务器
相关代码:https://gitee.com/xiaocheng12/auth2-demo/tree/master/
第一步就是最重要的,我们需要搭建一个验证服务器,它是我们进行权限校验的核心,验证服务器有很多的第三方实现也有 Spring 官方提供的实现,这里我们使用 Spring 官方提供的验证服务器。
1、接着创建一个新的项目模块 auth-service
,添加依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- OAuth2.0依赖,不再内置了,所以得我们自己指定一下版本 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!-- 引入security给我们提供登录功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.0</version>
</dependency>
</dependencies>
2、设置 yml 配置文件
server:
port: 8500
servlet:
#为了防止一会在服务之间跳转导致Cookie打架(因为所有服务地址都是localhost,都会存JSESSIONID)
#这里修改一下context-path,这样保存的Cookie会使用指定的路径,就不会和其他服务打架了
#但是注意之后的请求都得在最前面加上这个路径
context-path: /sso
3、接着我们需要编写一个 SpringSecurity 的配置类
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll(); // 使用表单登录
}
}
4、再编写一个 OAuth2 的配置类 OAuth2Configuration.java,
配置客户端
- ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在 这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
- AuthorizationServerSecurityConfigurer:用来配置令牌端点的安全约束
- AuthorizationServerEndpointsConfigurer:用来配置令牌(token)的访问端点和令牌服务(token services)。
AuthorizationServerEndpointsConfigurer 这个对象的实例可以完成令牌服务以及令牌endpoint配置。
- authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,请设置 这个属性注入一个 AuthenticationManager 对象。
- userDetailsService:如果你设置了这个属性的话,那说明你有一个自己的 UserDetailsService 接口的实现, 或者你可以把这个东西设置到全局域上面去,当你设置了这个之后,那么 “refresh_token” 即刷新令牌授权类型模式的流程中就会包含一个检查,用来确保这个账号是否仍然有效,假如说你禁用了这个账户的话就授权失败。
@EnableAuthorizationServer //开启验证服务器
@Configuration
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
@Resource
private AuthenticationManager manager;
@Resource
PasswordEncoder passwordEncoder;
@Resource
UserServiceImpl userDetailsService;
/**
* 这个方法是对客户端进行配置,一个验证服务器可以预设很多个客户端,
* 之后这些指定的客户端就可以按照下面指定的方式进行验证
* @param clients 客户端配置工具
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
// 存储在缓存中,后期可以选择存在数据库中
.inMemory()
// 设置客户端的id和密码(但是注意要加密)
.w