谈一谈SSO框架

933 阅读7分钟

随着技术的发展,现在是微服务的天下,而且一个公司常常不止一个产品,有时候需要多产品的登录打通,所以统一的登录管理框架就至关重要。

公司的SSO框架年久失修,已经无法满足现在的业务发展。所以今天结合业务需求来探讨一下现在的登录框架是什么样的。

登录管理

用户登录之后,通过Cookie或者Session管理用户登录状态,在多个系统中可以获取到用户登录状态。

不过,微服务盛行,服务器肯定不止一台,而Session在多服务器间使用比较麻烦。要么使用一台服务器单独管理Session,要么使用Nginx反向代理,确保单个用户的所有请求都在一台服务器,或者使用Session同步。

所以现在比较简单的做法是使用CookieRedis

Cookie与Redis

不过使用Cookie又会涉及到另一个问题:安全问题。稍微懂点Web编程的人就知道查看Cookie的方法,如果不加密,那么是非常危险的事情。

加密解密的算法很多,因为公司使用的BlowFish加密,所以简单点,还是这个加密方式吧。

其实只是用Cookie也是可以的,所有信息都存储到Cookie中,不过这样做不是很优雅,Cookie太大,而且万一被破解,那么用户信息就会泄露。

所以一般Cookie中会存储RedisKey。从Cookie中解密后,再使用这个KeyRedis中获取用户信息。

单点登录

单点登录这个东西有的系统需要,有的系统不需要,因为有时候系统之前登录打通了,不会说用户登录了这个系统就要下线另一个系统。所以要是可配的,

单点登录的实现方式有很多,这里介绍一种简单的,使用UUID。这个UUID会存储到用户信息(Redis)和Cookie中,登录的时候,会把解密出来的UUID和从Redis中取到的用户信息中的UUID进行比对,因为每次登录都会创建新的UUID,而这个UUID会更新到当前的Cookie中,所以如果不一致,那肯定是进行了二次登录。

环境区分

一个系统,用户看到的是线上环境,其实还有日常和灰度环境,当然也有日常和灰度的登录,而这个是不能和线上的登录搞混的,不然测试登录了日常,却可以访问线上那就奇怪了。

我们公司的做法是使用Cookie的名称进行区分,根据环境的不同,Cookie的名称也不同。不过这是不安全的做法,因为Cookie的名称是可以更改的,而且这个也是没有加密的。所以要把当前环境存储到用户信息当中,在拦截器中也要检测环境信息,如果环境不匹配,那么就要强制下线用户。

多系统区分

很多时候登录一般是打通的,可是有时候某个系统比较特殊,它就是不希望打通,可是还想使用SSO框架里的一些便捷的工具,所以要预留一个字段来存储系统名称,如果配置了,那么登录就不是打通的,它的黑名单,还有Redis中用户的存储都是独立的。

请求拦截

既然是登录框架,那么请求拦截是少不了的。

比如哪些接口是免登的,哪些接口是必须登录后才能访问的,哪些接口未登录返回JSON信息,哪些接口未登录跳转到登录页面等等。有时候是默认全部拦截,配置不需要拦截的URL,有时候是全部不拦截,配置需要拦截的URL,这个看工作量和使用习惯了。

这个没什么好说的,使用拦截器就好了,重点是拦截URL的方式,可以使用正则表达式简化配置。

获取Domain名称

Domain名称和请求链接是否匹配决定了浏览器能不能携带Cookie,这个有两种方式,一种是通过配置文件配置,一种是从浏览器请求的URL中自动获取。

跨域配置

用户看到的是一个系统,其实背后可能调了很多系统的接口,而且现在前后端分离了,那么跨域是不可避免的,现在每个系统里都配置了允许跨域,其实这个东西可以配置在SSO框架中,这个就不用每个系统配置了。

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    /**
     * 跨域请求支持
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 设置允许跨域的路径
        registry.addMapping("/**").allowedOrigins("*")
                .allowedMethods("*").allowedHeaders("*")
                .allowCredentials(true)
                .exposedHeaders(HttpHeaders.SET_COOKIE)
                .maxAge(3600L);
    }

    @Bean
    public FilterRegistrationBean<Filter> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }
}

黑名单

很多时候用户可能没有体验过黑名单,其实SSO框架还是要配置这个方法的,可以把黑名单用户存储到Redis中,黑名单可以是IP黑名单和用户黑名单,如果区分多系统,可以在RedisKey中添加系统标识来进行区分,不会把用户全部都禁掉。

IP检测

做完上面这些还是不够的,安全问题至关重要,还有一个要做的就是IP检测,如果用户登录的IP和之前的IP不同,即IP发生了改变,那么很有可能是Cookie被盗取了,这时会强制下线用户。

过滤SQL/XSS注入攻击

现在这种攻击已经很少了。

验证码工具

主要是图片验证码,用来检测机器人。

Swagger配置

既然这个框架每个系统都要用,可以把一些公共的东西抽出来,然后统一配置,当然这个其实不应该是SSO框架该管的事情。

获取当前登录用户工具

SSO框架最爽的地方是什么,就是你可以随时随地获取当前的登录用户信息,在写后端代码的时候,有时候需要当前的登录用户,当然可以让前端把当前登录的用户id传过来,可是这是不安全的,最好一行代码就可以取到用户信息,这该多么快乐啊。

获取当前的request

public class HttpContextUtils {

    public static HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }

    public static HttpServletResponse getHttpServletResponse() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    }
}

其实也可以把request写到方法的参数里,然后让Spring注入进来,可是有点麻烦,获取到了request,那么就可以获取到Cookie,获取到Cookie,那么就可以取到Key,取到了Key就可以从Redis中获取到用户信息啦。

使用的时候直接这样,就可以取到当前登录的用户。

SsoUser user = SessionUtil.getUser();

最后

很多公司应该都有SSO框架,这里抱着开放的态度,主要是探讨一下,你觉得SSO框架还应该需要什么呢?欢迎评论。

技术有限,如有错误欢迎指正。

欢迎大家关注我的公众号,共同学习,一起进步。加油🤣