基于Spring AOP实现的权限控制
1.AOP简介
AOP,面向切面编程,往往被定义为促使软件系统实现关注点的分离的技术。系统是由许多不同的组件所组成的,每一个组件负责一块特定的功能。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件
下面介绍一下AOP相关的术语:
通知: 通知定义了切面是什么以及何时使用的概念。Spring 切面可以应用5种类型的通知:
前置通知(Before):在目标方法被调用之前调用通知功能。
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
返回通知(After-returning):在目标方法成功执行之后调用通知。
异常通知(After-throwing):在目标方法抛出异常后调用通知。
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
连接点:是在应用执行过程中能够插入切面的一个点。
切点: 切点定义了切面在何处要织入的一个或者多个连接点。
切面:是通知和切点的结合。通知和切点共同定义了切面的全部内容。
引入:引入允许我们向现有类添加新方法或属性。
织入:是把切面应用到目标对象,并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期中有多个点可以进行织入:
编译期: 在目标类编译时,切面被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
类加载期:切面在目标加载到JVM时被织入。这种方式需要特殊的类加载器(class loader)它可以在目标类被引入应用之前增强该目标类的字节码。
运行期: 切面在应用运行到某个时刻时被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面的。
2.编写Spring AOP的aspect类
package com.sxk.aop;
import com.sxk.entity.Token;
import com.sxk.service.AuthorityService;
import com.sxk.service.TokenService;
import org.apache.commons.fileupload.RequestContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
/**
* Created by stonegeek on 2017/3/4.
*/
@Aspect
public class AroundTest {
@Autowired
TokenService tokenService;
@Autowired
AuthorityService authorityService;
@Pointcut("execution(public * com.sxk.controller.testcontroller.*(..))")
public void testaround(){}
@Around("testaround()")
public Object test(ProceedingJoinPoint jp) throws Throwable{
System.out.println("开始验证Token权限。。。。");
HttpServletRequest request= ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
String tokenName=this.getToken(request);
String url=this.getURL(request);
String method=this.getMethod(request);
System.out.println("Token为:"+tokenName);
System.out.println("URL为:"+url);
System.out.println("Method为:"+method);
Token token=tokenService.findByTokenName(tokenName);
if(token==null)
return "{'result':'Token not exsits'}";
Timestamp creatTime=token.getCreateTime();
int len=token.getEffectiveTime();
Timestamp timeNow=new Timestamp(new Date().getTime());
List<String> allApi=null;
if((creatTime.getTime()+len*1000*60)>=timeNow.getTime()){
allApi=authorityService.getAPI(tokenName);
System.out.println(allApi);
if(allApi!=null&&allApi.contains(url)){
System.out.println("Token验证通过!!!");
return jp.proceed();
}else {
System.out.println("验证失败!!!");
return "{'result':'No authority for this API!!'}";
}
}else {
System.out.println("The Token is Timeout");
return "{'result':'The Token is Timeout!!'}";
}
}
public String getToken(HttpServletRequest request){
return request.getHeader("token");
}
public String getURL(HttpServletRequest request){
return request.getRequestURI();
}
public String getMethod(HttpServletRequest request){
return request.getMethod();
}
}
代码详解:
在管理系统调用后台接口之前,会进行拦截,拦截方法是通过((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest()拿到request,在这个request的headers中会有一个key-value(前后台约定的),key为token,value为tokenName,此tokenName为UUID,然后通过这个tokenName从数据库中查询是否有此token,没有的话将返回token not exists字样,有的话 则进行判断此token是否过期,token表中存有token创建时间,还有有效时间,token过期的话,返回the token is timeout!,token有效的话再通过tokenName查询权限表此token的所有允许的API,再对此request和API进行比对,如果包含则允许请求资源,否则返回no authority for this api。
3.登录业务实现类
package com.sxk.service.impl;
import com.sxk.dao.TokenMapper;
import com.sxk.dao.UserMapper;
import com.sxk.service.Login_Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.Date;
import java.util.UUID;
import com.sxk.entity.User;
import com.sxk.entity.Token;
/**
* Created by stonegeek on 2017/3/5.
*/
@Service("login_ServiceImpl")
public class Login_ServiceImpl implements Login_Service {
int effectTime=200;
@Autowired
private UserMapper userMapper;
@Autowired
private TokenMapper tokenMapper;
@Override
public String login(String userName, String password) {
String json=null;
System.out.println("【认证中心】得到用户名和密码分别为:"+userName+","+password);
User user=userMapper.login(userName,password);
System.out.println(user);
Token token;
if (user!=null) {
System.out.println("【认证中心】用户名、密码验证通过!");
UUID tokenName = UUID.randomUUID();
token = new Token();
token.setLoginName(userName);
token.setTokenName(tokenName.toString());
Timestamp timeNow = new Timestamp(new Date().getTime());
token.setCreateTime(timeNow);
token.setEffectiveTime(effectTime);
System.out.println("别再出错了");
if (tokenMapper.findByLoginName(userName) == null) {
tokenMapper.insert(token);
} else {
tokenMapper.updateByPrimaryKeySelective(token);
}
json = "{\"token\":\"" + tokenName.toString() + "\"}";
}else{
json="{\"token\":\"false\"}";
}
System.out.println(json);
return json;
}
}
代码详解:
首先,登录时给了username和password,然后根据这两个字段查询数据库是否有此用户,或者用户的密码是否正确,通过之后,会根据将此username作为loginname存到token表中,于此同时还有一个UUID生成作为tokenname,creatime、effectTime一同存到token表中,当然此操作是有条件的,先根据tokenname判断token表中是否有此token,没有的话在进行insert,有的话则修改token的有效时间和创建时间。
4.AOP配置文件
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="aroundTest" class="com.sxk.aop.AroundTest" />
5.token实体类
package com.sxk.entity;
import java.io.Serializable;
import java.sql.Timestamp;
/**
* Created by lenovo on 2017/3/2.
*/
public class Token implements Serializable {
private Integer tokenId;
private String tokenName;
private Timestamp createTime;
private Integer effectiveTime;
private String loginName;
public Integer getTokenId() {
return tokenId;
}
public void setTokenId(Integer tokenId) {
this.tokenId = tokenId;
}
public String getTokenName() {
return tokenName;
}
public void setTokenName(String tokenName) {
this.tokenName = tokenName;
}
public Timestamp getCreateTime() {
return createTime;
}
public void setCreateTime(Timestamp createTime) {
this.createTime = createTime;
}
public Integer getEffectiveTime() {
return effectiveTime;
}
public void setEffectiveTime(Integer effectiveTime) {
this.effectiveTime = effectiveTime;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
}
6.数据库模型
7.关于UUID.randomUUID()简单介绍
UUID.randomUUID().toString()是javaJDK提供的一个自动生成主键的方法。UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的,是由一个十六位的数字组成,表现出来的形式。由以下几部分的组合:当前日期和时间(UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同),时钟序列,全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得),UUID的唯一缺陷在于生成的结果串会比较长。
8.总结
此权限控制省略了对其他业务类的操作,比如说用户的CRUD模块、权限的CRUD模块、API的CRUD模块、角色的CRUD模块以及各个模块之间的关联管理,不过核心部分已经介绍差不多了。
实际开发当中,要根据具体的需求来修改代码的结构实现等等!