Spring-Security-OAuth2实现SSO单点登录以及整合JWTSpring-Security-OAuth2实现SSO单点登录SSO单点登录常见实现方式同域单点登录跨域单点登录基于OAuth2的跨域单点登录实现SSO单点登录整合网关实现SSO单点登录Spring-Security-OAuth2整合JWTJWT整合JWTSpring-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注解启用授权服务器。然后客户端添加一个这样的接口RestControllerRequestMapping(/user)publicclassUserController{RequestMapping(/getCurrentUser)publicObjectgetCurrentUser(Authenticationauthentication){returnauthentication;}}此时浏览器访问该接口时就会跳转到授权登录界面进行授权登录登录成功后会跳转会/user/getCurrentUser接口返回用户信息。而其他业务系统也配置了相同的授权服务器并且也有一个相同的/user/getCurrentUser接口当浏览器访问该系统时就无需重复登录直接获取到用户信息。整合网关实现SSO单点登录一般我们会把token校验和解析的工作放到网关。此时网关会拦截每一个请求登录请求除外然后检查请求是否携带token如果没有携带token则会抛异常然后浏览器会重定向到授权服务器进行登录如果携带了token网关会请求授权服务器校验token校验通过后会把明文token信息放入请求头然后把请求转发给后端微服务。后端微服务收到请求后从头部中获取明文token并解析然后校验用户权限信息。实现上述功能只需要网关添加一个自定义的全局过滤器即可。Spring-Security-OAuth2整合JWT但是上述方案还有一个问题就是每次网关接收到请求之后都要请求授权服务器校验token这就等于每次都会多出一次网络IO的耗时。在高并发环境下这些请求授权服务器校验token的网络IO叠加起来也是一笔不小的性能开销。因此我们要想办法省掉那一次请求授权服务器校验token的网络IO开销于是我们会引入JWT。JWTJWT不需要请求颁发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方法处理步骤如下OverridepublicMonoVoidfilter(ServerWebExchangeexchange,GatewayFilterChainchain){//1.过滤不需要认证的url,比如/oauth/**if(shouldSkip(currentUrl)){returnchain.filter(exchange);}//2. 获取token// 从请求头中解析 Authorization value: bearer xxxxxxx// 或者从请求参数中解析 access_tokenStringauthHeaderexchange.getRequest().getHeaders().getFirst(Authorization);if(StringUtils.isEmpty(authHeader)){thrownewGateWayException(ResultCode.AUTHORIZATION_HEADER_IS_EMPTY);}//3. 校验token// 拿到token后通过公钥需要从授权服务获取公钥校验// 校验失败或超时抛出异常ClaimsclaimsJwtUtils.validateJwtToken(authHeader,publicKey);//4. 校验通过后从token中获取的用户登录信息存储到请求头中ServerWebExchangewebExchangewrapHeader(exchange,claims);returnchain.filter(webExchange);}