spring boot security权限控制,spring security权限管理

  spring boot security权限控制,spring security权限管理

  00-1010 1.具体用法2。SpEL3。@PreAuthorize最近有个小伙伴在微信群里问春天安全权限标注的问题:

  很多事情都是这么巧合,而宋歌最近在做的tienchin也是基于注释来处理权限问题,所以既然大家都有这个问题,那就一起来说说这个话题吧。

  当然,一些基础知识我就不说了。对于不熟悉Spring Security基本用法的朋友,可以在微信官方账号后台回复ss,有一系列原创教程。

  00-1010我们先来看看Spring安全权限注释的具体用法,如下:

  @ pre authorize( @ ss . haspermi( tien chin : channel : query )@ get mapping(/list )public table datainfo getChannelList(){ start page();list channel list=channel service . list();返回get datatable(list);}和上面类似,就是当前用户需要有tienchin:channel:query查询权限才能执行当前接口方法。

  所以要理解@预授权标注的原理,我觉得要从两个方面入手:

  首先,了解Spring提供的SpEL。其次,了解Spring Security中方法注释的处理规则。我们一个一个来看。

  00-1010 Spring表达式语言(简称SpEL)是一种功能强大的表达式语言,支持运行时对象导航图的查询和操作。它的语法类似于传统EL,但它提供了额外的功能,其中最好的是简单字符串的函数调用和模板函数。

  SpEL为Spring社区提供了一种简单高效的表达式语言,可以贯穿整个Spring产品组。这种语言的特点是基于Spring产品的需求而设计的,这是其外观的一大特点。

  虽然我们离不开Spring框架,但实际上我们离不开SpEL,因为它是如此的好用和强大,而且SpEL在整个Spring家族中也处于非常重要的地位。但是很多时候,我们对它只知道一个大概。其实如果你系统的学习过SpEL,上面Spring Security的注解其实很好理解。

  首先,让我给你一个简单的例子来平滑SpEL。

  为了省事,我将创建一个Spring Boot项目来向您演示。在创建它的时候,我不需要添加任何额外的依赖项,只需要最基本的依赖项。

  代码如下:

  String expressionStr= 1 2expression parser parser=new SpelExpressionParser();expression exp=parser . parse expression(expression str);ExpressionStr是我们定义的表达式字符串。这个字符串被ExpressionParser对象解析成一个表达式,然后就可以执行exp了。

  有两种执行方式。对于上面那个没有任何额外变量的,我们可以直接执行。直接执行的方式如下:

  object value=exp . getvalue();system . out . println(value . tostring());这个打印结果是3。

  记得之前群里有个小伙伴让我执行一个字符串表达式,我不知道该怎么做。js里有eval函数很方便,Java里我们也有SpEL,一样方便。

  但是很多时候我们要执行的表达式可能比较复杂,这个时候上面的调用方法是不够的。

  此时,我们可以为要调用的表达式设置一个上下文,然后我们将使用EvaluationContext或它的子类,如下所示:

  standardyevaluationcontext context=new standardyevaluationcontext();system . out . println(exp . getvalue(context));当然,上面的表达式不需要设置上下文。我举一个设定背景的例子。

  例如,我现在有一个用户类,如下所示:

  公共类用户{私有整数id;

   private String username; private String address; //省略 getter/setter}现在我的表达式是这样:

  

String expression = "#user.username";ExpressionParser parser = new SpelExpressionParser();Expression exp = parser.parseExpression(expression);StandardEvaluationContext ctx = new StandardEvaluationContext();User user = new User();user.setAddress("广州");user.setUsername("javaboy");user.setId(99);ctx.setVariable("user", user);String value = exp.getValue(ctx, String.class);System.out.println("value = " + value);

这个表达式就表示获取 user 对象的 username 属性。将来创建一个 user 对象,放到 StandardEvaluationContext 中,并基于此对象执行表达式,就可以打印出来想要的结果。

 

  如果我们将 user 对象设置为 rootObject,那么表达式中就不需要 user 了,如下:

  

String expression = "username";ExpressionParser parser = new SpelExpressionParser();Expression exp = parser.parseExpression(expression);StandardEvaluationContext ctx = new StandardEvaluationContext();User user = new User();user.setAddress("广州");user.setUsername("javaboy");user.setId(99);ctx.setRootObject(user);String value = exp.getValue(ctx, String.class);System.out.println("value = " + value);

表达式就一个 username 字符串,将来执行的时候,会自动从 user 中找到 username 的值并返回。

 

  当然表达式也可以是方法,例如我在 User 类中添加如下两个方法:

  

public String sayHello(Integer age) { return "hello " + username + ";age=" + age;}public String sayHello() { return "hello " + username;}

我们就可以通过表达式调用这两个方法,如下:

 

  调用有参的 sayHello:

  

String expression = "sayHello(99)";ExpressionParser parser = new SpelExpressionParser();Expression exp = parser.parseExpression(expression);StandardEvaluationContext ctx = new StandardEvaluationContext();User user = new User();user.setAddress("广州");user.setUsername("javaboy");user.setId(99);ctx.setRootObject(user);String value = exp.getValue(ctx, String.class);System.out.println("value = " + value);

就直接写方法名然后执行就行了。

 

  调用无参的 sayHello:

  

String expression = "sayHello";ExpressionParser parser = new SpelExpressionParser();Expression exp = parser.parseExpression(expression);StandardEvaluationContext ctx = new StandardEvaluationContext();User user = new User();user.setAddress("广州");user.setUsername("javaboy");user.setId(99);ctx.setRootObject(user);String value = exp.getValue(ctx, String.class);System.out.println("value = " + value);

这些就都好懂了。

 

  甚至,我们的表达式也可以涉及到 Spring 中的一个 Bean,例如我们向 Spring 中注册如下 Bean:

  

@Service("us")public class UserService { public String sayHello(String name) { return "hello " + name; }}

然后通过 SpEL 表达式来调用这个名为 us 的 bean 中的 sayHello 方法,如下:

 

  

@AutowiredBeanFactory beanFactory;@Testvoid contextLoads() { String expression = "@us.sayHello(javaboy)"; ExpressionParser parser = new SpelExpressionParser(); Expression exp = parser.parseExpression(expression); StandardEvaluationContext ctx = new StandardEvaluationContext(); ctx.setBeanResolver(new BeanFactoryResolver(beanFactory)); String value = exp.getValue(ctx, String.class); System.out.println("value = " + value);}

给配置的上下文环境设置一个 bean 解析器,这个 bean 解析器会自动跟进名字从 Spring 容器中找打响应的 bean 并执行对应的方法。

 

  当然,关于 SpEL 的玩法还有很多,我就不一一列举了。这里主要是想让小伙伴们知道,有这么个技术,方便大家理解 @PreAuthorize 注解的原理。

  

 

  

3. @PreAuthorize

接下来我们就回到 Spring Security 中来看 @PreAuthorize 注解。

 

  权限的实现方式千千万,又有各种不同的权限模型,然而归结到代码上,无非两种:

  基于 URL 地址的权限处理

  基于方法注解的权限处理

  松哥之前的 vhr 使用的是前者。

  @PreAuthorize 注解当然对应的是后者。这次做的 tienchin 项目就是后者,我们来看一个例子:

  

@PreAuthorize("@ss.hasPermi(tienchin:channel:query)")@GetMapping("/list")public TableDataInfo getChannelList() { startPage(); List<Channel> list = channelService.list(); return getDataTable(list);}

注解好说,里边的 @ss.hasPermi('tienchin:channel:query') 是啥意思呢?

 

  ss 是一个注册在 Spring 容器中的 bean,对应的类位于 org.javaboy.tienchin.framework.web.service.PermissionService 中。

  很明显,hasPermi 就是这个类中的方法。

  这个 hasPermi 方法的逻辑其实很简单:

  

public boolean hasPermi(String permission) { if (StringUtils.isEmpty(permission)) { return false; } LoginUser loginUser = SecurityUtils.getLoginUser(); if (StringUtils.isNull(loginUser) CollectionUtils.isEmpty(loginUser.getPermissions())) { return false; } return hasPermissions(loginUser.getPermissions(), permission);}private boolean hasPermissions(Set<String> permissions, String permission) { return permissions.contains(ALL_PERMISSION) permissions.contains(StringUtils.trim(permission));}

这个判断逻辑很简单,就是获取到当前登录的用户,判断当前登录用户的权限集合中是否具备当前请求所需要的权限。具体的判断逻辑没啥好说的,就是看集合中是否存在某个字符串。

 

  那么这个方法是在哪里调用的呢?

  大家知道,Spring Security 中处理权限的过滤器是 FilterSecurityInterceptor,所有的权限处理最终都会来到这个过滤器中。在这个过滤器中,将会用到各种投票器、表决器之类的工具,这里我就不细说了,之前的 Spring Security 系列教程都有详细介绍。

  在投票器中,我们可以看到专门处理 @PreAuthorize 注解的类 PreInvocationAuthorizationAdviceVoter,我们来看下他里边的核心方法:

  

@Overridepublic int vote(Authentication authentication, MethodInvocation method, Collection<ConfigAttribute> attributes) { PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes); if (preAttr == null) { return ACCESS_ABSTAIN; } return this.preAdvice.before(authentication, method, preAttr) ? ACCESS_GRANTED : ACCESS_DENIED;}

框架的源码写的就是好,你一看名字就知道他想干嘛了!这里就进入到最后一句,调用了一个 Advice 中到前置通知,来判断权限是否满足:

 

  

public boolean before(Authentication authentication, MethodInvocation mi, PreInvocationAttribute attr) { PreInvocationExpressionAttribute preAttr = (PreInvocationExpressionAttribute) attr; EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi); Expression preFilter = preAttr.getFilterExpression(); Expression preAuthorize = preAttr.getAuthorizeExpression(); if (preFilter != null) { Object filterTarget = findFilterTarget(preAttr.getFilterTarget(), ctx, mi); this.expressionHandler.filter(filterTarget, preFilter, ctx); } return (preAuthorize != null) ? ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx) : true;}

现在,当你看到这个 before 方法的时候,应该会觉得比较熟悉了吧。

 

  首先获取到 preAttr 对象,这个对象里边其实就保存着你 @PreAuthorize 注解中的内容。接下来跟进当前登录用户信息 authentication 创建一个上下文对象,此时创建出来的上下文对象中就包含了当前用户具备哪些权限。获取过滤器(我们这个项目中无)。获取到权限注解。最后执行表达式,去查看当前用户权限中是否包含请求所需要的权限。就这样,是不是很简单?

  到此这篇关于详解Spring Security中权限注解的使用的文章就介绍到这了,更多相关Spring Security权限注解内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: