本篇博客接着上篇博客自定义权限模块2——自定义tld标签,并在上篇博客项目的基础上进行改造。在上篇博客中我们初步看到权限控制的效果,前端通过自定义标签根据我们后端提供的权限值来选择加载或不加载某些UI,但是后端的代码还没有完善,上篇博客只是提供了前端的解决方案,这一篇博客来解决后端的实现。
实现之前有几点需要明确,首先后端要自定义注解Moudle和Permission,Moudle注解在Controller代码上,Permission注解在Controller中访问的方法上,而这些注解的值就是上篇博客中自定义标签<mt:security module="" permission="">
中使用的值;其次,后端要实现方法在项目启动时将代码中的注解保存到数据库,以供后续配置和使用。
- 项目目录
- Security.java
package com.xyc.security.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
* 权限实体(权限模块+权限)
* Created by xyc on 2017/8/9 0009.
*/
@Entity
@Table(name = "T_SECURITY", catalog = "test")
public class Security implements Serializable {
private static final long serialVersionUID = -6294525733922731263L;
/**
* 权限主键
*/
@Id
@GeneratedValue
@Column(name = "SID")
private Long sid;
/**
* 权限的模块ID
*/
@Column(name = "PID")
private Long pid;
/**
* 权限名称
*/
@Column(name = "NAME", nullable = false)
private String name;
/**
* 权限值
*/
@Column(name = "VALUE", nullable = false)
private String value;
public Security() {
}
public Security(String name, String value) {
this.name = name;
this.value = value;
}
public Security(Long pid, String name, String value) {
this.pid = pid;
this.name = name;
this.value = value;
}
public Long getSid() {
return sid;
}
public void setSid(Long sid) {
this.sid = sid;
}
public Long getPid() {
return pid;
}
public void setPid(Long pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
- SecurityDao.java
package com.xyc.security.dao;
import com.xyc.security.domain.Security;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* 权限的数据访问层,使用spring jpa
* Created by xyc on 2017/8/8 0008.
*/
@Repository
public interface SecurityDao extends JpaRepository<Security, Long> {
}
- SecurityService.java
package com.xyc.security.service;
import com.xyc.security.annotation.Module;
import com.xyc.security.annotation.Permission;
import java.util.List;
import java.util.Map;
/**
* Created by xyc on 2017/8/10 0010.
*/
public interface SecurityService {
void saveSecurity(Map<Module, List<Permission>> mpMap);
}
- SecurityServiceImpl.java
package com.xyc.security.service.impl;
import com.xyc.security.annotation.Module;
import com.xyc.security.annotation.Permission;
import com.xyc.security.dao.SecurityDao;
import com.xyc.security.domain.Security;
import com.xyc.security.service.SecurityService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Created by xyc on 2017/8/10 0010.
*/
@Service
public class SecurityServiceImpl implements SecurityService {
@Resource
private SecurityDao securityDao;
@Override
public void saveSecurity(Map<Module, List<Permission>> mpMap) {
if (mpMap == null || mpMap.isEmpty()) {
return;
}
this.securityDao.deleteAll();
mpMap.forEach((m, pList) -> {
Security security = new Security(m.name(), m.value());
this.securityDao.save(security);
pList.forEach(p -> {
this.securityDao.save(new Security(permission.getSid(), p.name(), p.value()));
});
});
}
}
- SecurityApplication.java
package com.xyc.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan(basePackages = "com.xyc.security.filter")/*扫描过滤器*/
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}
- 注解Module.java
package com.xyc.security.annotation;
import java.lang.annotation.*;
/**
* 权限模块注解
* Created by xyc on 2017/8/9 0009.
*/
@Documented //作为被标注的程序成员的公共API,可以被例如javadoc此类的工具文档化
@Target(ElementType.TYPE) //用于描述类、接口(包括注解类型) 或enum声明
@Retention(RetentionPolicy.RUNTIME) //在运行时有效(即运行时保留)
public @interface Module {
String name();
String value();
}
- 注解Permission.java
package com.xyc.security.annotation;
import java.lang.annotation.*;
/**
* 权限注解
* Created by xyc on 2017/8/9 0009.
*/
@Documented //作为被标注的程序成员的公共API,可以被例如javadoc此类的工具文档化
@Target(ElementType.METHOD) //用于描述方法
@Retention(RetentionPolicy.RUNTIME) //在运行时有效(即运行时保留)
public @interface Permission {
String name();
String value();
}
- SecurityHelper.java
package com.xyc.security.helper;
import com.xyc.security.annotation.Module;
import com.xyc.security.annotation.Permission;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Created by xyc on 2017/8/9 0009.
*/
public class SecurityHelper {
private final static Logger logger = LoggerFactory.getLogger(SecurityHelper.class);
/**
* 解析源码
*
* @param mpMap
* @param className
* @throws ClassNotFoundException
*/
public static void analyseClassPathCode(Map<Module, List<Permission>> mpMap, String className) throws ClassNotFoundException {
Class cls = Class.forName(className);
if (!cls.isAnnotationPresent(Controller.class) && !cls.isAnnotationPresent(RestController.class)) {
return;
}
if (!cls.isAnnotationPresent(Module.class)) { //没有权限模块配置
return;
}
Module module = (Module) cls.getAnnotation(Module.class);
if (mpMap.get(module) != null) {
return;
}
List<Permission> permissionList = new ArrayList<>();
Method[] methods = cls.getDeclaredMethods();
if (methods == null || methods.length == 0) { //没有权限项配置
mpMap.put(module, permissionList);
return;
}
for (Method method : methods) {
if (!method.isAnnotationPresent(Permission.class)) {
continue;
}
permissionList.add(method.getAnnotation(Permission.class));
}
mpMap.put(module, permissionList);
}
/**
* 扫描源码
*
* @param mpMap
* @throws URISyntaxException
* @throws IOException
*/
public static void scanSecurity(Map<Module, List<Permission>> mpMap) throws URISyntaxException, IOException {
ClassLoader classLoader = MethodHandles.lookup().lookupClass().getClassLoader(); //获取类加载器
URL[] urls = ((URLClassLoader) classLoader).getURLs(); //获取项目的所有文件路径URL
if (urls == null || urls.length == 0) { //如果项目下文件路径为空则返回
return;
}
for (URL url : urls) {
final Path path = Paths.get(url.toURI());
if (!path.toFile().isDirectory()) {
continue;
}
//如果是目录则代表是我们自己写的代码,进行解析
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { //正在访问一个文件时要干啥
String fileName = file.getFileName().toString();
if (!fileName.endsWith(".class") || fileName.contains("$")) { //$后面跟数字是匿名类编译出来的/$后面跟文字是内部类编译出来的
return FileVisitResult.CONTINUE; //继续遍历
}
String classPathName = file.toUri().toString().split(path.toUri().toString())[1]; //class path name com/xyc/permission/web/AController.class
String className = classPathName.substring(0, classPathName.lastIndexOf(".")).replace('/', '.'); //class name com.xyc.permission.web.AController
try {
SecurityHelper.analyseClassPathCode(mpMap, className);
} catch (ClassNotFoundException e) {
logger.error("解析权限异常", e);
}
return FileVisitResult.CONTINUE; //继续遍历
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { //访问一个文件失败时要干啥
return FileVisitResult.CONTINUE; //继续遍历
}
});
}
}
}
- SecurityFilter.java
package com.xyc.security.filter;
import com.xyc.security.annotation.Module;
import com.xyc.security.annotation.Permission;
import com.xyc.security.helper.SecurityHelper;
import com.xyc.security.service.SecurityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 过滤器,进行权限初始化
* Created by xyc on 2017/8/9 0009.
*/
@WebFilter
public class SecurityFilter implements Filter {
//定义一个全局的记录器,通过LoggerFactory获取
private final static Logger logger = LoggerFactory.getLogger(SecurityFilter.class);
@Resource
private SecurityService securityService;
/**
* init方法在项目启动时会执行一次,所以在这里使用过滤器的init方法来进行权限的初始化
*
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException { //启动服务器时加载过滤器的实例,并调用init()方法来初始化实例
Thread thread = new Thread(() -> { //使用多线程执行,不影响主线程工作
Map<Module, List<Permission>> mpMap = new HashMap<>();
try {
logger.info("开始解析权限");
SecurityHelper.scanSecurity(mpMap);
logger.info("开始保存权限");
this.securityService.saveSecurity(mpMap);
} catch (URISyntaxException | IOException e) {
logger.error("加载权限信息异常", e);
throw new RuntimeException(e);
}
});
thread.setDaemon(true); //守护线程
thread.start();
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
- 测试类AController.java
package com.xyc.security.controller;
import com.xyc.security.annotation.Module;
import com.xyc.security.annotation.Permission;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by xyc on 2017/8/9 0009.
*/
@RestController
@RequestMapping("/a")
@Module(name = "模块A", value = "A")
public class AController {
@GetMapping("/add")
@Permission(name = "添加", value = "add")
public String add() {
return "添加A";
}
@GetMapping("/delete")
@Permission(name = "删除", value = "delete")
public String delete() {
return "删除A";
}
@GetMapping("/update")
@Permission(name = "修改", value = "update")
public String update() {
return "修改A";
}
@GetMapping("/query")
@Permission(name = "查询", value = "query")
public String query() {
return "查询A";
}
}
- 测试类BController.java
package com.xyc.security.controller;
import com.xyc.security.annotation.Module;
import com.xyc.security.annotation.Permission;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by xyc on 2017/8/9 0009.
*/
@RestController
@RequestMapping("/b")
@Module(name = "模块B", value = "B")
public class BController {
@GetMapping("/add")
@Permission(name = "添加", value = "add")
public String add() {
return "添加B";
}
@GetMapping("/delete")
@Permission(name = "删除", value = "delete")
public String delete() {
return "删除B";
}
@GetMapping("/update")
@Permission(name = "修改", value = "update")
public String update() {
return "修改B";
}
@GetMapping("/query")
@Permission(name = "查询", value = "query")
public String query() {
return "查询B";
}
}
- 测试类CController.java
package com.xyc.security.controller;
import com.xyc.security.annotation.Module;
import com.xyc.security.annotation.Permission;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by xyc on 2017/8/9 0009.
*/
@RestController
@RequestMapping("/c")
@Module(name = "模块C", value = "C")
public class CController {
@GetMapping("/add")
@Permission(name = "添加", value = "add")
public String add() {
return "添加C";
}
@GetMapping("/delete")
@Permission(name = "删除", value = "delete")
public String delete() {
return "删除C";
}
@GetMapping("/update")
@Permission(name = "修改", value = "update")
public String update() {
return "修改C";
}
@GetMapping("/query")
@Permission(name = "查询", value = "query")
public String query() {
return "查询C";
}
}
- 测试类DController.java
package com.xyc.security.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Created by xyc on 2017/8/10 0010.
*/
@Controller
@RequestMapping("/d")
public class DController {
@GetMapping("/test")
public String test() {
new String("fadsfas");
return "test";
}
}
- application.properties
#jsp视图配置,如果报错,可修改为:spring.view.prefix和spring.view.suffix,这个和spring boot的版本有关
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
#JDBC数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=******
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
#create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
#update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
#validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
spring.jpa.properties.hibernate.hbm2ddl.auto=update
#logback日志配置
logging.file=D:\\security\\security.log
logging.level.root=info
- 测试
启动项目后查看数据库
- 总结
到这里权限的功能差不多要完成了,权限的数据已经保存到数据库,这时候你应该建一个用户表和一个用户权限关联表,当用户登录时获取此用户的权限,前端会根据当前用户的权限来加载视图。写到这里相比大家发现还有一个漏洞,就是虽然你的视图没有加载,但是可以通过url直接访问加载,因为后端没有限制,所以接下来要解决这个问题。