基于shiro的按钮级别的权限管理系统
一、项目背景
作为程序猿的你,是否在大学课堂上听到老师讲权限管理一脸懵逼;是否在互联网上看到炫酷的权限管理系统一脸羡慕;是否在公司学习使用权限管理一脸激动。那么,今天你看到这个教程之后,请你不要再懵逼,请不要再羡慕,请肆无忌惮的激动吧。好嗨哟,即将带你走上人生的巅峰。下面将手把手的教你实现基于shiro权限框架的权限管理系统,真正意义上的在按钮级别上完成权限控制。注:本教程侧重点在于shiro框架的使用与权限控制逻辑的实现,对于其它知识点不在重点讨论范围内。如需学习更多关于shiro的知识可在我的文章查看。
二、技术栈
后台:spring+springmvc+mybatis+shiro+kaptcha+fastjson+log4j+druid+maven+echcache+pageHelper等
前端:vue+jquery+iview+ztree等
数据库:mysql
其它:IntelliJ IDEA 2018.2.4 x64+tomcat8.0+jdk1.8
三、项目结构
四、项目预览
图1 登录首页
图2 系统首页
图3 用户列表
图4 用户新增
图5 角色列表
图6 角色新增
图7 菜单列表
图8 菜单新增
五、数据库设计
权限管理系统基础表有五张:sys_user(用户表)、sys_role(角色表)、sys_menu(资源表)、sys_user_role(用户角色关联表)、sys_role_menu(角色资源关联表),用户与角色和角色与资源都是多对多的关系,用户与资源必须通过授予角色才能建立关系。
(1)用户表详细设计
(2)角色表详细设计
(3)资源表详细设计
(4)用户角色表详细设计
(5)角色资源表详细设计
六、功能设计
(1)角色授权
此处新建一个角色为“测试”,授予用户管理菜单权限、新增按钮权限、删除按钮权限。
(2)分配角色
此处新增新建一个用户“a”,分配“测试”角色。
(3)新用户登录测试
因为“a”用户分配的角色为“测试”,“测试”角色拥有用户管理菜单、新增按钮、删除按钮权限,用该用户登录系统后只能看到用户管理菜单、新增按钮、删除按钮;不会看到其它的系统菜单和修改按钮。
七、项目代码示例
该项目的基础代码github地址为:https://github.com/tmAlj/shiro/tree/master/ssms,以下的给出的配置文件为本项目改变的内容,其余相同的部分未给出。
(1)pom.xml依赖管理配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wsd</groupId>
<artifactId>ssms1</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<!-- end -->
<!-- mysql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<!-- end -->
<!-- 数据源依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.26</version>
</dependency>
<!-- end -->
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency>
<!-- end -->
<!-- springMVC依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<!-- springMVC依赖end -->
<!-- log4j依赖-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.19</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.19</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- end -->
<!-- junit单元测试依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- end -->
<!-- fastjson依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.20</version>
</dependency>
<!-- end -->
<!-- servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- end -->
<!-- commons相关依赖 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- end -->
<!-- shiro依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<!-- end -->
<!-- kaptcha验证码start -->
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>0.0.9</version>
</dependency>
<!-- end -->
</dependencies>
</project>
(2)web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--项目启动首页配置-->
<display-name>tm-cli</display-name>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
<!-- 定义上下文,扫描spring配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-config.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- 统一编码过滤器配置 -->
<filter>
<filter-name>encode</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encode</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- springMVC前段控制器配置 -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- shiro过滤器配置 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
(3)shiro配置文件spring-shiro-config.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- 配置自定义reaml -->
<bean id="loginRealm" class="com.wsd.shiro.LoginRealm"/>
<!-- 配置shiro的核心securityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<!-- 配置realm -->
<property name="realm" ref="loginRealm"/>
<!-- 配置记住我的时长 -->
<property name="rememberMeManager.cookie.maxAge" value="60"></property>
</bean>
<!-- 配置ehcache缓存 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 配置shiro中bean生命周期管理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- AOP式方法级权限检查 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- shiro过滤器配置,与web.xml中shiro过滤器同名 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- 需要登录成功后跳转的页面 -->
<property name="loginUrl" value="/login.jsp"/>
<!-- 登录成功后跳转的页面 -->
<property name="successUrl" value="/index.jsp"/>
<!-- 访问未授权页面跳转的页面 -->
<property name="unauthorizedUrl" value="/unauthor.html"/>
<property name="filterChainDefinitions">
<!-- 静态资源需要设置为anon,否则找不到 -->
<value>
/statics/** = anon
/plugins/** = anon
/login.jsp = anon
/login = anon
/logout = logout
/captcha.jpg = anon
/** = authc
</value>
</property>
</bean>
</beans>
(4)springmvc配置文件spring-mvc-config.xml配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--视图解析器,如访问Controller中配置的"/tm",系统会自动找到路径为prefix后缀为suffix的文件并解析 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 使用零配置 -->
<context:component-scan base-package="com.wsd" />
<mvc:default-servlet-handler />
<mvc:annotation-driven />
<!-- 配置interceptor拦截器 -->
<mvc:interceptors>
<!-- 默认拦截所有请求 -->
<bean class="com.wsd.interceptor.Interceptor"/>
</mvc:interceptors>
<!-- 开启shiro中aop注解 -->
<aop:config proxy-target-class="true"></aop:config>
<!--配置fastjson,返回json数据格式-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<!-- 配置Fastjson支持 -->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
</list>
</property>
<property name="features">
<list>
<value>WriteMapNullValue</value>
<value>QuoteFieldNames</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 配置kaptcha验证码生成器 -->
<bean name="producer" class="com.google.code.kaptcha.impl.DefaultKaptcha" scope="singleton">
<property name="config">
<bean class="com.google.code.kaptcha.util.Config">
<constructor-arg>
<props>
<prop key="kaptcha.border">no</prop>
<prop key="kaptcha.textproducer.font.color">black</prop>
<prop key="kaptcha.textproducer.char.space">5</prop>
</props>
</constructor-arg>
</bean>
</property>
</bean>
</beans>
八、部分功能解析
(1)登录认证(认证参考)
1.1 前端实现
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<title>用户登录</title>
<link rel="stylesheet" type="text/css" href="statics/css/iview.css">
<link rel="stylesheet" type="text/css" href="statics/css/login.css" />
</head>
<body>
<div id="app" v-cloak>
<div class="tm-title">欢迎使用tm-cli管理系统</div>
<div class="tm-content">
<i-input prefix="ios-contact" placeholder="账号" class="tm-input" v-model="account"></i-input>
<i-input prefix="md-lock" placeholder="密码" class="tm-input" type="password" v-model="password"></i-input>
<i-input prefix="ios-analytics" placeholder="验证码" class="tm-input" v-model="code"></i-input>
<div class="tm-code">
<div class="tm-picture">
<img alt="点击刷新" :src="src" @click="onRefrCodeEvet()" class="tm-img">
</div>
<div class="tm-reflush" @click="onRefrCodeEvet()">点击刷新</div>
</div>
<i-button type="primary" long :loading="loading" @click="callLoginImpl()">登 录</i-button>
<div class="tm-footer">
<div class="tm-checkbox">
<checkbox v-model="remember"></checkbox>
</div>
<div class="tm-remember">
记住我
</div>
<div class="tm-forget">
忘记密码?
</div>
</div>
</div>
<div class="tm-copyright">Copyright © 2018 All Rights Reserved</div>
</div>
<script type="text/javascript" src="statics/js/jquery-3.2.1.js"></script>
<script type="text/javascript" src="statics/js/vue.min.js"></script>
<script type="text/javascript" src="statics/js/iview.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
account: '', //账号
password: '', //密码
code: '', //验证码
src: 'captcha.jpg', //验证码图片
loading: false, //登录按钮加载动画
remember: false, //记住我复选框
msg: '' //提示信息
},
methods: {
/*调用登录接口*/
callLoginImpl: function(){
var t = this;
if(t.doCheckFormFun()){
t.loading = true;
var data = "account="+this.account+"&password="+this.password+"&code="+this.code+"&remember="+this.remember;
$.ajax({
type: "POST",
url: "login",
data: data,
dataType: "json",
success: function(data){
if(data.code == 0){ //登录成功
t.loading = false;
parent.location.href ='index.jsp';
}else{
t.msg = data.msg;
t.$Message.warning(t.msg);
t.onRefrCodeEvet();
t.loading = false;
}
}
});
}
},
/*刷新验证码点击事件*/
onRefrCodeEvet: function(){
var t = this;
t.src = "captcha.jpg?t=" + $.now();
},
/*表单验证方法*/
doCheckFormFun: function(){
var t = this;
if(t.account == '' || t.account == undefined){
t.$Message.warning("账户输入不能为空!");
return false;
}
else if(t.password == '' || t.password == undefined){
t.$Message.warning("密码输入不能为空!");
return false;
}
else if(t.code == '' || t.code == undefined){
t.$Message.warning("验证码输入不能为空!");
return false;
}else{
return true;
}
}
}
})
</script>
</body>
</html>
1.2 controller实现
package com.wsd.controller;
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.Producer;
import com.wsd.utils.ResultData;
import com.wsd.utils.ShiroUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* Created by tm on 2018/8/26.
* 登录controller
*/
@Controller()
public class LoginController {
@Autowired
private Producer producer; //验证码操作对象
/**
* 生成验证码
* @param response
* @throws ServletException
* @throws IOException
*/
@RequestMapping("captcha.jpg")
public void captcha(HttpServletResponse response)throws ServletException, IOException {
//页面不用缓存
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
//生成文字验证码
String text = producer.createText();
//生成图片验证码
BufferedImage image = producer.createImage(text);
//验证码存入session,用于登录时做对比
ShiroUtils.setSessionAttribute(Constants.KAPTCHA_SESSION_KEY, text);
ServletOutputStream out = response.getOutputStream();
//输出验证码
ImageIO.write(image, "jpg", out);
}
/**
* 登录
*/
@ResponseBody
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResultData login(String account, String password, String code, String remember)throws IOException {
String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY); //session中获取保存的验证码内容
if(!kaptcha.equalsIgnoreCase(code)){
return ResultData.error("验证码不正确");
}
try{
Subject subject = ShiroUtils.getSubject();
password = new Sha256Hash(password).toHex();
UsernamePasswordToken token = new UsernamePasswordToken(account, password);
// 开启记住我的功能
if(remember.equals("false")){
token.setRememberMe(false);
}else{
token.setRememberMe(true);
}
subject.login(token);
}catch (UnknownAccountException e) {
return ResultData.error(e.getMessage());
}catch (IncorrectCredentialsException e) {
return ResultData.error(e.getMessage());
}catch (LockedAccountException e) {
return ResultData.error(e.getMessage());
}catch (AuthenticationException e) {
return ResultData.error("账户验证失败");
}catch (Exception e) {
return ResultData.error();
}
return ResultData.ok();
}
}
1.3 自定义loginRealm
package com.wsd.shiro;
import com.wsd.model.Menu;
import com.wsd.model.User;
import com.wsd.service.MenuService;
import com.wsd.service.UserService;
import com.wsd.service.impl.LoginServiceImpl;
import com.wsd.service.impl.MenuServiceImpl;
import com.wsd.service.impl.UserServiceImpl;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;
/**
* Created by tm on 2018/8/26.
* 自定义realm
* 注:需要在spring-shiro-config.xml中配置
*/
public class LoginRealm extends AuthorizingRealm {
@Autowired LoginServiceImpl lsi; //注入登录service
@Autowired MenuServiceImpl msi; //注入菜单service
@Autowired UserServiceImpl usi; //注入用户service
/*授权*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User)principalCollection.getPrimaryPrincipal();
Long userId = user.getUserId();
List<String> permsList = null;
//系统管理员,拥有最高权限
if(userId == 1){
List<Menu> menuList = msi.queryList(new HashMap<String, Object>());
permsList = new ArrayList<String>(menuList.size());
for(Menu menu : menuList){
permsList.add(menu.getPerms());
}
}else{
permsList = usi.queryAllPerms(userId);
}
//用户权限列表
Set<String> permsSet = new HashSet<String>();
for(String perms : permsList){
if(StringUtils.isBlank(perms)){
continue;
}
permsSet.addAll(Arrays.asList(perms.trim().split(",")));
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
/*验证*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String account = (String) authenticationToken.getPrincipal(); //获取输入的账户
String password = new String((char[]) authenticationToken.getCredentials()); //获取输入的密码
//数据中查询用户信息
User user = lsi.queryByUserName(account);
//账号不存在
if(user == null) {
throw new UnknownAccountException("账号或密码不正确");
}
//密码错误
if(!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("账号或密码不正确");
}
//账号锁定
if(user.getStatus() == 0){
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
}
(2)权限控制(授权参考)
2.1 前端实现(通过shiro的标签控制前端权限标签参考)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<title>用户管理</title>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/statics/css/iview.css">
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/statics/css/index.css">
</head>
<body>
<div id="app" v-cloak>
<!--操作按钮-->
<div class="tm-btns">
<shiro:hasPermission name="sys:user:save">
<i-button type="info" @click="onAddEvet()">新增</i-button>
</shiro:hasPermission>
<shiro:hasPermission name="sys:user:update">
<i-button type="primary" @click="onUpdateEvet()">修改</i-button>
</shiro:hasPermission>
<shiro:hasPermission name="sys:user:delete">
<i-button type="warning" @click="onDeleteAllEvet()">删除</i-button>
</shiro:hasPermission>
<shiro:hasPermission name="sys:user:list">
<div class="tm-btns-search">
<i-input placeholder="输入用户名" style="width: 110px" v-model="userName" @keyup.enter.native="doSearchEvet">
<icon type="ios-search" slot="suffix" @click="doSearchEvet()"></icon>
</i-input>
</div>
</shiro:hasPermission>
</div>
<!--表格-->
<shiro:hasPermission name="sys:user:list">
<i-table border :loading="loading" :columns="columns" size="small" :data="datas" :height="433" @on-selection-change="onSeltChangeEvet"></i-table>
</shiro:hasPermission>
<!--分页-->
<shiro:hasPermission name="sys:user:list">
<div class="tm-page" v-if="pageShow">
<page :total="total" show-sizer show-total size="small" @on-change="onChangePageEvet" @on-page-size-change="onChangeSizeEvet"/>
</div>
</shiro:hasPermission>
</div>
<script type="text/javascript" src="${pageContext.request.contextPath}/statics/js/jquery-3.2.1.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/statics/js/vue.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/statics/js/iview.min.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
loading: false, //表格加载动画
page: '1', //当前页码
limit: '10', //每页显示条数
pageShow: false, //是否显示分页标志
total: '', //数据总数
userName: '', //用户搜索框内容
parentVue: '', //父级vue对象,主要为了消息提示框的全局显示
checkList: [], //复选框保存值数组
userIdList: [], //用户id数组
columns: [ //表格头部数据
{
width: 50,
type: 'selection',
title: '',
key: 'select'
},
{
type: 'index',
width: 70,
title: '序号'
},
{
width: 120,
title: '用户名',
key: 'username'
},
{
width: 120,
title: '手机号码',
key: 'mobile'
},
{
width: 120,
title: '状态',
key: 'status',
render: function(h, params){
var row = params.row;
var color = row.status === 0 ? 'error' : 'success';
var text = row.status === 0 ? '禁用' : '正常';
return h('Tag', {
props: {
type: 'dot',
color: color
}
}, text);
}
},
{
title: '邮箱',
key: 'email'
},
{
title: '创建时间',
key: 'createTime',
sortable: true,
render: (h,params)=>{
return h('div',
vm.doFormatDateFun(new Date(params.row.createTime),'yyyy-MM-dd hh:mm:ss')
)
}
}
],
datas: [] //表格数据
},
methods: {
/*调用查询用户列表接口*/
callGetUserListImpl: function(){
var p = this.parentVue;
var t = this;
t.loading = true; //打开加载动画
var data = "page="+t.page+"&limit="+t.limit+"&userName="+t.userName;
$.ajax({
type: "POST",
url: "../sys/user/list",
data: data,
dataType: "json",
success: function(data){
if(data.code == 0){
//当数量超过10时,显示分页条
if(data.page.total > 10){
t.total = data.page.total;
t.pageShow = true;
}
t.datas = data.page.list;
t.loading = false;
}
},
error: function () {
t.loading = false;
p.$Message.error("系统异常,请稍后重试!");
}
});
},
/*调用删除用户接口*/
callDeleteUserImpl: function(){
var p = this.parentVue;
var t = this;
t.loading = true; //打开加载动画
$.ajax({
type: "POST",
url: "../sys/user/delete",
data: JSON.stringify(t.userIdList),
dataType: "json",
contentType:"application/json",
success: function(data){
t.loading = false;
if(data.code == 0){
p.$Message.success('操作成功!');
t.callGetUserListImpl();
}else{
p.$Message.error(data.msg);
}
},
error: function () {
t.loading = false;
p.$Message.error("系统异常,请稍后重试!");
}
});
},
/*添加按钮点击事件*/
onAddEvet: function(){
var p = this.parentVue;
p.url = "sys/user/user_add";
p.title = "新增用户";
},
/*修改按钮点击事件*/
onUpdateEvet: function(){
var p = this.parentVue;
var t = this;
if(t.userIdList.length != 1){
p.$Message.warning('请选择一行您要修改的数据!');
}else{
p.url = "sys/user/user_add";
p.title = "修改用户"; //传参
p.userId = t.userIdList; //传参
}
},
/*删除一行按钮点击事件*/
onDeleteEvet: function(item){
var p = this.parentVue;
var t = this;
t.userIdList = []; //清空,避免数据重复
/*系统提示框*/
p.$Modal.confirm({
title: '操作提示',
content: '您确定删除所选项目吗?',
onOk: function(){
t.userIdList.push(item);
t.callDeleteUserImpl();
},
onCancel: function(){}
});
},
/*删除所有按钮点击事件*/
onDeleteAllEvet: function(){
var p = this.parentVue;
var t = this;
if(this.userIdList.length == 0){
p.$Message.warning('请选择您要删除的数据!');
}else{
/*系统提示框*/
p.$Modal.confirm({
title: '操作提示',
content: '您确定删除所选项目吗?',
onOk: function(){
t.callDeleteUserImpl();
},
onCancel: function(){}
});
}
},
/*改变页码点击事件*/
onChangePageEvet: function(item){
var t = this;
t.page = item;
t.callGetUserListImpl();
},
/*每页显示条数点击事件*/
onChangeSizeEvet: function(item){
var t = this;
t.limit = item;
t.callGetUserListImpl();
},
/*搜索按钮点击事件*/
doSearchEvet: function (){
var t = this;
t.callGetUserListImpl();
},
/*多选行数据按钮点击事件*/
onSeltChangeEvet: function(item){
var t = this;
t.userIdList = []; //清空,避免数据重复
if(item.length != 0){
//获取多选用户ID数组
for (var i = 0; i < item.length; i++){
t.userIdList.push(item[i].userId)
}
}
},
/*日期格式化方法*/
doFormatDateFun: function(date, fmt) {
var o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'h+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'S': date.getMilliseconds() // 毫秒
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
}
}
return fmt
}
},
created: function(){
var t = this;
t.callGetUserListImpl();
t.parentVue = parent.vm;
}
})
</script>
</body>
</html>
2.2 controller实现(通过shiro的权限注解控制请求注解参考)
package com.wsd.controller;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.wsd.base.BaseController;
import com.wsd.model.User;
import com.wsd.service.impl.LoginServiceImpl;
import com.wsd.service.impl.UserAndRoleServiceImpl;
import com.wsd.service.impl.UserServiceImpl;
import com.wsd.utils.ResultData;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Created by tm on 2018/8/26.
* 系统用户controller
*/
@Controller
@RequestMapping("sys/user")
public class UserController extends BaseController {
@Autowired UserServiceImpl usi; //注入系统用户service
@Autowired LoginServiceImpl lsi; //注入登录service
@Autowired UserAndRoleServiceImpl uarsi; //注入系统用户角色service
/**
* 访问user.jsp页面
* @return
*/
@RequestMapping()
public String goUserPage(){
return "user";
}
/**
* 访问user_add.jsp页面
* @return
*/
@RequestMapping("/user_add")
public String goUserAddPage(){
return "user_add";
}
/**
* 获取用户列表
* @param page 当前页码
* @param limit 每页显示条数
* @return
*/
@ResponseBody
@RequestMapping("/list")
@RequiresPermissions("sys:user:list")
public ResultData getUserList(Integer page, Integer limit, String userName){
//PageHelper分页插件
PageHelper.startPage(page, limit);
List<User> userList = usi.queryList(userName);
PageInfo<User> p = new PageInfo<User>(userList);
return ResultData.ok().put("page", p);
}
/**
* 用户信息
*/
@ResponseBody
@RequestMapping("/info")
@RequiresPermissions("sys:user:info")
public ResultData info(Long userId){
//执行查询
User userInfo = usi.queryObject(userId);
//获取用户所属的角色列表
List<Long> roleIdList = uarsi.queryRoleIdList(userId);
userInfo.setRoleIdList(roleIdList);
return ResultData.ok().put("userInfo", userInfo);
}
/**
* 保存用户
* @param user 用户实体
* @return
*/
@ResponseBody
@RequestMapping("/save")
@RequiresPermissions("sys:user:save")
public ResultData saveUsers(@RequestBody User user){
//判断用户名称是否可用
User u = lsi.queryByUserName(user.getUsername());
if(u != null){
return ResultData.error("当前用户名称不能使用!");
}
usi.save(user);
return ResultData.ok();
}
/**
* 修改用户
*/
@ResponseBody
@RequestMapping("/update")
@RequiresPermissions("sys:user:update")
public ResultData update(@RequestBody User user){
usi.update(user);
return ResultData.ok();
}
/**
* 删除用户
* @param userIdList 用户id数组
* @return
*/
@ResponseBody
@RequestMapping("/delete")
@RequiresPermissions("sys:user:delete")
public ResultData deleteUsers(@RequestBody Long[] userIdList){
if(ArrayUtils.contains(userIdList, 1L)){
return ResultData.error("系统管理员不能删除");
}
if(ArrayUtils.contains(userIdList, getUserId())){
return ResultData.error("当前用户不能删除");
}
usi.deleteUser(userIdList);
return ResultData.ok();
}
}
九、参考文档
十、获取该项目源代码
A:微信扫描下方二维码,打赏一杯咖啡钱
B:打赏的时候留下您的邮箱地址,二十四小时内通过邮箱发送给您