springboot框架下,只需要加入security的jar包,重启应用后就会拦截所有的请求,例如请求webapp下的页面就会自动进入springsecurity自带的登录页面。
现在的问题是,拦截之后要跳转到指定的页面。
WebSecurityConfig中
http.cors().and().csrf().disable().authorizeRequests().antMatchers(".*").authenticated()
这样设置的话,所有的页面或者接口都不会被拦截
编目的security实现:
1.因为有个地方调试不顺利(具体好像是 SpringContextHolder里得不到UserDetailService对象,偶尔能得到,后来彻底得不到了),所以用户信息没有加入security框架的userDetailService里。而是在系統启动的时候读入了redis里。
2.没有用JWTAuthenticationFilter和JWTAuthorizationFilter。用了一个Filter。从request对象中得到jwt,自己验证登录信息是否正确。
3.登录没有执行filter中的验证方法,直接到了url对应的controller方法里。
正经的security实现:
1. 收到登录请求的时候把登录信息中的username以及根据username得到的权限(包括用户密码、角色对应的权限等)放入user对象中。密码是加密的,加密方法可以自己设置。
2. 在验证登录的filter中从request对象中得到user对象。
如何验证user对象是否过期、锁定等问题,猜想是在AuthenticationManager里面去遍历AuthenticationProvider,由provider来完成验证
系统启动的时候会初始化AuthenticationManager,从实例名来看,就是ProviderManager。
默认配置的Provider有DaoAuthenticationProvider,但是验证的时候却是由AbstractUserDetailsAuthenticationProvider的authenticate方法验证的。
AuthenticationProvide接口有很多实现类。
正经的security不需要自己写登录的验证方法,是由AuthenticationProvider的实现类来验证的。
系统启动的时候会执行AbstractUserDetailsAuthenticationProvider
中的方法afterPropertiesSet,WebSecurityConfig中的config方法。
在WebSecurityConfig中配置http.cors.disable()可以绕过浏览器跨域请求时自动发送的CORS请求,
Cors是Cross Origin Resource Sharing。
csrf:cross site request forgery,跨站请求伪造
authenticate顺序:
AbstractUserDetailsAuthenticationProvider
执行authenticate方法,判断cache中的user对象是否为空,如果为空,就调用子类DaoAuthenticationProvider中的retrieveUser方法,调用getUserDetailsService的loadUserByUserName方法。
框架默认会验证密码是否正确,有效期的验证要等抽空再看。
把user的权限集合放入全局缓存SecurityContextHolder中,业务请求过来的时候,根据jwt得到roleid,roleId有对应的url集合,这个url集合的key是url。这个对应关系和具体用户无关,应该是在系统启动时加入redis的,如果启动后某角色的权限被修改,再去map中刷新权限。对于权限直接挂到用户的情况来说不合适,那种情况下,不能用角色来表示用户的权限了,要用url集合来表示。如果考虑数据权限,还要加上表名,字段名,某字段对应的限制条件表达式。
目前的水平似乎不能在WebSecurityConfig中注入一个自己定义的类,所以用户的权限信息只能保存在UserDetailService返回的User里,GrantedAuthority里保存roleId貌似不太合适,因为jwt里有roleId。如果用来保存roleId对应的权限url,那么只要user对象存在,就不用去查询用户权限。security框架的机制是如果登录失败了会再去查一遍最新的用户名和密码,但是这样是有问题的,如果我改了密码,但是由于修改密码不能更新到缓存中,所以如果密码已经被泄露了,那么用旧密码还是能登录成功。所以cacheWasUsed应该一直让他为false才对。这样的话,user的权限url也是每次验证去拿新的。这样就不会有问题,但是会不会增加数据库的负担呢。
现在的问题是登录的时候返回给前端的信息貌似用user对象是无法表达的,如果可以的话,那就是把信息序列化为字符串,放入auths,前端拿到信息之后再反序列化。如果不这么做,那就要在登录接口之外再加上一个获取用户信息的接口,这个接口会带上jwt,只要jwt验证通过就可以拿到用户信息。
业务访问的时候,url和jwt,验证流程是根据url去缓存里拿到roleId的集合,然后判断这个集合是否包含jwt中解析出的roleId。
webSecurityConfig的config方法配置各种authenticationProvider,有的filter用A provider验证。
WebSecurityConfiguration$$EnhancerBySpringCGLIB
SessionAuthenticationStrategy
==========================================================================
UsernamePasswordAuthenticationFilter(A)和BasicAuthenticationFilter(B)的路线不太一样,
A的路线是先走父类的父类的doFilter方法,判断是否需要验证,如果不需要验证,则沿着chainFilter接着往下走,否则就调用子类的attemptAuthentication方法,attemptAuthentication方法再调用authenticationManager的authenticate方法。
如果执行了
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
那么这个请求又跑到刚被执行过的这个filter里了;
如果不执行这个方法,在successfulAuthentication里面写
chain.doFilter(request, response);也是一样的效果。为什么不能到controller里呢,这个问题还不太清楚,可能是由于filter的性质导致的。因为它不是OncePerRequestFilter