Spring Security & Session实现企业级用户登录及权限控制
1.需求背景
OCP与ODC部署到内部与外部需要具有成熟的用户管理系统,内部需要接入buc用户体系,外部输出需要对接金融云和阿里云三方登录解决方案。原有的OCP1.0系统中用户管理模块是自己实现的,用户信息从Browser传递到Server过程中明文暴露,Cookie或Session泄露容易发生CSRF。1.0系统中权限管理比较混乱,一个角色多达十几个权限,权限系统由前端来控制,不能实现API级别的精确权限控制。
Spring Framework4.x开始提供对Spring Securiy支持,这是一个基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。
Spring Session是分布式Session的解决方案,在单点式应用中,Session是由tomcat管理的,存在于tomcat的内存中,当我们为了解决分布式场景中的Session共享问题时,引入了redis,其共享内存,以及支持key自动过期的特性,非常契合Session的特性,我们在企业开发中最常用的也就是这种模式。Spring Session通过AOP的方式,将HttpSession持久化到Redis或MySql中,实现分布式部署时对Session的共享。
本文解决的目标:
- Spring Security对接Buc、金融云三方登录
- Spring Security实现对已登录的用户进行权限管理
- Spring Session共享Session,解决分布式部署“单点”登录
2.方案流程
刚开始接触Spring Security时新的概念特别多,容易找不到着手点。简单来讲,Spring Security提供了一系列的规范接口,需要按要求在这些接口下面实现自己的业务逻辑,Spring Security就会用户的登录及权限动作进行管理。
核心接口
UserDetails:Spring Security用户信息接口
UserDetailsService:其中loadUserByUsername方法用户通过username来从内存或DB中查询出用户信息,在这个方法中可以自定义权限信息
OncePerRequestFilter:过滤登录请求,确保只能通过一次,三方登录时可以在此写入三方登录逻辑
AuthenticationProvider:验证用户登录是否合法
WebSecurityConfigurerAdapter:配置类
Http请求的调用路径如下:
Http -> OncePerRequestFilter -> UserDetailsService -> AuthenticationProvider -> success -> Controller -> Browser
Spring Security Auth项目可以实现标准的Auth2.0协议的登录,支持Github/FaceBook,仅需在application.properties中写入配置即可,使用起来更为简便。但是Buc和金融云三方登录均未实现标准的Auth2.0协议,故这种方案并不适用。
Spring Session支持持久化到Redis或者MySql。Spring Boot工程由于具有自动装配的特性,使用起来极为简单。仅需开启@EnableRedisHttpSession或者@EnableJdbcHttpSession注解即可。Redis支持任意对象的持久化,所以只要配置好Redis连接信息就可以。选择持久化至MySql,还需建立两张表存储session。
1 | CREATE TABLE `SPRING_SESSION` ( |
3.三方包依赖
1 | <!-- buc --> |
4.代码Demo
Spring Security
重载UserDatails接口用来构建Spring Security标准的用户信息类
1 | public class OcpUserDetails implements UserDetails { |
UserDetailsService,重载loadUserByUsername,将DB中的用户信息与权限信息放入Spring Security上下文中
1 |
|
重载OncePerRequestFilter接口,自定义登录拦截逻辑
1 |
|
Spring Security验证是否登录逻辑
1 |
|
WebConfig
1 |
|
表达式 | 描述 |
---|---|
hasRole([role]) | 当前用户是否拥有指定角色 |
hasAnyRole([role1,role2]) | 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true |
hasAuthority([auth]) | 等同于hasRole |
hasAnyAuthority([auth1,auth2]) | 等同于hasAnyRole |
Principle | 代表当前用户的principle对象 |
authentication | 直接从SecurityContext获取的当前Authentication对象 |
permitAll | 总是返回true,表示允许所有的 |
denyAll | 总是返回false,表示拒绝所有的 |
isAnonymous() | 当前用户是否是一个匿名用户 |
isRememberMe() | 表示当前用户是否是通过Remember-Me自动登录的 |
isAuthenticated() | 表示当前用户是否已经登录认证成功了 |
isFullyAuthenticated() | 如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true |
本地登录时,表单将username与password POST提交至 /login
除了在WebSecurityConfigurerAdapter配置URL级别的权限控制之外,还可以通过注解,实现方法级安全控制
注解 | 描述 |
---|---|
@PreAuthorize | 在方法调用之前,基于SpEL表达式的计算结果来限制对方法的访问 |
@PostAuthorize | 允许方法调用,但是如果SpEL表达式的计算结果为false,将抛出一个安全性异常 |
@PreFilter | 允许方法调用,但必须按照表达式来过滤方法的结果 |
@PostFilter | 允许方法调用,但必须在进入方法之前过滤输入值 |
SpEL可以实现更灵活的安全控制逻辑,比如某个用户只能在周二访问,非付费用户输入的字符字数不超过140个字等,但是安全注解不宜过于智能与灵活。
buc
1 | "SpringJavaAutowiringInspection") ( |
获取session中bucy用户信息BucSSOUser bucSSOUser = SimpleUserUtil.getBucSSOUser(request);
buc接入文档
http://gitlab.alibaba-inc.com/buc/sso/wikis/home
http://gitlab.alibaba-inc.com/buc/sso/wikis/buc-springboot-starter-guide
金融云
接入文档
https://lark.alipay.com/antcloud-platform/antcloud/antcloud-login
5.结语
使用Spring Security中很容易产生用户是否登录以及登录后的信息在哪里查看的困惑。
1
2SecurityContext ctx = SecurityContextHolder.getContext();
Authentication auth = ctx.getAuthentication();可以用上面提供类从Session中获取用户的登录信息,既可以在OncePerRequestFilter中判断用户是否登录,也可以用在Controller中获取当前session中的用户信息。
- Spring Security中部分功能封装过于细致,这种高度智能化的封装带来的好处是便于本地及Demo级开发,导致并不适应自定义的灵活需求。比如HttpBasic,自定义登录页面和jdbcAuthentication。
参考文献:
Spring Security:
https://www.jianshu.com/p/08cc28921fd0
https://segmentfault.com/a/1190000013057238Spring Session:
http://blog.didispace.com/spring-session-xjf-2/