Spring-Security-OAuth2实现SSO单点登录

SSO单点登录

单点登录简称sso,作用是在大型的分布式或微服务架构下,有许多不同的应用,而用户只需要通过sso登录一次,就可以访问这里面的所有应用。

常见实现方式

常见的单点登录的实现方式有【同域单点登录】、【跨域单点登录】、【基于OAuth2的跨域单点登录】三种。

同域单点登录

在这里插入图片描述

同域单点登录使用在所有服务的一级域名都相同的场景,不同服务通过二级域名作区分。由于所有子系统的一级域名都相同,因此只要设置cookie的domain属性为对应的一级域名,这样所有的子系统都能共享这个cookie。

通过cookie存储Session ID,搭配Spring Session,就可以实现不同系统间的Session共享。

跨域单点登录

但是如果不同服务间的一级域名不一样,就无法共享cookie。此时就需要一个单独的授权服务(UAA)来统一处理登录请求,并通过共享UAA的cookie实现自动登录。

在这里插入图片描述

基于OAuth2的跨域单点登录

基于OAuth2的跨域单点登录是跨域单点登录的升级版,在跨域单点登录方案的基础上增加了OAuth2协议的实现。

在这里插入图片描述

实现SSO单点登录

通过Spring-Security-OAuth2实现SSO单点登录。

首先客户端需要引入Maven依赖spring‐cloud‐starter‐oauth2;然后在application.yml文件中配置授权服务器相关接口的地址以及client id和client secret;最后通过@EnableOAuth2Sso注解启用单点登录功能。

而服务端则要进行授权服务器的配置,需要继承AuthorizationServerConfig类并重写它的configure方法,进行授权码模式的相关配置;通过@EnableAuthorizationServer注解启用授权服务器。

然后客户端添加一个这样的接口

@RestController
@RequestMapping("/user")
public class UserController {

	@RequestMapping("/getCurrentUser")
	public Object getCurrentUser(Authentication authentication) {
		return authentication;
	}
}

此时浏览器访问该接口时,就会跳转到授权登录界面进行授权登录,登录成功后会跳转会/user/getCurrentUser接口返回用户信息。

而其他业务系统也配置了相同的授权服务器,并且也有一个相同的/user/getCurrentUser接口,当浏览器访问该系统时,就无需重复登录,直接获取到用户信息。

整合网关实现SSO单点登录

一般我们会把token校验和解析的工作放到网关。

在这里插入图片描述

此时网关会拦截每一个请求(登录请求除外),然后检查请求是否携带token,如果没有携带token,则会抛异常,然后浏览器会重定向到授权服务器进行登录;如果携带了token,网关会请求授权服务器校验token,校验通过后会把明文token信息放入请求头,然后把请求转发给后端微服务。

后端微服务收到请求后从头部中获取明文token并解析,然后校验用户权限信息。

实现上述功能,只需要网关添加一个自定义的全局过滤器即可。

在这里插入图片描述

Spring-Security-OAuth2整合JWT

但是上述方案还有一个问题,就是每次网关接收到请求之后,都要请求授权服务器校验token,这就等于每次都会多出一次网络IO的耗时。

在这里插入图片描述

在高并发环境下,这些请求授权服务器校验token的网络IO叠加起来也是一笔不小的性能开销。因此我们要想办法省掉那一次请求授权服务器校验token的网络IO开销,于是我们会引入JWT。

JWT

JWT不需要请求颁发token的授权服务进行解析,每个接收到token的服务,都可以自行解析并验证token。JWT通过非对称加密和数字签名技术,可以防止token被篡改。

JWT有三部分组成:头部(Header)、载荷(Payload)、签名(Signature)。

在这里插入图片描述

Header包含了加密算法和令牌类型等JWT元数据信息(Json格式),然后通过base64进行编码。

Payload包含了实际的有效信息比如用户名(Json格式),然后也是通过base64进行编码。

Signature部分比较复杂,首先是将前面的两个base64字符串用“.”进行拼接,然后再通过指定的加密算法加上secret对拼接后的字符串进行加密,得到一个数字签名。secret可以是对称密钥,也可以是非对称加密中的私钥。

在这里插入图片描述

整合JWT

如果授权服务器使用了私钥加密生成的签名,网关取得token后,就需要通过公钥进行解密。能解密成功代表token正确有效,否则该token就是无效的。

在这里插入图片描述

此时Gateway在启动的时候,就需要请求授权服务器获取公钥。

在这里插入图片描述

授权服务器需要引入Maven依赖spring‐security‐jwt来配置授权服务器的令牌存储策略为JWT,而网关则需要引入Maven依赖jjwt来对token进行解析,具体配置就不详细描述。

此时我们修改我们在Gateway中的自定义过滤器AuthenticationFilter的filter方法,此时的filter方法处理步骤如下:

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
	//1.过滤不需要认证的url,比如/oauth/**
	if(shouldSkip(currentUrl)) {
		return chain.filter(exchange);
	}
	
	//2. 获取token
	// 从请求头中解析 Authorization value: bearer xxxxxxx
	// 或者从请求参数中解析 access_token
	String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
	if(StringUtils.isEmpty(authHeader)) {		
		throw new GateWayException(ResultCode.AUTHORIZATION_HEADER_IS_EMPTY);
	}
	
	//3. 校验token
	// 拿到token后,通过公钥(需要从授权服务获取公钥)校验
	// 校验失败或超时抛出异常
	Claims claims = JwtUtils.validateJwtToken(authHeader,publicKey);
	
	//4. 校验通过后,从token中获取的用户登录信息存储到请求头中
	ServerWebExchange webExchange = wrapHeader(exchange,claims);
	
	return chain.filter(webExchange );
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