前言
提示:拦截器和aop都能实现像shiro和springsecurity的权限控制
一、定义一个切面
@Aspect
@Component
public class PermissionAdvice {
/**
* 定义切点方法,普通方法标记该注解的都会被切
*/
@Pointcut("@annotation(com.shidai.petroleum.anno.PermissionAnnotation)")
public void permissionCheck(){}
/**
*获取token,并检验是否有该方法的权限
* @param joinPoint 切点的方法入参
* @return
* @throws Throwable
*/
@Around("permissionCheck()")
public Object permissionCheckSecond(ProceedingJoinPoint joinPoint) throws Throwable {
//获取请求对象中的token+jwtutil取出该token中权限list,将token中的权限取出,权限列表中如果有该注解值就可以访问该注解
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
assert sra != null;
HttpServletRequest request = sra.getRequest();
String token = request.getHeader("token");
//如果token不为空,然后有效的话,就检测是否满足该切点的权限
if(token!=null && JwtUtil.verify(token)){
//获取了权限列表
String[] permissions = JwtUtil.getPermissions(token);
//获取被代理类名+方法名
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
//获取方法的参数类型-》获取到方法上的注解信息
Class<?> classTarget = joinPoint.getTarget().getClass();
Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
Method declaredMethod = classTarget.getDeclaredMethod(methodName, parameterTypes);
PermissionAnnotation annotation = declaredMethod.getAnnotation(PermissionAnnotation.class);
//该注解所需要的权限
String needpermission = annotation.value();
System.out.println(className+":"+methodName+":"+needpermission);
for (String permission : permissions) {
if(permission.equals(needpermission)){
return joinPoint.proceed();
}
}
}
HttpServletResponse response = sra.getResponse();
response.setStatus(401);
response.getOutputStream().write("token有误或者你没有该权限".getBytes(StandardCharsets.UTF_8));
//设置响应格式,防止中文乱码
response.setContentType("application/json;charset=utf-8");
return null;
}
}
二、定义自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionAnnotation {
String value();//这个是存储权限的变量
}
三、选择一个被切点,配置自定义注解
@RestController
@RequestMapping("/order")
public class OrderVO {
@Autowired
OrderService orderService;
//该商品的权限只能是货主的add,才能访问
@PermissionAnnotation("huozu:add")
@PostMapping("/")
public InfoResult<Integer> saveOrder(@RequestBody OrderPO orderPO){
return orderService.saveOrder(orderPO);
}
/**
* 货主只能看到自己的订单,而承运商可以看到所有人的订单
* @param pageNum
* @param pageSize
* @return
*/
//只有货主的查找权限才行
@PermissionAnnotation("huozu:find")
@GetMapping("/{num}/{size}")
public InfoResult<PageInfo<OrderPO>> orderPage(@PathVariable("num") int pageNum,@PathVariable("size") int pageSize){
PageHelper.startPage(pageNum,pageSize);
PageInfo<OrderPO> orderPages = new PageInfo<>(orderService.orderPage());
return new InfoResult<>(200,orderPages,"查询分页数据成功");
}
}
四、登录服务中将权限和角色存进token中
给浏览器返回相应头token
@Service
public class UserServiceImpl implements UserService {
@Resource
UserMapper userMapper;
@Resource
RoleMapper roleMapper;
@Resource
PermissionMapper permissionMapper;
@Override
public Integer saveUser(UserPO userPO) {
return userMapper.saveUser(userPO);
}
@Override
public InfoResult<UserPO> loginUser(UserPO userPO) {
//先去数据库根据账号密码,查到是否有用户对象
UserPO user = userMapper.loginUser(userPO.getUsername(), userPO.getPassword());
if(user==null){
return new InfoResult<UserPO>(401,userPO,"账号或密码错误");
}else{
//登录成功,先去查询该账户对应的角色
RolePO rolePO = roleMapper.findRoleByUid(user.getId());
String role = rolePO.getName();
//获取权限
List<PermissionPO> permissionPOS = permissionMapper.permissionList(rolePO.getId());
List<String> permissions = new ArrayList<>();
for (PermissionPO permissionPO : permissionPOS) {
permissions.add(permissionPO.getName());
}
String[] permission = permissions.toArray(new String[permissions.size()]);
//创建token
String token = JwtUtil.createToken(user.getId(), permission, role);
user.setToken(token);
//将token设置到请求头中
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
assert sra != null;
HttpServletResponse response = sra.getResponse();
response.setHeader("token",token);
//前后端分离需要暴露请求头
response.setHeader("Access-Control-Expose-Headers", "token");
return new InfoResult(200,user,"登录成功");
}
}
}
五、JwtUtil工具类封装生成token方法
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Arrays;
import java.util.Date;
/**
* @Author:roger
* @Date:2022/2/15 11:18:06
* @Description:
*/
public class JwtUtil {
private static final String SECRET = "shidai"; // 秘钥
private static final String ISSUSER = "java"; // 签发人
private static final long EXPIRES = 30*60*1000; // token的过期时间 30min
// 用来创建jwt静态方法
public static String createToken(int uid,String[] permissions,String role){
//加密算法
Algorithm algorithm = Algorithm.HMAC256(SECRET);
Date now = new Date();
Date expires = new Date(now.getTime() + EXPIRES);
String jwt = JWT.create()
.withIssuer(ISSUSER) //签发人
.withIssuedAt(now) //签发时间
.withExpiresAt(expires) //过期时间
.withArrayClaim("permission",permissions) //加入权限
.withClaim("role",role) //加入角色
.withClaim("uid",uid) //加入用户名
.sign(algorithm);
return jwt;
}
// 校验jwt是否合法
public static boolean verify(String token) {
try {
// 签名算法
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUSER).build();
verifier.verify(token);
return true;
} catch (Exception ex) {
//ex.printStackTrace();
return false;
}
}
// 从token中获取用户名
public static int getUid(String token) {
try {
return JWT.decode(token).getClaim("uid").asInt();
} catch (Exception ex) {
//ex.printStackTrace();
return 0;
}
}
// 从token中获取用户的权限数组
public static String[] getPermissions(String token) {
try {
return JWT.decode(token).getClaim("permission").asArray(String.class);
} catch (Exception ex) {
//ex.printStackTrace();
return null;
}
}
//从token中获取用户的角色
public static String getRole(String token){
try {
return JWT.decode(token).getClaim("role").asString();
} catch (Exception ex) {
//ex.printStackTrace();
return null;
}
}
// 判断token是否过期,true-过期,false-没过期
public static boolean isExpires(String token) {
Date now = new Date();
Date expire = JWT.decode(token).getExpiresAt();
if (now.after(expire)) {
return true;
}
return false;
}
public static void main(String[] args) throws InterruptedException {
String[] permissions = {"vip:add","vip:find"};
String token=createToken(1,permissions,"vip");
System.out.println(token);
Thread.sleep(500);
System.out.println(verify(token));
System.out.println(verify("aaa.b.bc"));
System.out.println(Arrays.toString(getPermissions(token)));
System.out.println(getRole(token));
System.out.println(getUid(token));
System.out.println(isExpires(token));
}
}
总结
整个流程思路如下
先登录,登录成功就查用户对应的角色权限,通过jwtutils封装进token中,然后设置到相应头中,浏览器接收到后,之后的请求都会携带token请求,当需要访问带权限的请求时,通过aop代理进行增强,在方法执行前,先取出token判断是否有该权限,如果有,就放行,否则不放行