spring security权限控制,ECharts图形报表

tech2022-08-16  148

第10章 权限控制、图形报表

1. 在项目中应用Spring Security

前面我们已经学习了Spring Security框架的使用方法,本章节我们就需要将Spring Security框架应用到 后台系统中进行权限控制,其本质就是认证和授权。 要进行认证和授权需要前面课程中提到的权限模型涉及的7张表支撑,因为用户信息、权限信息、菜单 信息、角色信息、关联信息等都保存在这7张表中,也就是这些表中的数据是我们进行认证和授权的依 据。所以在真正进行认证和授权之前需要对这些数据进行管理,即我们需要开发如下一些功能: 1、权限数据管理(增删改查) 2、菜单数据管理(增删改查) 3、角色数据管理(增删改查、角色关联权限、角色关联菜单) 4、用户数据管理(增删改查、用户关联角色) 鉴于时间关系,我们不再实现这些数据管理的代码开发。我们可以直接将数据导入到数据库中即可。

1.1 导入和配置

第一步:在health_parent父工程的pom.xml中导入Spring Security的maven坐标

<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring.security.version}</version> </dependency>

第二步:在health_backend工程的web.xml文件中配置用于整合Spring Security框架的过滤器 DelegatingFilterProxy

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <security:http security="none" pattern="/login.html"></security:http> <security:http security="none" pattern="/css/**"></security:http> <security:http security="none" pattern="/img/**"></security:http> <security:http security="none" pattern="/js/**"></security:http> <security:http security="none" pattern="/plugins/**"></security:http> <!-- auto-config:自动配置,如果设置为true,表示自动应用一些默认配置,比如框架会提供一个默认的登录页面 user-expressions:是否使用spring security提供的表达式来描述权限 --> <security:http auto-config="true" use-expressions="true"> <security:access-denied-handler ref="myExceptionAdvice"/> <security:headers> <!-- 设置在页面可以通过iframe访问受保护的页面,默认为不允许访问,页面嵌套的要告诉具体策略--> <security:frame-options policy="SAMEORIGIN"></security:frame-options> </security:headers> <!-- 配置拦截规则,/**表示拦截所有请求--> <!--只要认证通过就可以访问--> <security:intercept-url pattern="/pages/**" access="isAuthenticated()" /> <!-- pattern:描述拦截规则 asscess:指定所需的访问角色或者访问权限 --> <!-- 如果我们要使用自己指定的页面作为登录页面,必须配置登录表单--> <!-- form-login指定登录页面访问URL--> <security:form-login login-page="/login.html" username-parameter="username" password-parameter="password" login-processing-url="/login.do" default-target-url="/index.html" authentication-failure-url="/login.html"></security:form-login> <!-- csrf:对应CsrfFilter过滤器 disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403) --> <security:csrf disabled="true"></security:csrf> <!-- logout:退出登录 logout-url:退出登录操作采采芣苡的请求路径 logout-success-url:退出登录后的跳转页面 基于过滤器实现 --> <security:logout logout-url="/logout.do" logout-success-url="/login.html" invalidate-session="true"/> </security:http> <!-- 配置认证管理器--> <security:authentication-manager> <!-- 配置认证提供者--> <security:authentication-provider user-service-ref="springSecurityUserService"> <!-- 指定对密码加密的对象--> <security:password-encoder ref="passwordEncoder"></security:password-encoder> </security:authentication-provider> </security:authentication-manager> <!-- 配置密码加密对象--> <bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="passwordEncoder"></bean> <!-- 开启注解方式权限控制--> <security:global-method-security pre-post-annotations="enabled"/> <bean class="com.ybb.ex.MyExceptionAdvice" id="myExceptionAdvice"/> </beans>

再写个类实现UserDetailsService接口

package com.ybb.service; import com.alibaba.dubbo.config.annotation.Reference; import com.ybb.pojo.Permission; import com.ybb.pojo.Role; import com.ybb.pojo.User; import com.ybb.service.UserService; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Set; @Component public class SpringSecurityUserService implements UserDetailsService { //使用dubbo通过网络远程调用服务提供方获取数据库中的用户信息 @Reference private UserService userService; //根据用户名查询数据库获取用户信息 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService.findByUserName(username); if(user == null){ //用户名不存在 return null; } List<GrantedAuthority> list = new ArrayList<>(); //动态为当前用户授权 Set<Role> roles = user.getRoles(); for (Role role : roles) { //遍历角色集合,为用户授予角色 list.add(new SimpleGrantedAuthority(role.getKeyword())); Set<Permission> permissions = role.getPermissions(); for (Permission permission : permissions) { //遍历权限集合,为用户授权 list.add(new SimpleGrantedAuthority(permission.getKeyword())); } } org.springframework.security.core.userdetails.User securityUser = new org.springframework.security.core.userdetails.User(username,user.getPassword(),list); return securityUser; } }

这里踩了个坑,配置了成功的访问路径,但一直出问题,后面发现在web.xml中配置了个spring-redis和spring-security,出现了问题,不能这么配置,不规范

后面改动了工程,在springmvc中引入了这两个xml文件,就好了。注意配置规范

1.2controller,service,dao

这里因为涉及到动态的加载权限,要访问数据库,也涉及到多表联查,这里用的方式是用java代码简化xml配置的思路,根据id查询中间表,再移动到role,这是一块,后面再根据role的id查询中间表到permssion,又刚好pojo实体类中,user中有一个属性包含role的集合,role中又包含permission的集合,所以就简单了

userSerivce

public interface UserService { User findByUserName(String username); }

userSericeImpl

@Service(interfaceClass = UserService.class) @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Autowired private RoleDao roleDao; @Autowired private PermissionDao permissionDao; //根据用户名查询书库获取用户信息和关联的角色信息,同时需要查询角色关联的权限信息 @Autowired private MenuDao menuDao; @Override public User findByUserName(String username) { User user = userDao.findByUserName(username); if (user==null){ return null; } Integer userId = user.getId(); //根据用户ID查询对应的角色 Set<Role> roles = roleDao.findByUserId(userId); for (Role role : roles) { //角色ID,去查询关联权限 Integer roleId = role.getId(); Set<Permission> permissions = permissionDao.findByRoleId(roleId); role.setPermissions(permissions); } user.setRoles(roles); return user; }

userDao

public interface UserDao { User findByUserName(String username); } <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ybb.dao.UserDao"> --> <select id="findByUserName" resultType="com.ybb.pojo.User" parameterType="string"> SELECT * FROM t_user where username=#{username} </select> </mapper>

RoleDao

public interface RoleDao { Set<Role>findByUserId(Integer userId); } <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ybb.dao.RoleDao"> <select id="findByUserId" resultType="com.ybb.pojo.Role" parameterType="int"> SELECT r.* FROM t_user_role ur,t_role r where ur.role_id=r.id and ur.user_id=#{userId} </select> </mapper>

包扫描记得动一下

<!--批量扫描--> <dubbo:annotation package="com.全局" />

1.3权限

这里用注解的方式实现权限的给予

@PreAuthorize("hasAuthority('CHECKITEM_DELETE')")//权限校验 @RequestMapping("/delete") public Result delete(Integer id){ try { checkItemService.deleteById(id); }catch (Exception e){ return new Result(false,MessageConstant.DELETE_CHECKGROUP_FAIL); } return new Result(true, MessageConstant.DELETE_CHECKGROUP_SUCCESS); }

权限和角色是有层级关系的,但对于spring来说都只是个字符串而已,当要涉及到角色或者权限验证的时候,就去这个集合中找有没有这个字符串,没有就403.

List<GrantedAuthority> list = new ArrayList<>();

1.4 403时如何处理给用户看

spring这有个接口AccessDeniedHandler,专门处理403情况的,不过也算个异常,自己可以写个异常处理器来实现。这里博主比较懒,基于spring框架来实现的。

package com.ybb.ex; import com.alibaba.fastjson.JSON; import com.ybb.entity.Result; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * Description :spring security内部封装了权限出问题的异常处理器,如果出异常就会跑这来 * Version :1.0 */ public class MyExceptionAdvice implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { Result result = new Result(false,"出问题啦"); String data = JSON.toJSON(result).toString(); response.setContentType("application/json;charset=utf-8"); response.getWriter().print(data); } }

