1. 首页
  2. >
  3. 编程技术
  4. >
  5. Java

运用拦截器与注解,在SpringBoot中实现自定义权限认证

权限的认证框架很多,比如Shiro与SpringSecurity。今天使用拦截器与注解的方式,实现一个自定义的权限认证。

目前,系统中需要两种角色,分别是平台管理员与普通用户,他们各自拥有不同的权限。在真正开始他们的操作之前,系统要求先登录。

(1)第一次登陆系统后,之后利用Cookie与Session来标识用户。

先写好Cookie与Session的工具类备用

CookieUtil类,我这边的Cookie生成规则为时间戳+用户id+用户类型,平台管理员为0,普通用户为1

import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;  /**  * 管理cookie  */ public class CookieUtil {      public static final String COOKIE_NAME = "code";      public static String generateCookieByUserId(Integer userId, Integer type) {         Long currentTime = System.currentTimeMillis();         return currentTime + "" + userId + "" + type;     }      public static String getCookieValueFromRequest(HttpServletRequest request) {         Cookie[] cookies = request.getCookies();         if (null != cookies) {             for (Cookie cookie : cookies) {                 if (cookie.getName().equals(COOKIE_NAME)) {                     return cookie.getValue();                 }             }         }         return null;     }      public static void setCookieValueIntoResponse(HttpServletResponse response, String value) {         Cookie cookie = new Cookie(COOKIE_NAME, value);         //设置cookie的路径为任意路径         cookie.setPath("/");         response.addCookie(cookie);     } } 

SessionUtil类,服务端内部维护一个map,键为用户id,值为对应的cookie值。每次用户访问时,取出请求的cookie,遍历map,如果与cookie存在对应的键值对,则说明用户已经登陆。

因为有一个全局的容器,因此要么选用线程安全的容器,要么给非线程安全的容器的非读操作加锁。

package com.paas.boc.license.util;  import java.util.HashMap;  /**  * 管理Session  */ public class SessionUtil {      //K:用户id值 V:对应的cookie值     private static HashMap<Integer, String> map;      static {         map = new HashMap<>();     }      public static synchronized void putSession(Integer key, String value) {         map.put(key, value);     }      public static String getSession(Integer key) {         return map.get(key);     }      public static boolean containsKey(Integer key) {         return map.containsKey(key);     }      public static boolean containsValue(String value) {         return map.containsValue(value);     }      public static Integer getKeyByValue(String value) {         for (Integer key : map.keySet()) {             if (map.get(key).equals(value)) {                 return key;             }         }         return -1;     }      public static synchronized void removeSession(Integer key) {         map.remove(key);     }   } 


(2)定义角色常量类与角色注解@RoleNum

角色常量类

public class Role {      /**      * 需要管理员角色      */     public static final int ADMIN = 0;      /**      * 需要普通用户角色      */     public static final int NORMAL = 1;      /**      * 拥有管理员或普通用户角色即可      */     public static final int COMMON = 2;  } 

@RoleNum中定义int型角色变量,0代表需要管理员角色,1代表拥有普通用户角色即可,2代表两种角色即可访问。

自定义注解可以参考这篇文章使用自定义注解简易模拟Spring中的自动装配@Autowired

import java.lang.annotation.*;  /**  * 0代表需要管理员角色,1代表拥有普通用户角色即可,2代表两种角色即可访问  */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface RoleNum {     int value(); } 

这个注解加在Controller的类上或方法上,代表访问该类或方法前需要该注解代表的角色。


(3)设置拦截器,拦截前端每次对Controller的请求

package com.paas.boc.license.handler;  import com.paas.boc.license.annotation.RoleNum; import com.paas.boc.license.util.CookieUtil; import com.paas.boc.license.util.SessionUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;  import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;  public class SecurityInterceptor implements HandlerInterceptor {      @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {         //先查看服务器端是否存在cookie对应的session         String cookie = CookieUtil.getCookieValueFromRequest(request);         response.setCharacterEncoding("UTF-8");         if (!SessionUtil.containsValue(cookie)) {             response.getWriter().write("{\"code\":-1,\"msg\":\"请先登录\",\"data\":null}");             return false;         } else {             //查看该cookie对应的user_id是否拥有访问该路径的权限             Integer type = Integer.parseInt(cookie.substring(cookie.length() - 1));             if (hasPermission(handler, type)) {                 return true;             } else {                 response.getWriter().write("{\"code\":-1,\"msg\":\"权限不够\",\"data\":null}");                 return false;             }         }     }      @Override     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {      }      @Override     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {      }      private boolean hasPermission(Object handler, Integer type) {         if (handler instanceof HandlerMethod) {             HandlerMethod handlerMethod = (HandlerMethod) handler;             // 获取方法上的注解             RoleNum roleNum = handlerMethod.getMethod().getAnnotation(RoleNum.class);             // 如果方法上的注解为空 则获取类的注解             if (roleNum == null) {                 roleNum = handlerMethod.getMethod().getDeclaringClass().getAnnotation(RoleNum.class);             }             // 如果标记了注解,则判断权限             if (roleNum != null) {                 if (roleNum.value() == 2) {                     return true;                 }                 if (roleNum.value() == type) {                     return true;                 }                 return false;             }         }         return false;     } }  

(4)注入拦截器的配置类

excludePathPatterns代表排除对该文件或方法拦截

import com.paas.boc.license.handler.SecurityInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;  @Configuration public class SecurityConfig implements WebMvcConfigurer {      @Override     public void addInterceptors(InterceptorRegistry registry) {         registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/**")                 .excludePathPatterns("/*.js")                 .excludePathPatterns("/*.css")                 .excludePathPatterns("/**.html")                 .excludePathPatterns("/user/login");     } } 

(5)应用到Controller中

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.paas.boc.license.annotation.RoleNum; import com.paas.boc.license.constant.Role; import com.paas.boc.license.entity.UserInfo; import com.paas.boc.license.plugins.UserCreatorParam; import com.paas.boc.license.service.UserInfoService; import com.paas.boc.license.util.AESUtil; import com.paas.boc.license.util.CookieUtil; import com.paas.boc.license.util.R; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.Base64Utils; import org.springframework.web.bind.annotation.*;  import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import javax.validation.constraints.NotBlank;  @RestController @RequestMapping(value = "/user") public class UserController {      @Autowired     UserInfoService userInfoService;      //用户登录     @RoleNum(Role.COMMON)     @PostMapping("/login")     public R<Object> login(HttpServletResponse response, String username, String password) {         return userInfoService.login(response, username, password);     }      //用户注销     @RoleNum(Role.COMMON)     @GetMapping("/logout")     public R<Object> logout(@CookieValue(CookieUtil.COOKIE_NAME) String code) {         return userInfoService.logout(code);     }       //管理员查看所有用户     @RoleNum(Role.ADMIN)     @GetMapping("/page")     public R<IPage<UserInfo>> page(Page<UserInfo> page) {         return userInfoService.page(page);     }      //管理员创建普通用户     @RoleNum(Role.ADMIN)     @PostMapping("/create")     public R<Object> create(@Valid UserCreatorParam userCreatorParam) {         return userInfoService.create(userCreatorParam);     }      //管理员或普通用户修改自己的密码     @RoleNum(Role.COMMON)     @PostMapping("/updatePassword")     public R<Object> updatePassword(@CookieValue(CookieUtil.COOKIE_NAME) String code, String password) {         return userInfoService.updatePassword(code, password);     }  }