1、主要的几个类
1) SecurityConfig
自定义security策略,继承WebSecurityConfigurerAdapter,用于进行spring security的各种配置
2)JWTAuthenticationFilter
自定义验证类,继承UsernamePasswordAuthenticationFilter,用于验证用户并生成token,有两个主要方法:attemptAuthentication:接收并解析用户凭证。
successfulAuthentication:用户成功登录后,这个方法会被调用,在这个方法里生成token并跳转到/getSystemMessage接口,该接口用于携带token重定向至systemId对应的子系统页面。
unsuccessfulAuthentication:用户登录失败后,调用这个方法返回403错误提示。
3)JWTAuthorizationFilter
自定义授权类,继承BasicAuthenticationFilter,用于验证用户token。
2、security认证过程
(1)用户名和密码被过滤器获取到,封装成Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
(2)AuthenticationManager 身份管理器负责验证这个Authentication 。
(3)认证成功后,AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。
(4)SecurityContextHolder安全上下文容器将第3步填充了信息的Authentication,通过SecurityContextHolder.getContext().setAuthentication()方法,设置到其中。
3、security 认证架构
1)常用过滤链
HTTP Basic 认证请通过过滤器链, 到达 BasicAuthenticationFilter
登录认证被 UsernamePasswordAuthenticationFilter 识别,拦截并处理
2)基于用户凭证创建 AuthenticationToken
以表单登录为例子, 用户在登录表单中输入用户名和密码, 并点击确定, 浏览器提交POST请求到服务器, 穿过过滤器链, 被 UsernamePasswordAuthenticationFilter 识别, UsernamePasswordAuthenticationFilter 提取请求中的用户名和密码来创建 UsernamePasswordAuthenticationToken 对象。
3)把组装好的 AuthenticationToken 传递给 AuthenticationManagager
组装好的 UsernamePasswordAuthenticationToken 对象被传递给 AuthenticationManagager 的 authenticate 方法进行认证决策。AuthenticationManager 只是一个接口, 实际的实现是 ProviderManager。
ProviderManager 有一个配置好的认证提供者列表(AuthenticationProvider), ProviderManager 会把收到的 UsernamePasswordAuthenticationToken 对象传递给列表中的每一个 AuthenticationProvider 进行认证。
4)认证处理
ProviderManager 会把收到的 UsernamePasswordAuthenticationToken 对象传递给列表中的每一个 AuthenticationProvider 进行认证.那到底 UsernamePasswordAuthenticationToken 由supports方法来决定的。
5)UserDetailsService获取用户信息
UserDetailsService 获取的对象是一个 UserDetails。
6)认证结果处理
如果认证成功(用户名,密码完全正确), AuthenticationProvider 将会返回一个完全有效的 Authentication 对象(UsernamePasswordAuthenticationToken). 否则抛出 AuthenticationException 异常。
4、sso单点登录流程
假设项目现在有认证中心、子系统A、子系统B三个系统:
1)用户未登录直接访问子系统A时,由于子系统A的localStorage未存储有token,前端直接通过浏览器重定向至认证中心的验权接口去校验用户是否登录,并在url上拼接子系统A的唯一标识码systemID,用于认证中心登录或校验成功后重定向回子系统A。
2)重定向到认证中心后,认证中心的校验接口尝试获取token,由于用户并未登录,因此认证中心的cookie中未存储token值,校验不通过,因此校验接口重定向至认证中心的登录页面并带上子系统A的systemID。
3)用户在登录页面登录成功后,生成token并存储到认证中心的cookie中,然后登录接口通过systemID获取子系统A的页面地址(存储在数据库中),将token拼接在url上并重定向到子系统A。
4)子系统A的前端接收到认证中心的重定向请求并获取到url上的token值,将token值存储到localStorage,至此完成子系统A的单点登录。
5)用户登录成功后直接访问子系统B,此时子系统B的localStorage未存储token,前端直接通过浏览器重定向至认证中心的验权接口去校验用户是否登录,并在url上拼接子系统B的唯一标识码systemID,用于认证中心登录或校验成功后重定向回子系统B。
6)认证中心校验接口尝试获取cookie中的token,由于前面用户已经登录成功,cookie中已存在token,因此校验接口判断用户已经登录,通过systemID重定向回子系统B,并在url上拼接上token。
7)子系统B的前端接收到认证中心的重定向请求并获取到url上的token值,将token值存储到localStorage,至此完成子系统B的单点登录。
5、问题
搭项目时遇到两个问题,现记录如下:
1)保存token到cookie和获取cookie中的token失败,由于子系统和认证中心之间是跨域请求,因此无法对cookie进行操作,这里使用nginx反向代理来实现对cookie的操作(也就是所谓的欺骗浏览器)。
2)后台重定向失败,这点和前端大佬排查了很久,最后发现是由于前端请求后台的接口时使用的是ajax,导致后台无法重定向,页面跳转失败;因此前端改用location.hrefc请求接口(即浏览器地址栏请求),从而解决上述问题。