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

最近在使用spring security进行编码,在实际使用的过程中,遇到的问题记录一下。

背景:在一个项目中,我使用spring security进行权限控制。不仅前台控制页面和按钮的显示,还在后台对没有权限的请求进行过滤。因为每个需要进行权限控制的后台请求,都需要写相同的代码,如果一个两个还好,写多了就开始想能不能减少代码量。

先看看没有使用自定义注解的时候是怎么在后台进行权限控制的

上面的例子是一个上传文件后台请求,我们需要判断这个用户是否有上传文件的权限。在这里我定义了一个工具类AuthenticationUtil,用来判断用户有没有登录(authen),和判断用户是否具有某个权限(authenRole),并把判断结果放到Map中。如果没有权限,就不再执行后面的语句。假如我每个请求都需要判断权限,那么每个请求的开头都需要加这样5行代码,这样代码重用性就低。而且controller里面不能专注关心业务,还混入了权限的代码。

为了解决这个问题,使用自定义注解,插入一个AOP,可以很好的解决这个问题。

看一下使用AOP之后的代码

一行代码就完成之前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<? extends IReturnHandle> handle() default DefaultReturnHandle.class;
}

value是权限的名称,而另一个变量handle,是用来进行类型转换的,因为有时候url请求的返回值不是Map,而是其他类型,这时候就需要自己实现一个类型转换类来完成返回值的转换。

2.定义类型转换接口IReturnHandle

package demo.config.aop;

public interface IReturnHandle<T,K> {
    //完成类型转换,从类型T转成类型K,和java8的内置函数apply一样的作用
    K handle(T t);
}

3.定义一个默认的类型转换实现类

package demo.config.aop;

import java.util.Map;

public class DefaultReturnHandle implements IReturnHandle<Map<String, Object>, Map<String, Object>> {

    @Override
    public Map<String, Object> handle(Map<String, Object> 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<String, Object> result = new HashMap<>();
        boolean authen = false;
        Object[] args = pjp.getArgs();
        if(args!=null){
            //从方法的参数中,过滤出HttpServletRequest参数
            Optional<Object> 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<Map<String, Object>,?> 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中。

如果方法返回值是Map<String,Object>,那就这样就行,如果不是,则需要多传递一个变量,如下图

因为下载文件的请求的返回值是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<String, Object> 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<String, Object> 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;
    }
}

因为项目涉及其他代码,就不上传了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章