权限的认证框架很多,比如Shiro与SpringSecurity。今天使用拦截器与注解的方式,实现一个自定义的权限认证。
目前,系统中需要两种角色,分别是平台管理员与普通用户,他们各自拥有不同的权限。在真正开始他们的操作之前,系统要求先登录。
(1)第一次登陆系统后,之后利用Cookie与Session来标识用户。Cookie与Session的区别可以参考这篇文章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);
response.addCookie(cookie);
}
}
SessionUtil类,服务端内部维护一个map,键为用户id,值为对应的cookie值。每次用户访问时,取出请求的cookie,遍历map,如果与cookie存在对应的键值对,则说明用户已经登陆。
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
/**
* 管理Session
*/
public class SessionUtil {
//K:用户id值 V:对应的cookie值
private static HashMap map;
static {
map = new HashMap<>();
}
public static 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 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