问题
我们从若依项目的简介中可以知道,若依前后端分离系统采用了Spring Security作为权限校验框架,那么,如果我们想要不登录就可以访问某些页面应该怎么办?
分析
若依官网的解释:若依官网解释
有关spring security配置的东西若依框架都在SecurityConfig
类里面有写。找到configure()
方法,一般这个类里面会写。
这里的意思就是 permitAllUrl.getUrls()
集合 里面的路径因为后面设置了permitAll()
用户可以任意访问。所以我们要想某些路径不登陆就可以访问,其实只需要我们的不登陆像访问的路径在 permitAllUrl.getUrls()
集合里面即可。
# 若依原作者的注释
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
SecurityConfig
类里面有如下代码:
/*** 允许匿名访问的地址*/
@Autowired
private PermitAllUrlProperties permitAllUrl;
permitAllUrl 是从这里注入进来的,想要知道怎么不登陆就可以访问,就必须分析类PermitAllUrlProperties
。
PermitAllUrlProperties 类
这个类有@Configuration
注解,代表这个类是已启动就被注册到 spring 容器里面。
package com.ruoyi.framework.config.properties;import com.ruoyi.common.annotation.Anonymous;
import org.apache.commons.lang3.RegExUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;/*** 设置Anonymous注解允许匿名访问的url* 该配置类 项目启动就会被加载* 实现了 ApplicationContextAware 类 就可以在本类获取 bean 对象* 凡是继承 InitializingBean 的类,在初始化bean的时候都会执行 afterPropertiesSet 方法*** 注意:1、在Spring初始化bean的时候,如果该bean实现了InitializingBean接口,并且同时在配置文件中指定了init-method,* 系统则是先调用afterPropertieSet()方法,然后再调用init-method中指定的方法* Spring为bean提供了两种初始化bean的方式,实现 InitializingBean 接口,* 实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。* 2、实现InitializingBean接口是直接调用afterPropertiesSet方法,* 比通过反射调用 init-method 指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。* 3、如果调用 afterPropertiesSet 方法时出错,则不调用init-method指定的方法。** BeanPostProcessor,该接口中有两个方法,InitializingBean发挥作用的时机就在这两个方法之间。从语义也很好看出来,* postProcessBeforeInitialization -> initializingBean -> postProcessAfterInitialization* afterPropertiesSet 发生作用的时机是当前类的实例化的时候,而 BeanPostProcessor 则是所有类,这也是为什么 afterPropertiesSet 的函数中没有参数** postProcessBeforeInitialization方法在bean初始化之前执行, postProcessAfterInitialization方法在bean初始化之后执行。** 构造函数 -> postProcessBeforeInitialization -> @PostConstruct -> initializingBean -> init-method -> postProcessAfterInitialization* 由此可见初始化Bean的先后顺序为:* Bean 本身的构造函数* BeanPostProcessor 的 postProcessBeforeInitialization方法* 类中添加了注解 @PostConstruct 的方法* InitializingBean 的 afterPropertiesSet 方法* init-method* BeanPostProcessor 的 postProcessAfterInitialization 方法* Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)* @author ruoyi*/
@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");private ApplicationContext applicationContext;private List<String> urls = new ArrayList<>();public String ASTERISK = "*";@Overridepublic void afterPropertiesSet(){/*** RequestMappingHandlerMapping的作用是在容器启动后将系统中所有控制器方法的请求条件(RequestMappingInfo)和控制器方法(HandlerMethod)的对应关系注册* 到RequestMappingHandlerMapping Bean的内存中,待接口请求系统的时候根据请求条件和内存中存储的系统接口信息比对,再执行对应的控制器方法。*/RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);// HandlerMethod 类用于封装控制器方法信息,包含类信息、方法Method对象、参数、注解等信息,具体的接口请求是可以根据封装的信息调用具体的方法来执行业务逻辑;// RequestMappingInfo其实就是将我们熟悉的@RequestMapping注解的信息数据封装到了RequestMappingInfo POJO对象之中,然后和HandlerMethod做映射关系存入缓存之中;// 可以参考博客:https://blog.csdn.net/yaomingyang/article/details/107026595Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();map.keySet().forEach(info -> {HandlerMethod handlerMethod = map.get(info);// 获取方法上边的注解 替代path variable 为 *// 从类或方法中查找某个 Anonymous 注解Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);Optional.ofNullable(method).ifPresent(anonymous -> info.getPatternsCondition().getPatterns().forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));// 获取类上边的注解, 替代path variable 为 *Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);Optional.ofNullable(controller).ifPresent(anonymous -> info.getPatternsCondition().getPatterns().forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));});}@Overridepublic void setApplicationContext(ApplicationContext context) throws BeansException{this.applicationContext = context;}public List<String> getUrls(){return urls;}public void setUrls(List<String> urls){this.urls = urls;}
}
从上面的代码我们知道,只要我们给类上加 @Anonymous
注解,就可以将不登陆就可以访问的路径加入urls
集合里面。
/*** 匿名访问不鉴权注解* * @author ruoyi*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Anonymous
{
}
测试
有了以上的分析,我们接下来测试一波。我们测试缓存监控页面,请求为
http://localhost/dev-api/monitor/cache
不加@Anonymous
注解之前:
测试结果:
加了 @Anonymous
注解之后:
测试结果,访问成功:
总结
1、在若依的系统里面,如果我们想要实现某一个接口不登陆就可以访问,只需要在方法上添加 @Anonymous
注解即可。
2、如果我们想要在我们的项目里面实现不登陆就可以访问,我们可以将若依的PermitAllUrlProperties
类和Anonymous
类拿过来,然后在SecurityConfig
spring security的配置类添加如下代码。
/*** 允许匿名访问的地址*/
@Autowired
private PermitAllUrlProperties permitAllUrl;@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{// 注解标记允许匿名访问的url// 自定义@Anonymous注解添加到方法上可以实现跳过权限验证。ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());// ....
}
拓展:另一种方法基于配置文件
经过上面的分析,我们知道,实现不登陆就可以访问某些接口,若依系统采用的是注解的形式,其实我们还可以将不需要登陆的接口放入配置文件里面。
/*** 允许匿名访问的地址*/
//@Autowired
//private PermitAllUrlProperties permitAllUrl;
@Resource
private IgnoreUrlsConfig ignoreUrlsConfig;
...
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
// 注解标记允许匿名访问的url
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
// permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
// 白名单
//不需要保护的资源路径允许访问
for (String url : ignoreUrlsConfig.getUrls()) {
registry.antMatchers(url).permitAll();
}
//以上是修改内容
httpSecurity...
}
1、在 SecurityConfig
类改为
去掉原有的方式的类的注入,添加IgnoreUrlsConfig
2、添加类 IgnoreUrlsConfig
/*** 用于配置白名单资源路径*/
@Component
@ConfigurationProperties(prefix = "secure.ignore")
public class IgnoreUrlsConfig {
private List<String> urls = new ArrayList<>();
public List<String> getUrls() {return urls;}
public void setUrls(List<String> urls) {this.urls = urls;}
}
3、在 application.yml
添加白名单
secure:ignore:urls: #安全路径白名单- /monitor/cache
4、测试,还是测试之前的接口
http://localhost/dev-api/monitor/cache
记住,去掉权限的注解
测试成功:
以上的两种方法,我自己还是比较喜欢第二种,想要添加或者删除不需要登陆就可以访问的路径只需要修改配置文件即可,还不会污染业务代码。