Spring security后台使用自定义注解进行权限控制

最近在使用spring security进行编码,在实际使用的过程中,遇到的问题记录一下。
背景:在一个项目中,我使用spring security进行权限控制。不仅前台控制页面和按钮的显示,还在后台对没有权限的请求进行过滤。因为每个需要进行权限控制的后台请求,都需要写相同的代码,如果一个两个还好,写多了就开始想能不能减少代码量。
先看看没有使用自定义注解的时候是怎么在后台进行权限控制的
Spring security后台使用自定义注解进行权限控制
文章图片

上面的例子是一个上传文件后台请求,我们需要判断这个用户是否有上传文件的权限。在这里我定义了一个工具类AuthenticationUtil,用来判断用户有没有登录(authen),和判断用户是否具有某个权限(authenRole),并把判断结果放到Map中。如果没有权限,就不再执行后面的语句。假如我每个请求都需要判断权限,那么每个请求的开头都需要加这样5行代码,这样代码重用性就低。而且controller里面不能专注关心业务,还混入了权限的代码。
为了解决这个问题,使用自定义注解,插入一个AOP,可以很好的解决这个问题。
看一下使用AOP之后的代码
Spring security后台使用自定义注解进行权限控制
文章图片

一行代码就完成之前5行代码做的事情,是不是很简洁,controller中也不用再关心权限的问题了。
这里我使用Springboot中的Aop技术,定义一个切面,使用around进行注入。
1.自定义注解AuthenticRequire

package demo.config.aop; import java.lang.annotation.*; @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AuthenticRequire { //权限的名称 String value() default ""; //没有权限的时候,通过这个接口得到返回值 Class handle() default DefaultReturnHandle.class; }

value是权限的名称,而另一个变量handle,是用来进行类型转换的,因为有时候url请求的返回值不是Map,而是其他类型,这时候就需要自己实现一个类型转换类来完成返回值的转换。
2.定义类型转换接口IReturnHandle
package demo.config.aop; public interface IReturnHandle { //完成类型转换,从类型T转成类型K,和java8的内置函数apply一样的作用 K handle(T t); }

3.定义一个默认的类型转换实现类
package demo.config.aop; import java.util.Map; public class DefaultReturnHandle implements IReturnHandle {@Override public Map handle(Map stringStringMap) { return stringStringMap; } }

4.针对AuthenticRequire注解,定义一个AOP切面,完成权限控制
package demo.config.aop; import demo.util.AuthenticationUtil; import lombok.extern.log4j.Log4j2; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Optional; @Aspect @Component @Log4j2 public class AuthenticAop { @Pointcut("@annotation(demo.config.aop.AuthenticRequire)") public void operationLog(){}@Around("operationLog()") public Object around(ProceedingJoinPoint pjp){ MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); AuthenticRequire authenticRequire = method.getAnnotation(AuthenticRequire.class); Map result = new HashMap<>(); boolean authen = false; Object[] args = pjp.getArgs(); if(args!=null){ //从方法的参数中,过滤出HttpServletRequest参数 Optional objectOptional = Arrays.stream(args).filter(arg->arg instanceof HttpServletRequest).findFirst(); if(objectOptional.isPresent()) { HttpServletRequest request = (HttpServletRequest) objectOptional.get(); //判断用户有没有登录,如果有登录,再判断有没有权限 authen = AuthenticationUtil.authen((AbstractAuthenticationToken) request.getUserPrincipal(), result); if(authen) { String roleName = authenticRequire.value(); authen = AuthenticationUtil.authenRole((AbstractAuthenticationToken) request.getUserPrincipal(), roleName, result); } } } //如果没有权限,直接返回 if(!authen){ if(authenticRequire.handle()!=null){ try { IReturnHandle handle = authenticRequire.handle().newInstance(); return handle.handle(result); } catch (Exception e) { log.error(e.getMessage(), e); } } return result; }//有相应的权限,执行方法,并返回结果 Object methodReturn = null; try { methodReturn = pjp.proceed(); } catch (Throwable throwable) { log.error(throwable.getMessage(), throwable); } return methodReturn; } }
5.最后直接使用就可以了,在有需要的地方注入注解,完成权限控制。假如用户没有对应权限,则请求不会进入到controller中。
Spring security后台使用自定义注解进行权限控制
文章图片

如果方法返回值是Map,那就这样就行,如果不是,则需要多传递一个变量,如下图
Spring security后台使用自定义注解进行权限控制
文章图片

Spring security后台使用自定义注解进行权限控制
文章图片

因为下载文件的请求的返回值是ResponseEntity,所以需要自己实现一个IReturnHandle实现类进行类型转换。
注意:注解的方法需要有一个参数HttpServletRequest参数,用来获取security权限信息。如果没有这个参数,则认为没有权限。
最后再付上权限判断的工具类
package demo.util; import org.springframework.security.authentication.AbstractAuthenticationToken; import javax.servlet.http.HttpServletRequest; import java.security.Principal; import java.util.Map; /** * 权限认证操作 */ public class AuthenticationUtil {/** * 判断用户是否登录 * @param principal * @param result * @return */ public static boolean authen(AbstractAuthenticationToken principal, Map result){ if(principal==null) { result.put("error", "用户未登陆"); return false; } return true; }/** * 判断用户是否具备某个权限 * @param principal * @param roleName * @param result * @return */ public static boolean authenRole(AbstractAuthenticationToken principal, String roleName, Map result){ if(roleName==null || roleName.isEmpty()) return true; boolean isRole = principal.getAuthorities().stream() .map(o->o.getAuthority()).filter(o->o.equalsIgnoreCase(roleName)).count()>0; if(!isRole){ if(result!=null) result.put("error", "当前用户没有操作权限"); return false; } return true; } }

【Spring security后台使用自定义注解进行权限控制】因为项目涉及其他代码,就不上传了。

    推荐阅读