然后配置进spring-security中,注意插入位置

1.5显示用户名

前面我们已经完成了认证和授权操作,如果用户认证成功后需要在页面展示当前用户的用户名。Spring Security在认证成功后会将用户信息保存到框架提供的上下文对象中,所以此处我们就可以调用Spring Security框架提供的API获取当前用户的username并展示到页面上。 实现步骤: 第一步:在main.html页面中修改,定义username模型数据基于VUE的数据绑定展示用户名,发送ajax 请求获取username

new Vue({ el: '#app', data: { username: null, }, created(){ axios.get("/user/getUsernameAndMenu.do").then((res)=>{ if (res.data.flag){ this.username=res.data.data.username } }) }, package com.ybb.controller; import com.alibaba.dubbo.config.annotation.Reference; import com.ybb.constant.MessageConstant; import com.ybb.entity.Result; import com.ybb.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.swing.*; /** * Description : * Version :1.0 */ @RestController @RequestMapping("/user") public class UserController { @Reference private UserService userService; //获得当前登录用户的用户名 @RequestMapping("/getUsernameAndMenu") public Result getUsername(){ //当spring security完成认证后,会将当前用户信息保存到框架提供的上下文对象(基于session) User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); System.out.println(user); if (user!=null){ return new Result(true, MessageConstant.GET_MENU_SUCCESS,user.getUsername); } return new Result(false,MessageConstant.GET_USERNAME_FAIL); } }

1.6 用户退出

a标签加security配置,结束

2. 图形报表ECharts

2.1 ECharts简介

ECharts缩写来自Enterprise Charts,商业级数据图表,是百度的一个开源的使用JavaScript实现的数据 可视化工具,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11, Chrome,Firefox,Safari等),底层依赖轻量级的矢量图形库 ZRender,提供直观、交互丰富、可高 度个性化定制的数据可视化图表

https://echarts.apache.org/examples/zh/index.html

下载什么的就不说了。只需要引入echarts.js文件就能用了

2.2会员数量折线图

2.21 需求分析

会员信息是体检机构的核心数据,其会员数量和增长数量可以反映出机构的部分运营情况。通过折线图 可以直观的反映出会员数量的增长趋势。本章节我们需要展示过去一年时间内每个月的会员总数据量。 展示效果如下图:

2.22 完善页面

记得引入echats.js文件

<!DOCTYPE html> <html> <head> <!-- 页面meta --> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>传智健康</title> <meta name="description" content="传智健康"> <meta name="keywords" content="传智健康"> <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport"> <!-- 引入样式 --> <link rel="stylesheet" href="../css/style.css"> <script src="../plugins/echarts/echarts.js"></script> </head> <body class="hold-transition"> <div id="app"> <div class="content-header"> <h1>统计分析<small>会员数量</small></h1> <el-breadcrumb separator-class="el-icon-arrow-right" class="breadcrumb"> <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item> <el-breadcrumb-item>统计分析</el-breadcrumb-item> <el-breadcrumb-item>会员数量</el-breadcrumb-item> </el-breadcrumb> </div> <div class="app-container"> <div class="box"> <!-- 为 ECharts 准备一个具备大小(宽高)的 DOM --> <div id="chart1" style="height:600px;"></div> </div> </div> </div> </body> <!-- 引入组件库 --> <script src="../js/vue.js"></script> <script src="../js/axios-0.18.0.js"></script> <script type="text/javascript"> // 基于准备好的dom,初始化echarts实例 var myChart1 = echarts.init(document.getElementById('chart1')); // 使用刚指定的配置项和数据显示图表。 //myChart.setOption(option); axios.get("/report/getMemberReport.do").then((res)=>{ myChart1.setOption( { title: { text: '会员数量' }, tooltip: {}, legend: { data:['会员数量'] }, xAxis: { data: res.data.data.months }, yAxis: { type:'value' }, series: [{ name: '会员数量', type: 'line', data: res.data.data.memberCount }] }); }); </script> </html>

2.23 后台代码

这里实现的业务逻辑是比如第一个月1个,第二个月加了2个,展示的3,累加的效果。

package com.ybb.controller; import com.alibaba.dubbo.config.annotation.Reference; import com.ybb.constant.MessageConstant; import com.ybb.entity.Result; import com.ybb.service.MemberService; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.text.SimpleDateFormat; import java.util.*; /** * Description : * Version :1.0 */ @RestController @RequestMapping("/report") public class report { @Reference private MemberService memberService; @RequestMapping("/getMemberReport") public Result getMemberReport() { //需要往前端传两个数组对象,用map封装 HashMap<String, Object> map = new HashMap<>(); List<String> months = new ArrayList<>(); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM"); //先要计算过去一年的12个月 Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MONTH, -12); for (int i = 0; i < 12; i++) { calendar.add(Calendar.MONTH, 1); months.add(format.format(calendar.getTime())); } map.put("months",months); List<Integer> memberCountByMonths = memberService.findMemberCountByMonths(months); map.put("memberCount",memberCountByMonths); return new Result(true, MessageConstant.GET_MEMBER_NUMBER_REPORT_SUCCESS,map); } }

MemberService

List<Integer>findMemberCountByMonths(List<String>months);

MemberServiceImpl

package com.ybb.service.Impl; import com.alibaba.dubbo.config.annotation.Service; import com.ybb.dao.MemberDao; import com.ybb.pojo.Member; import com.ybb.service.MemberService; import com.ybb.utils.MD5Utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; /** * Description : * Version :1.0 */ @Service(interfaceClass = MemberService.class) @Transactional public class MemberServiceImpl implements MemberService { @Autowired private MemberDao memberDao; //根据月份查询会员数量 @Override public List<Integer> findMemberCountByMonths(List<String> months) { List<Integer>memberCount=new ArrayList<>(); for (String month : months) { String date = month + ".31"; Integer memberCountBeforeDate = memberDao.findMemberCountBeforeDate(date); memberCount.add(memberCountBeforeDate); } return memberCount; } }

dao

public Integer findMemberCountBeforeDate(String date); <!--根据日期统计会员数,统计指定日期之前的会员数--> <select id="findMemberCountBeforeDate" parameterType="string" resultType="int"> select count(id) from t_member where regTime &lt;= #{value} </select>
最新回复(0)