一、Spring Security 简介
1. 认证授权分析
用户在进行资源访问时,要求系统要对用户进行权限控制,其具体流程如图所示:
2. Spring Security 概述
Spring Security 是一个企业级安全框架,由spring官方推出,它对软件系统中的认证,授权,加密等功能进行封装,并在springboot技术推出以后,配置方面做了很大的简化.市场上现在的分布式架构下的安全控制正在逐步的转向Spring Security.
3. Spring Security 基本架构
Spring Security 在企业中实现认证和授权业务时,底层构建了大量的过滤器.
其中:
绿色部分为认证过滤器,需要我们自己配置,也可以配置过个认证过滤器.也可以使用Spring Security提供的默认认证过滤器.黄色部分为授权过滤器.Spring Security就是通过这些过滤器然后调用相关对象一起完成认证和授权操作.
二、SpringSecurity 认证逻辑实现
1. 自定义登陆逻辑
第一步:定义security配置类
第二步:定义UserDetailService接口实现类,自定义登录逻辑
UserDetailService为SpringSecurity官方提供的登录逻辑处理对象,我们自己可以实现此接口,然后在对应的方法中进行登录逻辑的编写即可.
(1) 步骤分析
①实现UserDetailsService接口类
② 依赖注入BCryptPasswordEncoder
③ 基于用户名从数据库查询用户信息
④ 判断输入信息是否与username匹配,如果不匹配,则抛出用户不存在异常
⑤ 查询用户密码和权限信息
⑥ 将权限信息以API封装到list集合中
⑦ 将用户信息封装到UserDetails对象中并返回
说明,这里的User对象会交给SpringSecurity框架,框架提取出密码信息,然后与用户输入的密码进行匹配校验.
第三步:启动服务进行登陆,访问测试
2. 自定义登陆页面
第一步:定义登陆页面(直接在resources - static目录下创建即可)
注意:请求的url暂时为”/login”,请求方式必须为post方式,请求的参数暂时必须为username,password。这些规则默认在UsernamePasswordAuthenticationFilter中进行了定义。
第二步:修改安全配置类,让其实现接口,并重写相关config方法,进行登陆设计
(1) 登录后跳转页面
(2) 步骤分析
① 继承WebSecurityConfigurerAdapter接口,重写configure方法
② 关闭跨域攻击
③ 配置登录URL(登录表单使用哪个页面)
登录页面是哪个?
登录后跳转到哪儿? => 参数名不一致时的操作
登录成功/失败的url(重定向到哪儿/转发到哪儿)
④ 放行登录URL(不需要认证就可以访问)
定义要放行的资源
放行资源
除定义的放行资源外拦截所有资源
3. 登录成功处理器
3.1 跳转页面(直接执行重定向的处理器)
(1) 直接执行重定向的处理器
说明: 将SecurityConfig中重定向的地址传入RedirectAuthenticationSuccessHandler,作为重定向的跳转url
(2) 步骤分析
① 实现AuthenticationSuccessHandler类,重写onAuthenticationSuccess方法
② 封装一个重定向的URL
③ 接收从SecurityConfig类中传递的URL,赋值给封装的URL中,再传参到重写的onAuthenticationSuccess方法
④ 将接收到的重定向URL传到 sendRedirect() 方法中,实现页面跳转
3.2 返回JSON数据的处理器
(1) 直接返回JSON数据的处理器
说明: 项目中可不做 获取用户身份和用户凭证
(2) 步骤分析
① 实现AuthenticationSuccessHandler类,重写onAuthenticationSuccess方法
② 设置响应数据的编码
③ 告诉客户端响应数据的类型,以及客户端以怎样的编码进行显示
④ 获取一个输出流对象
⑤ 构建一个map对象,将返回的数据存入map中
⑥ 基于json中的ObjectMapper对象将一个对象转换为json格式字符串
⑦ 向客户端输出一个json格式字符串
⑧ 刷新输出流
补充: 获取用户身份(principal -身份) 和 用户凭证(credentials -凭证,默认为密码)
//可以在这样的方法中获取登录的用户信息(后续可以将这样的数据写到redis缓存或者直接构建一个token对象写到客户端)
//获取用户身份(principal-身份的意思)
User principal = (User) authentication.getPrincipal();
System.out.println(principal.getUsername()+"/"+principal.getPassword()
+"/"+principal.getAuthorities() );
//获取用户凭证(默认为密码,credentials 是凭证的意思)
Object credentials = authentication.getCredentials();
//输出用户身份和用户凭证
System.out.println("principal="+principal);
System.out.println("credentials="+credentials);
4. 登录失败处理器
4.1 跳转页面(直接执行重定向的处理器)
(1) 代码截图
(2) 步骤分析
① 实现AuthenticationFailureHandler类,重写onAuthenticationFailure方法
② 封装一个重定向的URL
③ 接收从SecurityConfig类中传递的URL,赋值给封装的URL中,再传参到重写的onAuthenticationFailure方法
④ 将接收到的重定向URL传到 sendRedirect() 方法中,实现页面跳转
4.2 返回JSON数据的处理器
(1) 代码截图
(2) 步骤分析
① 实现AuthenticationFailureHandler类,重写onAuthenticationFailure方法
② 设置响应数据的编码
③ 告诉客户端响应数据的类型,以及客户端以怎样的编码进行显示
④ 获取一个输出流对象
⑤ 构建一个map对象,将返回的数据存入map中
⑥ 基于json中的ObjectMapper对象将一个对象转换为json格式字符串
⑦ 向客户端输出一个json格式字符串
⑧ 刷新输出流
5. 放行静态资源
(1) 代码截图
(2) 步骤分析
① 设置请求的授权
② 设置要放行的资源
③ 允许上述资源直接访问
④ 设置除了以上资源必须认证才可以访问
6. 登出设计及实现
(1) 代码截图
(2) 步骤分析
① 开始设置登出信息
② 设置登出路径
③ 设置登出后显示的页面
三、SpringSecurity授权逻辑实现
1. 修改授权配置类
在权限配置类上添加启用全局方法访问控制注解:
2. 定义资源Controller
定义一个ResourceController类,作为资源访问对象
其中,@PreAuthorize注解描述方法时,用于告诉系统访问此方法时需要进行权限检测。需要具备指定权限才可以访问。例如:
@PreAuthorize(“hasAuthority('sys:res:delete”) 需要具备sys:res:delete权限
@PreAuthorize(“hasRole(‘admin’)”) 需要具备admin角色
(1) 代码截图
(2) 步骤分析
① 定义增删改查的请求路径和返回值
② 指定相应方法的权限或角色
3. 启动服务访问测试
使用不同用户进行登陆,然后执行资源访问,假如没有权限,则会看到响应状态吗403
四、Spring认证和授权异常处理
1. 异常类型
对于SpringSecurity框架而言,在实现认证和授权业务时,可能出现如下两大类型异常:
(1) AuthenticationException (用户还没有认证就去访问某个需要认证才可访问的方法时,可能出现的异常,这个异常通常对应的状态码401)
(2)AccessDeniedException (用户认证以后,在访问一些没有权限的资源时,可能会出现的异常,这个异常通常对应的状态吗为403)
2. 异常处理规范
SpringSecurity框架给了默认的异常处理方式,当默认的异常处理方式不满足我们实际业务需求时,此时我们就要自己定义异常处理逻辑,编写逻辑时需要遵循如下规范:
(1)AuthenticationEntryPoint: 统一处理 AuthenticationException 异常
(2)AccessDeniedHandler: 统一处理 AccessDeniedException 异常.
3. 自定义异常处理对象
3.1 处理没有认证的访问异常
(1) 代码截图
(2) 步骤分析
① 实现AuthenticationEntryPoint接口类,重写commence方法
② 设置响应数据的编码
③ 告诉客户端响应数据的类型,以及客户端以怎样的编码进行显示
④ 获取一个输出流对象
⑤ 构建一个map对象,将返回的数据存入map中(返回状态码为 .SC_UNAUTHORIZED)
⑥ 基于json中的ObjectMapper对象将一个对象转换为json格式字符串
⑦ 向客户端输出一个json格式字符串
⑧ 刷新输出流
3.2 处理没有权限时抛出的异常
(1) 代码截图
(2) 步骤分析
① 实现AccessDeniedHandler接口类,重写handle方法
==> 重定向到其他页面: 见图
② 设置响应数据的编码
③ 告诉客户端响应数据的类型,以及客户端以怎样的编码进行显示
④ 获取一个输出流对象
⑤ 构建一个map对象,将返回的数据存入map中(返回状态码为 .SC_FORBIDDEN)
⑥ 基于json中的ObjectMapper对象将一个对象转换为json格式字符串
⑦ 向客户端输出一个json格式字符串
⑧ 刷新输出流
4. 配置异常处理对象
在配置类SecurityConfig中添加自定义异常处理对象