运用拦截器与注解,在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); } }