【深入理解SpringCloud微服务】Spring-Security-OAuth2实现SSO单点登录以及整合JWT
Spring-Security-OAuth2实现SSO单点登录以及整合JWT
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 );
}
更多推荐

所有评论(0)