自定义注解实现权限验证
第一步,定义注解——相当于定义标记;
@Target
注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。以下是ElementType
全部的作用域,可根据项目需求进行选择。
/** 类,接口(包括注解类型)或枚举的声明 /
TYPE,
/* 属性的声明 /
FIELD,
/* 方法的声明 /
METHOD,
/* 方法形式参数声明 /
PARAMETER,
/* 构造方法的声明 /
CONSTRUCTOR,
/* 局部变量声明 /
LOCAL_VARIABLE,
/* 注解类型声明 /
ANNOTATION_TYPE,
/* 包的声明 */
PACKAGE
@Retention
注解用来修饰自定义注解的生命力。注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。
/**
* Annotations are to be discarded by the compiler.
* (注解将被编译器忽略掉)
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
* (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME,
-
@Documented
注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。 -
@Inherited
注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited
注解只对那些@Target
被定义为ElementType.TYPE
的自定义注解起作用。
通过以上这些元注解,博主这里写了一个自定义注解
//类和方法上生效
@Target({ElementType.METHOD,ElementType.TYPE})
//运行时有效
@Retention(RetentionPolicy.RUNTIME)
//在javadoc文档中使用(这个不重要)
@Documented
public @interface XXSecurity {
long [] value() default {};
}
拦截器的配置在我的博文Interceptor与Shiro小结有说明,这里不再重复介绍
第二步,配置注解——把标记打在需要用到的程序代码中
博主将该自定义注解配置到拦截器中,实现一个通过注解进行用户权限验证的功能。
这里用三个test方法来进行自定义注解权限的验证。
注解使用最常见的方法**@自定义注解名(这个注解中的属性名=需要设置的值)**
@Controller
@RequestMapping("/user")
@ResponseBody
public class UserController {
private final Logger LOG = LoggerFactory.getLogger(this.getClass());
@Resource
private UserDAO userDAO;
// 进行加密,运行程序时,可直接返回userId来代替加密环节
@GetMapping(value = "/test1")
public String test1(Long userId){
return JWTUtil.sign(userId,86400000);
// return String.valueOf(userid);
}
//这个方法只能有RoleConstant.One和RoleConstant.Two的人可一访问
@RequestMapping(value = "test2", method = RequestMethod.GET)
@XXSecurity({RoleConstant.One,RoleConstant.Two})
public String test2(@RequestHeader("token")String token) {
Long userId = JWTUtil.unsign(token,Long.class);
UserEntity userEntity = userService.getUserById(userId);
return "OK";
}
//这个方法只能有RoleConstant.Four的人可一访问
@RequestMapping(value = "test3", method = RequestMethod.GET)
@XXSecurity({RoleConstant.Four})
public String test3(@RequestHeader("token")String token) {
Long userId = JWTUtil.unsign(token,Long.class);
UserEntity userEntity = userService.getUserById(userId);
return "Ok";
}
}
然后写一个RoleConstant
类,将权限对应的值放在其中,这里仅作简单的演示。
public class RoleConstant {
public static final long One = 1l;
public static final long Two = 2l;
public static final long Three = 3l;
public static final long Four = 4l;
}
然后到配置中,设置test1不需要被拦截,test2和test3需要被拦截
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截以下请求,/**即拦截所有 -->
<mvc:mapping path="/**"/>
<!-- 不拦截以下强求 -->
<mvc:exclude-mapping path="/user/test1" />
<bean class="com.cloneZjrt.util.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作。
这里,我将注解的验证放在拦截器中,对掩护权限进行拦截
重写HandlerInterceptor
,对权限进行解析和验证
public class MyInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
//请求发送到Controller之前调用
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
/**
* 配置跨域
*/
httpServletResponse.setHeader("Access-Control-Allow-Origin","*");//允许所有域名访问
httpServletResponse.setHeader("Access-Control-Allow-Methods", "*");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, token");
httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
/**
* 根据自己的业务编写代买
* 以验证请求头为例
*/
if(httpServletRequest.getHeader("token") == null){
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
HashMap<String,String> returnJson = new HashMap<>();
returnJson.put("status","415");
returnJson.put("message","拒绝");
returnJson.put("data",null);
PrintWriter out;
out = httpServletResponse.getWriter();
out.append(returnJson.toString());
return false;
}
String token = httpServletRequest.getHeader("token");
if (token == null) {
//这里应该用自定义异常
httpServletResponse.getWriter().write("未登录,请重新登录后操作");
return false;
} else {
//这里的JWTUtil工具类仅作简单的加密和解密,不做具体介绍
//长寿运行程序时,可直接使header中的token等于userId,跳过解密这一环节
//这一块代码过于繁琐,也可写在单独的方法里。
Long userId = JWTUtil.unsign(token,Long.class);
if (userId == null || userService.getUserById(userId) == null) {
System.out.println("非法用户");
}
//获取请求用户的权限列表,这里是权限的ID列表,对应RoleConstant类中的属性
List<Long> roleList = userService.getRoleIdByUserId(userId);
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
XXSecurity eMethod = handlerMethod.getMethodAnnotation(XXSecurity .class);
if (eMethod != null) {
//验证用户是否有方法注解上所需要的权限
long[] value = eMethod.value();
if (value.length == 0) {
return true;
}
//注解中的权限包含该用户的权限就直接放心
for (long i : value) {
if (roleList.contains(i)) {
return true;
}
}
System.out.println("权限不足");
return false;
} else {
//拿到类上的注解,和上面同理
XXSecurity eType = handlerMethod.getMethod().getDeclaringClass().getAnnotation(XXSecurity .class);
if (eType != null) {
long[] value = eType.value();
if (value.length == 0) {
return true;
}
for (long i : value) {
if (roleList.contains(i)) {
return true;
}
}
System.out.println("权限不足");
return false;
} else {
return true;
}
}
}
//走到这里说明在方法和类上都没有权限注解,直接放行
return true;
}
}
//请求发送到Controller之后调用
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
//完成请求的处理的回调方法
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
- 第四步,验证
在数据库中,用户ID为“1”的用户只有“One”“Two”“Three”三个权限
1.访问:http://localhost:8888/cloneZjrt/user/test?userId=1
打印:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1ODMxMjkzOTYxMTMsInBheWxvYWQiOiIxIn0.2cky7JbAFvxD8elZvAgAa5fFKgOupxrxa9X9vojdtkg
2.访问:http://localhost:8888/cloneZjrt/user/test2
headers中设置
[{“key”:“Content-Type”,“value”:“application/json”,“description”:"",“enabled”:true},{“key”:“token”,“value”:“eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1ODMxMjkzOTYxMTMsInBheWxvYWQiOiIxIn0.2cky7JbAFvxD8elZvAgAa5fFKgOupxrxa9X9vojdtkg”,“description”:"",“enabled”:true}]
打印:OK
3.访问:http://localhost:8888/cloneZjrt/user/test3
headers中设置
[{“key”:“Content-Type”,“value”:“application/json”,“description”:"",“enabled”:true},{“key”:“token”,“value”:“eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1ODMxMjkzOTYxMTMsInBheWxvYWQiOiIxIn0.2cky7JbAFvxD8elZvAgAa5fFKgOupxrxa9X9vojdtkg”,“description”:"",“enabled”:true}]
打印:权限不足
当然,也可以在测试类中,通过反射的方法进行验证
@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring配置文件
@WebAppConfiguration
@ContextConfiguration({"classpath*:applicationContext.xml"})
public class TestDAO {
public void testOne() throws Exception {
try {
//获取UserController对象
Class userController = Class.forName("com.cloneZjrt.controller.UserController");
//这里形参为String.class,因为我这个方法返回的是String
Method userMethod = userController.getMethod("test2", String.class);
if(userMethod.isAnnotationPresent(XXSecurity.class)){
System.out.println("UserController类上配置了XXSecurity注解!");
//获取该元素上指定类型的注解
XXSecurity xxSecurity = userMethod.getAnnotation(XXSecurity.class);
System.out.println("RoleConstant: " + xxSecurity.value());
//在注解中,我设置的value属性是是一个数组,遍历一下全部打印
for(Long l : xxSecurity.value()){
System.out.println(l);
}
}else{
System.out.println("UserController类上没有配置XXSecurity注解!");
}
} catch (LogicException e) {
e.printStackTrace();
}
}
}
打印
UserController类上配置了XXSecurity注解!
RoleConstant: [J@3163987e
1
2
验证通过
参考博文:SSM项目服务端鉴权(自定义注解实现)