Springboot自定义注解类实现拦截用户登录以及权限判断

项目权限管理实现


用户权限管理常通过拦截器拦截指定url实现,本项目中使用restful风格,可根据url拦截,但仍然不太方便。项目中的实现是,自定义一个注解,将它用在需要登录/某种权限的方法中,然后在拦截器中判断要访问的方法是否有我们自定义的注解,如果有就判断当前用户是否登录了(判断是否携带了登录之后获取到的token),从而决定是否拦截。

1. 定义用户角色枚举类

UserRole.java

项目使用枚举类定义用户角色权限,value值越小权限越高,高权限具有低权限所有功能

public enum UserRole {

    // 值越小权限越高,高权限具有低权限所有功能

    ADMIN(0), // 超级管理员

    xxx(10), 

    MANAGER(20), 

    xxx(30), 

    EVERYONE(1000); // 表示所有用户,用来接口认证

    @Getter
    private int value;

    UserRole(int value) {
        this.value = value;
    }
}

2. 自定义一个注解类

自定义一个注解类,在需要控制权限的方法前注解

UserAuthentication.java, 用来标识该接口需要进行权限控制,并且value默认为 UserRole.EVERYONE 及所有人。

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UserAuthentication {

        UserRole value() default UserRole.EVERYONE;

    }

ElementType.MeTHOD 表示该自定义注解可以用在方法上

RetentionPolicy.RUNTIME 表示该注解在代码运行时起作用

在方法上使用注解,例如:

    @PostMapping("/login")
    @UserAuthentication    // 不加为默认值 EVERYONE 
    public void doSomething() {
        
    }

    @UserAuthentication(UserRole.MANAGER) //可指定该接口访问级别,表示只有管理员可以访问

3. 自定义拦截器

AuthorizationInterceptor.java 继承自 HandlerInterceptorAdapter

HandlerInterceptorAdapter 有三个方法

1. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {    
        return true;    
}    
2. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {    
}    
3. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {    
}
  • preHandle在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制等处理;

  • postHandle在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView;

  • afterCompletion在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面),可以根据ex是否为null判断是否发生了异常,进行日志记录;

本项目为前后端分离项目,无需进行曲页面渲染,项目通过重写 preHandle()方法来进行鉴权,返回值为 false 则不放行该请求,若为true则放行。

preHandle 主要判断用户角色权限是否符合,以及是否操作的是当前租户资源,起到隔离的效果。

@Component
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {

    public static final String TOKEN_HEADER = "Authorization";


    @Autowired
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ParseException {
        

        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        // 判断接口是否需要登录/指定权限
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

            //判断是否有 @UserAuthentication注解
        UserAuthentication authAnnotation = method.getAnnotation(UserAuthentication.class);
        
            //若无 直接放行
        if (authAnnotation == null) {
            return true;
        }

            // 若有 执行认证
        String token = request.getHeader(TOKEN_HEADER);
        if (token == null && request.getCookies() != null) {
            for (Cookie cookie : request.getCookies()) {
                if (cookie.getName().equals(TOKEN_HEADER)) {
                    token = cookie.getValue();
                    break;
                }
            }
        }

        if (token != null && !token.isEmpty()) {
            // 通过token获取用户信息
            UserSession session = userService.getUserSession(token);
            if (session != null) {
                // 当前登录用户的权限 <= 指定权限则可使用该接口
                if (session.getRole() != null && session.getRole().getValue() <= authAnnotation.value().getValue()) {
                    Map pathVariables = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
                    // 获取resful url中的tenantId值 
                    String userIdStr = (String) pathVariables.get("userId");
                    if (userIdStr != null) {
                        Long userIdId = Long.valueOf((String) pathVariables.get("userId"));
                        // 两个判断:(1)用户角色判断 (2)判断当前用户要操作的资源是否是自己userId下的所属资源
                        if (session.getRole().getValue() <= UserRole.ADMIN.getValue() || session.getTenantId().equals(userId)) {
                            request.setAttribute(“USER”, session);  // 如果是将查询到的用户session放入request中,后续参数解析器解析,方便使用
                            return true;
                        }
                    } else {
                        request.setAttribute(“USER“, session);
                        return true;
                    }
                }
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);  // 权限不符
                return false;
            }
        }

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); //未授权
        return false;
    }

(4)自定义参数解析类

编写 UserSessionArgumentResolver.java 继承自 HandlerMethodArgumentResolver

HandlerMethodArgumentResolver = HandlerMethod + Argument(参数) + Resolver(解析器), 其实就是HandlerMethod方法的解析器, 将 HttpServletRequest(header + body 中的内容)解析为HandlerMethod方法的参数。

这里的主要作用是将preHandle()那里设置的UserSession对象解析为controller的的形参,方便使用。

public class UserSessionArgumentResolver implements HandlerMethodArgumentResolver {

    // 用于判断是否需要处理该参数分解,返回true表示需要,并且会调用下面的resolveArgument方法
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().isAssignableFrom(UserSession.class);
    }

    /**
     * @return Object 返回controller方法上的形参对象,这里返回的是UserSession.class对象。
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        return webRequest.getAttribute(“USER”, RequestAttributes.SCOPE_REQUEST);
    }
    
}

(5)配置拦截器及注册参数解析器

springboot 配置很方便,编写AuthorizationConfig.java 类, 继承WebMvcConfigurerAdapter类。

@Configuration
public class AuthorizationConfig implements WebMvcConfigurer {

    @Autowired
    private AuthorizationInterceptor authorizationInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authorizationInterceptor);  //拦截所有请求
    }

    @Override // 自定义参数解析
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new UserSessionArgumentResolver());
    }

}

(6) 使用
拿退出方法来说,方法上使用注解,value值为空指默认,即所有人都可以使用此接口,此时session对象就是由参数解析器解析得到的,并不是前端直接传过来的

    @PostMapping("/logout")
    @UserAuthentication
    public ResponseEntity<BaseResponse> logout(UserSession session, HttpServletResponse response) {
        userService.logout(session);
        return ResponseEntity.ok(BaseResponse.ok());
    }

要加其他权限更改注解的value值即可

    @PostMapping("/login")
    @Transactional    
    @UserAuthentication(UserRole.MANAGER)
    public void doSomething() {
        
    }

项目权限部分的实现大致就是这样。

参考:

  1. 自定义注解类拦截用户登录
  2. 自定义参数解析器 HandlerMethodArgumentResolver

你可能感兴趣的:(javaweb,Spring)