目录
- 1 需求
- 2 实现
- 2.1 背景
- 2.2 实现
- 2.3 springsecurity 框架相关的配置
- 2.3.1 @EnableGlobalMethodSecurity详解
- 2.3.2 认证失败处理类AuthenticationEntryPointImpl
- 2.3.3 自定义退出处理类LogoutSuccessHandlerImpl
- 2.3.4 filter 为啥会多次执行
- 3 @PreAuthorize 注解
1 需求
我们打开若依项目,发现一些接口上面是有@PreAuthorize 注解,那么这个注解的作用是什么?
2 实现
2.1 背景
我们点进去@PreAuthorize 注解 里面的源码,发现他是第三方的jar包里面的,
我们都知道,若依项目是集成了 springsecurity 框架,所以这个注解直接使用的是这个 springsecurity 框架里面的,我们拿上这个注解使用就可以,那么他咋使用,底层逻辑是什么?
2.2 实现
既然若依项目是集成了 springsecurity 框架,那么肯定需要写springsecurity 的配置类,我们找一下
若依项目集成了很多的第三方的jar包,所以配置类是比较多,在这个ruoyi-framework 模块下,是有一个config文件夹,下面就是放的各种各样jar包的配置文件,我们找到和 springsecurity 框架相关的配置文件。
2.3 springsecurity 框架相关的配置
2.3.1 @EnableGlobalMethodSecurity详解
当我们想要开启spring方法级安全时,只需要在任何 @Configuration实例上使用 @EnableGlobalMethodSecurity 注解就能达到此目的。同时这个注解为我们提供了prePostEnabled 、securedEnabled 和 jsr250Enabled 三种不同的机制来实现同一种功能。
A. prePostEnabled = true:
会开启 @PreAuthorize 和 @PostAuthorize 两个注解。
@PreAuthorize注解会在方法执行前进行验证,
支持Spring EL表达式;
@PostAuthorize 注解会在方法执行后进行验证,
不经常使用, 适用于验证带有返回值的权限。
Spring EL提供了returnObject,
用于能够在表达式语言中获取返回的对象信息;
B. securedEnabled = true:
会开启@Secured 注解,用来定义业务方法的安全配置,
在调用的接口或方法上使用该注解。
在需要安全控制(一般使用角色或者权限进行控制)的方法上指定
@Secured,达到只有具备那些角色/权限的用户才可以访问该方法。
指定角色时必须以ROLE_开头,不可省略;不支持Spring EL表达式;
如果想要使用@Secured注解指定"AND"条件,
即调用deleteAll方法需同时拥有ADMIN和DBA角色的用户时,
@Secured便不能实现,
只能使用@PreAuthorize/@PostAuthorize注解。
2.3.2 认证失败处理类AuthenticationEntryPointImpl
这个类是在spring security 配置类里面进行引用的,所以我们先看这个类里面干了什么事情
认证失败处理类AuthenticationEntryPointImpl 实现了AuthenticationEntryPoint接口,重写了这个接口的方法
它在用户请求处理过程中遇到认证异常时,被ExceptionTranslationFilter用于开启特定认证方案(authentication schema)的认证流程。
这里参数request是遇到了认证异常authException用户请求,response是将要返回给客户的相应,方法commence实现,也就是相应的认证方案逻辑会修改response并返回给用户引导用户进入认证流程。
也就是认证失败,就会自动的进方法commence方法里面,走里面的
逻辑;
修改response并返回给用户引导用户进入认证流程。这个是主要的
目的
2.3.3 自定义退出处理类LogoutSuccessHandlerImpl
这个类实现了LogoutSuccessHandler接口,重写了里面的onLogoutSuccess方法,并且这个类上面是@Configuration;
说明当前类是全局配置类;
LogoutSuccessHandler接口是Spring Security框架里面的
;
为什么要实现这个接口?
大多数业务场景下,自定义登出成功页面也满足不了一些要求,
更别提默认的登出成功页面。这时候,就需要别的方案支持
,幸运的是,Spring Security 框架还真就非常贴心的提供了
这样一个接口 LogoutSuccessHandler, 专门用于处理用户
登出成功请求。当然了,对于 LogoutSuccessHandler 接口,
Spring Security 框架有一些默认的实现,也可以自行扩展。
同用户登录成功的业务场景,在用户登出成功后,我们也要通过邮件、
短信、微信,来通知用户,在什么时间,什么地点,退出了系统。
更甚至,可以通知用户本次登录都操作了那些功能,做了哪些操作
等等。同时,也要把这些信息记录在日志文件中。而这所有的需求,
都可以通过实现 LogoutSuccessHandler 接口来实现。
扩展 LogoutSuccessHandler 接口实现发送信息、日志记录。
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
super.onLogoutSuccess(request, response, authentication);
this.logger.info(String.format("IP %s,用户 %s, 于 %s 退出系统。", request.getRemoteHost(), authentication.getName(), LocalDateTime.now()));
try {
// 发邮件
this.emailService.send();
// 发短信
this.smsService.send();
// 发微信
this.weChatService.send();
} catch (Exception ex) {
this.logger.error(ex.getMessage(), ex);
}
}
目前若依项目,当退出的时候,所做的事情是在redis里面清空token,之后异步记录一下日志;
package com.ruoyi.framework.security.handle;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.web.service.TokenService;
/**
* 自定义退出处理类 返回成功
* 也就是用户退出系统,在退出之前会默认做的事情
* @author jing
*/
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
// 将token的工具类 引入
@Autowired
private TokenService tokenService;
/**
* 退出处理
*
* @return
*/
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException
{
// 根据request 获取到用户的信息
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser))
{
String userName = loginUser.getUsername();
// 删除用户缓存记录 redis 里面进行删除
tokenService.delLoginUser(loginUser.getToken());
// 记录用户退出日志
AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, "退出成功"));
}
// 直接渲染信息到客户端
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功")));
}
}
2.3.4 filter 为啥会多次执行
在servlet2.3中,Filter会经过一切请求,包括服务器内部使用的forward转发请求和<%@ include file=”/login.jsp”%>的情况
servlet2.4中的Filter默认情况下只过滤外部提交的请求,forward和include这些内部转发都不会被过滤
就是有可能一个接口里面,使用了forward和include这些内部转发;
然后继续 执行过滤器
3 @PreAuthorize 注解
通过在PreAuthorize表达式的Bean名称开头添加@来引用注册为组件的Bean。