注解实现请求方法的登录控制

前言

之前一直使用的是,拦截器来统一验证当前用户是否登录,通过验证cookie或者session里面的是否存在已经登录标识来完成登录逻辑判断。但是会发现,这个很麻烦,而且有很多配置需要配置,例如免验证URL等等配置,无法实现可拔插式方法级别的控制。

public class RequestInterceptor extends HandlerInterceptorAdapter {
public String[] allowUrls;//配置不拦截的资源,所以在代码里面来排除.
public void setAllowUrls(String[] allowUrls) {
this.allowUrls = allowUrls;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// TODO Auto-generated method stub
request.setCharacterEncoding("UTF8");
HttpSession session=request.getSession();//获取登录的SESSION
String sessionid=request.getSession().getId();//获取登录的SESSIONID
String requestPath=request.getServletPath();//获取客户请求页面
//先过滤掉不需要判断SESSION的请求
for(String url : allowUrls) {
if(requestPath.contains(url)) {
return true;
}
}
Object attribute = request.getSession().getAttribute("sys_user");
if( attribute == null ){
response.sendRedirect("/index.jsp");
}
return true;
}

大体上是这样的,通过allowUrls来控制免登录url(上面的代码其实可以使用配置文件的方式来配置allowUrls的值,可以不通过setAllowUrls的方式来赋值,但是为了方面扩展就加入了。)

这里会面临一个问题,那就是如果网站网页多的话,那么allowUrls的值会变得很庞大,可能会缺漏。所以下面讲解本人用到的解决方式—-注解spring配置方式(跟数组形式没有什么区别)

spring 配置方式

path 对所有的请求拦截使用/**,对某个模块下的请求拦截使用:/myPath/*
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.kingge.oa.user.LoginInterceptor" />
</mvc:interceptor>
或者
<!-- 拦截是否登录
<mvc:interceptor>
需拦截的地址
二级目录
<mvc:mapping path="/*/*"/>
<bean class="com.jk.ssm.interceptor.RequestInterceptor" >
<property name="allowUrls"> //回去调用拦截器的 setAllowUrls 方法
<list>
如果请求中包含以下路径,则不进行拦截
<value>/account/login.html</value>
<value>/captcha/image.html</value>
<value>/register/register.html</value>
<value>/error/400.html</value>
<value>/error/404.html</value>
<value>/error/500.html</value>
</list>
</property>
</bean>
</mvc:interceptor>

使用注解

关于注解

官方说辞:JDK5开始,java增加了对元数据(MetaData)的支持,怎么支持?答:通过Annotation(注解)来实现。Annotation提供了为程序元素设置元数据的方法。元数据:描述数据的数据。


个人理解:首先什么是元数据,元数据就是对一类事物的统称,他不仅限于某个事物的描述。例如我们有ABC三个系统,分别使用oracle,mysql,db2,都有登录功能,他们的用户表字段名称是不一样的。那么有个需求,我想把A系统的用户数据pour到B系统中,那么进行映射操作?这个时候就需要一个描述用户数据的一个统一标识(元数据)这样我们就可以先把,A系统数据映射到元数据,然后再从元数据取数据映射到B系统中。


粗俗的理解,元数据就是一个类的属性,但是他所具备的职能的而应用范围,跟真正意义上类的属性数不一样的。传统的类的属性他只描述这个类,元数据可以描述多个具有共性的类。


再举个例子,我们现在常用的数据中心(DC)就是使用了元数据来作为数据传输的媒介。

DC流程.png


元数据作用::Annotation就像代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取。读取到了程序元素的元数据,就可以执行相应的处理。通过注解,程序开发人员可以在不改变原有逻辑的情况下,在源代码文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过解析这些注解获取到这些补充信息,从而进行验证或者进行部署等。


到java8为止一共提供了五个 注解


五个基本原数据.png


  • unchecked异常:运行时异常。是RuntimeException的子类,不需要在代码中显式地捕获unchecked异常做处理。Java异常
  • @SafeVarargs (java7新增):java7的“堆污染”警告与@SafeVarargs
    堆污染:把一个不带泛型的对象赋给一个带泛型的变量是,就会发生堆污染。
    例如:下面代码引起堆污染,会给出警告
    List l2 = new ArrayList<Number>();
    List<String> ls = l2;
    3中方式去掉这个警告

3种方式去掉这个警告:
使用注解@SafeVarargs修饰引发该警告的方法或构造器。
使用@SuppressWarnings(“unchecked”) 修饰。
使用编译器参数命令:-Xlint:varargs

  • @Functionlnterface (java8新增):修饰函数式接口
    使用该注解修饰的接口必须是函数式接口,不然编译会出错。那么什么是函数式接口?答:如果接口中只有一个抽象方法(可以包含多个默认方法或static方法),就是函数式接口。

五个基本元注解

元注解:描述注解的注解(概念跟元数据类似)。

java提供了6个元注解(Meta Annotation),在java.lang.annotation中。其中5个用于修饰其他的Annonation定义。而@Repeatable专门用于定义Java8新增的重复注解。所以要定义注解必须使用到5个元注解来定义( 五个注解用法 详情百度 )

  • @Inherited
  • @Documented
  • @Retention(英文:保留)
  • @Target ( 目标)

自定义注解

参见下面,例子或者白度,具体就不阐述了。

使用注解解决登录问题

定义一个枚举类

作用: 是否进行验证权限(因为后期可能会增加权限判断注解,而且是否登录也可以说是权限判断的一种,所以这里的枚举类的作用就是保存是否进行权限判断信息)


public enum Action
{
Normal("0","执行权限验证"), Skip("1", "跳过权限验证");
private final String key;
private final String desc;
private Action(String key, String desc)
{
this.key = key;
this.desc = desc;
}
//省略get set方法

定义登录和权限注解

Login属性是action ,属性类型是Action(上面的枚举类)

@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Login
{
Action action() default Action.Normal;
}

@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Permission
{
String value() default ""; // 这里我是保存一个权限代码,例如赋值为4000,表示当前用户的必须具备4000的权限才能够访问方法
Action action() default Action.Normal;
}

拦截器

public class LoginInterceptor extends HandlerInterceptorAdapter
{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
if(handler instanceof HandlerMethod){ //是否为请求方法
HandlerMethod handlerMethod = (HandlerMethod) handler;
Login login = handlerMethod.getMethodAnnotation(Login.class);//当前请求方法是否添加了Login注解
if( login != null && "0".equals(login.action().getKey()) ){//判断属性的值是否是0-表示需要进行登录验证
Object attribute = request.getSession().getAttribute("sys_user");
if( attribute == null ){
response.sendRedirect("/index.jsp");
}
}
return true;
}
return true;
}
}

在spring中配置拦截器

<mvc:interceptors>
<bean class="com.kingge.oa.user.LoginInterceptor"></bean>
</mvc:interceptors>

给请求方法添加权限控制

@Login(action=Action.Skip) //不需要进行登录校验
@Permission(value="4000",action=Action.normal)//需要进行权限号为4000的权限校验
@RequestMapping("/list")
public String list(Model model,HttpServletRequest request)
{
request.getSession().setAttribute("sys_user", "denglule");
List<User> userList = userService.findAllObjects();
System.out.println( userList );
model.addAttribute("userList",userList );
return "list";
}
@Login(action=Action.Normal)//添加操作,需要校验是否登录
@RequestMapping(value="/add", method=RequestMethod.POST)
public String add( User user )
{
System.out.println( user );
userService.insert(user);
return "forward:/user/list";
}
如果你感觉文章对你又些许感悟,你可以支持我!!
-------------本文结束感谢您的阅读-------------