安全
安全
一套系统一定需要考虑安全的问题.
登录
首先要确定的是,用谁的登录页面,这个决定了使用谁的SSO. 如果使用系统外的登录页面,我们称为集成外部SSO,如果使用我们自己的登录页面,我们成为内部的功能扩展
登录原理
登录页面的渲染,是通过二次跳转实现的,主要是为了避免在iframe中的登录过期以后,能顺利的主屏幕跳转到主页面,而不是在iframe内部跳转.
- 一个请求如果没有携带登录标识(token),就会被SSOFilter拦截,跳转到登录页面
- 请求方法
com.istock.union.controller.PreLoginController.toLogin - 跳转到空白页面,再使用top.href进行的跳转,再次请求
com.istock.union.controller.PreLoginController.preLogin,渲染登录页面,这个preLogin,可以在后台增加部分内容,比如登录页面上需要展示一些公告信息…. - 登录页面渲染过程中,会加载iframe,请求
com.istock.union.controller.PreLoginController.showLogin,中间有个判断,是否需要加载登录框,如果已经携带登录标识,可以直接跳过登录,如果没有登录标识,显示登录框 - showLogin的存在,还可以扩展非用户名密码登录,比如扫码,安全key登录等等…
- 输入用户名密码,
LoginByPasswdController.login,检查数据库用户名密码,调用InnerTokenService,申请token,并且在页面进行保存,最后跳转到login.success.url,完成登录.
注意
在config/conf.properties的配置文件中,pre开头的请求,不会经过SSOFilter
登录扩展
非数据库的用户名密码校验
有的客户希望我们的系统集成公司的邮箱系统,因为那里面有所有的公司员工,邮箱服务还是用户名密码的验证方式,但是不是存储在数据库里面,而是需要实现LDAP的访问协议.
需要实现com.istock.union.user.service.SSOUserService 对传入的用户名和密码进行校验
非用户名密码验证
有的系统客户的安全等级要求比较高,比如会使用UKey的方式活着扫码的方式实现登录
- 扩展登录页面,在logFrame.vm或者login.vm上面,可以引入多tab的方式,在保留用户名密码登录功能的同时,增加不同的TAB,实现比如UKey登录,扫码登录.
- 在系统中需要实现整个showLogin开始的登录页面的展示逻辑和登录逻辑.
- 一定要调用到tokenApply方法生成token
- 使用loginSuccess方法进行页面跳转
提示
showLogin的还有一个功能,就是判断当前是否要展示登录框,如果在调用showLogin的时候,已经存在了有效的登录标识,可以直接跳转到登录成功页面,这就是登录成功以后,再直接在地址栏输出安全url的情况下,可以直接跳转.而且可以控制到底是跳转到访问的url还是跳转到登录成功页.
授权&鉴权

- 当页面请求一个/s开头的资源,这个代表一定要经过授权的安全资源.
- 首先经过SSOFilter,对登录标识进行检查,如果没有登录,会被filter拦截,直接跳转登录页面.
- 在SSOFilter中,系统会对cookie,requestHeader和requestParameter进行判断,判断中间是否存在登录标识,如果存在登录标识,则对登录标识进行验证,然后将登录标识代表的用户数据塞入threadlocal,保证后续其他的操作,在使用SSOClientUtils的时候,可以得到当前的用户
- 在请求到具体的资源controller之前,会被com.istock.union.filter.AuthorizationInterceptor拦截 在授权前,先判断用户的一些异常情况,比如是否密码期限已经到了,需要强制改密码.如果需要强制改密码,则跳转到强制密码修改页面.
- 检查登录标识所存储的登录信息中,是否存在权限信息,如果不存在权限,则先进行授权,如果已经存在,则跳过. 最后再进入controller的方法前,被方法中配置的@SecurityChecker("user:deptInfo:search")拦截,执行com.istock.union.security.SecurityCheckerAspect的拦截方法,拿着当前资源的资源标识去授权列表中去匹配是否存在授权记录,如果存在授权记录,则允许访问,如果不存在,则返回401.
注意
一个controller方法,如果没有使用@SequrityChecker注解,则不会被鉴权 SSOFilter负责的是登录标识的检查,凡事没有在exculde列表中的资源,都会被检查.
注意
AuthenticationInterceptor负责的是授权和授权之前的用户状态信息的判断.用户密码过期,强制修改密码的功能,是在登录后,授权前的校验判断.在这边判断的好处是,如果用户在登录后,直接修改URL请求,还是会直接跳转到强制修改页面.还有其他的功能也类似,比如第一次登录改密码
登录信息操作
SSOClientUtils
通过SSOClientUtils进行操作,可以得到当前线程的登录用户.这个登陆用户是在SSOFilter进行设置的. SSOClientUtils内部的存储机制,是使用com.istock.union.user.service.StorageService接口实现,一共提供了2种实现 com.istock.union.security.SessionStorageService 登录信息使用session存储,依赖于容器提供的HttpSession机制,如果使用请求分发,需要开启session粘滞,同时需要注意无法使用多浏览器tab页面操作多用户.
com.istock.union.user.security.impl.BaseCacheStorageService 登录信息使用cache存储,cacheManager可以设置,支持ehcache,redis和memcached 对于用户规模比较小,用户的变化不多的情况下,可以使用session机制,比较简单,不容易出问题.对于系统规模比较大,用户要求比较高,推荐使用cache存储.
注意
com.istock.union.user.service.StorageService接口中的putObject和getObject,没有失效时间,会随着token的失效一起失效,类似于session的过期机制.可以存储一些session相关数据,比如com.istock.user.controller.DictInfoController中,对于请求中间数据的存储.
单一/多重登录
通常有用户会要求,系统中的user,只允许登录一个人,如果在另外的地方登陆,会T掉之前的登录. 修改配置文件config/conf.properties
#配置不同登录设备的登录限制次数
#-1代表不限制
#配置项中间的大写值代表登录设备,需要通过nginx中转发设置httpHeader device="PC"
PC.count=-1
MOBILE.count=-1
#token超过数量以后的移除策略
#old,移除最老的token
#forbidden,禁止当前登录 ..... 慎用,如果当前已经登录,关闭浏览器,没有登出,则后面的用户就不能登录了,只能等超时
sso.login.token.overStrategy=old登录验证码
#sso在登录的时候,如果访问过多的数据库,存在被刷库的可能
#配置sso.login.verify.count为一个数字,意思是登录次数超过配置的数值,就在页面上展示验证码
#配置为<=0,永远展示验证码
#配置为10000,会额外记录一个信息在session,需要开启nginx的会话保持
sso.login.verify.count=1
通过上述配置,可以在登录页面出现验证码,也可以不出现验证码,也可以错误次数超过一定的次数才出现验证码登录标识
登录标识(token),在登录之后的跳转 com.istock.union.controller.LoginByPasswdController.loginSuccess(ModelMap, SSOUser, HttpServletRequest, HttpServletResponse) 会生成token
#将登录标识(token)存储在什么地方,可选值,cookie:page,需要使用js,写入到当前页面
#如果需要同一个浏览器使用多个用户同时登录,需要使用page
sso.token.storeType=page这个配置会决定token的存储位置,支持2种设置
- 如果值为cookie,则会通过response写入cookie
- 使用cookie存储,比较简单
- 不支持同一个浏览器同时操作多用户
- 如果值为page,则会通过空白页面,login/redirect.vm,通过js的方式,写入到浏览器的本地存储.
- token的值是通过在http的header或者在url请求中的参数传递的
- 直冲同一个浏览器同时操作多个登录用户
注意
一旦使用某一个配置,请不要更换,如果更换,请清空浏览器缓存.
