认证: 验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户
授权: 经过认证后判断当前用户是否有权限进行某个操作
一、入门案例实现
搭建springboot工程后,创建启动类和Controller,引入SpringSecurity依赖
尝试访问Controller接口就会自动跳转到SpringSecurity的默认登录界面
输入用户名是user,密码会在控制台输出
1.1、登陆校验流程
登陆接口逻辑
携带用户名密码访问服务器,成功就会生成jwt并且返回给前端,
以后的请求就会在请求头携带token,只有获取到token中的用户信息才能确定是否有访问权限
SpringSecurity完整流程
原理是一个过滤器链,内部提供了各种功能的过滤器
下面看看入门案例的过滤器链
UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请
求
ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和
AuthenticationException 。
FilterSecurityInterceptor:负责权限校验的过滤器。
1.2、自定义登陆校验
第一步:验证用户名和密码正确性
实现UserDetailsService接口重写其中的loadUserByUsername方法,
从数据库查询用户名和密码,查询成功就返回一个UserDetails对象(因为重写的这个方法需要返回这个对象)
如果要测试,需要往用户表中写入用户数据,并且如果你想让用户的密码是明文存储,需要在密
码前加{noop}
第二步 有了上面的准备工作后,就设置SpringSecurity拦截除了登陆接口的一系列接口了
继承WebSecurityConfigurerAdapter类,然后重写其中的 configure方法
接下来实现登陆方法
需要经过authenticationManager.authenticate(authenticationToken);进行验证,注意这个方法返回的对象就是第一步实现的UserDetailsService返回的对象,这个方法的参数是authenticationToken,包含了用户名密码信息
当authenticate不为空就代表密码验证成功接下来就根据UserId生成token,并且将token返回给前端
以后前端每次的请求就会携带这个token
经过前面的三步,已经实现了拦截除登陆接口的所有方法,并且实现了登陆接口的逻辑
其中实现UserDetailsService接口重写其中的loadUserByUsername方法来定义如何检验密码
通过在SpringSecurity配置类的configure方法指定需要拦截的方法
实现登陆接口,就能保证当访问没有被拦截的登陆方法就能根据需要生成token,供给后续SpringSecurity拦截的方法判断是否有访问权限
第四步
自定义一个过滤器,过滤器会获取请求头中的token,对token进行解析获取其中的userId,然后根据这个userid去redis获取对应的LoginUser对象,然后封装Authentication对象存入SecurityContextHolder(可以后续登出的时候获取到用户信息)
然后在配置类中通过http.addFilterBefore(jwtAuthenticationTokenFilter将token校验过滤器添加到过滤器链中。到此就实现了登陆接口进行校验密码生成token,其他接口访问前需要校验token
该方法需要继承OncePerRequestFilter类重写其中的doFilterInternal方法
登出接口
获取SecurityContextHolder中的认证信息,删除redis中对应的数据即可。
到这一步一个基本的登陆功能就实现了,但是并没有权限系统
授权
授权基本流程
使用默认的FilterSecurityInterceptor来进行权限校验。在
FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。
所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。
然后设置我们的资源所需要的权限即可。
使用基于注解的权限控制方案,通过使用注解来指定对应的资源所需的权限
首先需要先开启相关配置。
@EnableGlobalMethodSecurity(prePostEnabled = true)
在对应的资源使用@PreAuthorize注解,表示访问该资源需要test权限
写死的情况
在先前的写UserDetailsServiceImpl查询用户后获取到对应的权限信息封装到1UserDetails返回时就可以直接把权限信息封装到UserDetails中返回
所以这次需要前定义了UserDetails的实现类LoginUser,在其中添加相关权限的信息
具体添加属性
@JSONField(serialize = false)
private List<GrantedAuthority> authorities;
注解确保序列化时不会转为JSON字符串,保证了隐私性
这样进一步我们就能在UserDetailsServiceImpl返回带有权限信息的LoginUser对象了
在UserDetailsServiceImpl返回信息时也会多下面这一步
List<String> list = new ArrayList<>(Arrays.asList("test"));
return new LoginUser(user,list);
从数据库查询权限信息
RBAC权限模型
基于角色的权限控制(具体应该是用户关联角色,角色关联权限)
所以可以在UserDetailsServiceImpl中去调用mapper的方法查询权限信息封装到LoginUser对象中即可,之后经过权限过滤器就可以校验权限了。