思路: 定义切面,对标记有@Role(userRole)注解的方法(Api)进行拦截,验证当前登录的用户是否有该userRole角色,如果没有,则提示没有权限。否则放行。
特点:
(优)简单, 稳健, 在满足需求的情况下非常适合用来做权限控制;
(劣)灵活度不高,与代码耦合太高,权限不可以动态配置。
# 定义角色枚举类型User_Role
package com.futao.springmvcdemo.model.enums; import com.futao.springmvcdemo.foundation.LogicException; import com.futao.springmvcdemo.model.system.ErrorMessage; /** * @author futao * Created on 2018/9/19-14:41. * 角色 */ public enum User_Role { /** * 普通登录用户 */ Normal(1, "普通用户"), /** * 管理员用户 */ Admin(2, "管理员"); private int type; private String description; User_Role(int type, String description) { this.type = type; this.description = description; } public int getType() { return type; } public String getDescription() { return description; } /** * 通过type来查询枚举值 * * @param roleType * @return */ public static User_Role value(int roleType) { if (roleType == User_Role.Normal.getType()) { return User_Role.Normal; } else if (roleType == User_Role.Admin.getType()) { return User_Role.Admin; } throw LogicException.le(ErrorMessage.ROLE_NOT_EXIST); } }
# 定义标记注解@Role
@LoginUser该注解要求用户必须登录,具体实现可以参考前面的文章
package com.futao.springmvcdemo.annotation; import com.futao.springmvcdemo.model.enums.User_Role; import java.lang.annotation.*; /** * @author futao * Created on 2018-12-13. * 用户权限控制 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @LoginUser public @interface Role { /** * 要求的用户角色 * * @return */ User_Role[] value() default User_Role.Normal; }
# 用户实体添加角色字段
该字段的值取自枚举类User_Role中的type
public class User extends BaseEntity /** * 角色 * {@link com.futao.springmvcdemo.model.enums.User_Role} */ private int role;
# 定义拦截切面RoleInterceptor
该切面对标记了@Role()注解的类或者方法进行拦截,并在执行方法前检查用户的角色权限
package com.futao.springmvcdemo.annotation.impl.aop; import com.futao.springmvcdemo.annotation.Role; import com.futao.springmvcdemo.foundation.LogicException; import com.futao.springmvcdemo.model.entity.User; import com.futao.springmvcdemo.model.enums.User_Role; import com.futao.springmvcdemo.model.system.ErrorMessage; import com.futao.springmvcdemo.service.UserService; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Arrays; /** * @author futao * Created on 2018-12-13. */ @Order(1) @Aspect @Component public class RoleInterceptor { @Resource private UserService userService; /** * 切入点 * * @Pointcut("@within(org.springframework.web.bind.annotation.RestController) && @annotation(org.springframework.web.bind.annotation.RestController)") * @Pointcut("execution(public * com.futao.springmvcdemo.controller.*.*(..))") * @Pointcut("execution(public * com.futao.springmvcdemo.controller..* (..)) && @annotation(org.springframework.web.bind.annotation.RestController)") */ @Pointcut("@annotation(com.futao.springmvcdemo.annotation.Role)") public void pointCut() { } @Before("pointCut()") public void checkUserRole(JoinPoint point) { User user = userService.currentUser(); //未登录 if (user == null) { throw LogicException.le(ErrorMessage.NOT_LOGIN); } //注解打在方法上 Role annotation = ((MethodSignature) point.getSignature()).getMethod().getAnnotation(Role.class); if (annotation == null) { //注解打在类上 annotation = (Role) point.getSignature().getDeclaringType().getAnnotation(Role.class); } if (annotation != null) { if (!Arrays.asList(annotation.value()).contains(User_Role.value(user.getRole()))) { throw LogicException.le(ErrorMessage.ROLE_NOT_ALLOW); } } } // @Around("pointCut()") // public void around(ProceedingJoinPoint point) throws Throwable { // System.out.println(StringUtils.repeat("-", 100)); // point.proceed(); // } }
这样就OK了
# 使用
在需要进行权限拦截的接口上打上注解,并标记需要的权限
/** * 获取当前在线人数 * 需要角色为admin=2的用户才可以访问该接口 * @return */ @Role(User_Role.Admin) @GetMapping("onlinePeopleQuantity") public SingleValueResult onlinePeopleQuantity() { return new SingleValueResult(onlineHttpSessionListener.getOnlinePeopleQuantity().get()); }
# 测试
# tips
使用HandlerInterceptor也可以实现同样的效果。
HandlerInterceptor拦截的是请求地址,所以针对请求地址做一些验证、预处理等操作比较合适。当你需要统计请求的响应时间时MethodInterceptor将不太容易做到,因为它可能跨越很多方法或者只涉及到已经定义好的方法中一部分代码。MethodInterceptor利用的是AOP的实现机制。在对一些普通的方法上的拦截HandlerInterceptor就无能为力了,这时候只能利用AOP的MethodInterceptor。