Shiro权限控制(七):Spring整合Shiro做权限控制综合实践-按钮层级-超详细

前言

本文是Spring整合Shiro进行细粒度权限控制的综合实训,主要内容包括:

一、Spring中引入Shiro框架
二、Shiro登录控制
三、权限表设计
四、服务权限控制
五、自定义标签控制页面(HTML)按钮权限

开始正文之前,先介绍一下我的工程环境:Spring+Spring MVC+Mybatis+MySql+Maven,下面从Spring引入Shiro框架开始本文的介绍,文章有点长,很详细,请耐心阅读

一、Spring中引入Shiro框架

1.首先在pom.xml文件中引入Shiro相关依赖

    <!-- shiro 包-->
	<dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.2.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>1.2.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-ehcache</artifactId>
        <version>1.2.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-quartz</artifactId>
        <version>1.2.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.2.2</version>
    </dependency>

2.创建spring-shiro-web.xml配置文件

在工程的config目录下创建Shiro的配置文件spring-shiro-web.xml,Shiro相关的配置,就放到这个文件中,例如自定义的Realm,登录拦截的Filter,服务权限控制等,我们先放一个空文件

<?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:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:p="http://www.springframework.org/schema/p"
	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-4.0.xsd
           http://www.springframework.org/schema/tx 
           http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    
</beans>

web.xml文件中引入spring-shiro-web.xml

项目启动时要加载Shiro相关配置,因此需要在web.xml文件中引入spring-shiro-web.xml

<!-- Spring配置 -->
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
		classpath:config/applicationContext.xml,
		classpath:config/spring-shiro-web.xml
	</param-value>
</context-param>

4.web.xml中配置Shiro过滤器

Shiro是基于过滤器来进行权限控制的,因此需要在web.xml中配置Shiro的过滤器

    <!-- shiro filter start -->
	<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>
	<!-- shiro filter end -->

需要注意的是,这里的过滤器名称shiroFilter需要在spring-shiro-web.xml中存在,因此在spring-shiro-web.xml中增加shiroFilter的配置

    <!-- Shiro的web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    	<property name="securityManager" ref="securityManager"></property>
    	<property name="filterChainDefinitions">
    		<value>
    			/index.jsp = anon
                /unauthorized.jsp = anon
                /user/checkLogin = anon
                /user/queryUserInfo = authc
                /user/deleteUser/** = authc,perms[CUSTOMER_USER:DELETE]
    		</value>
    	</property>
    </bean>

补充说明:

  • 在securityManager属性标签中,需要引用到SecurityManager
  • 在filterChainDefinitions属性标签可以配置服务的访问权限,value值说明如下:
    /user/checkLogin = anon表示不需要Shiro权限控制就可以访问
    /user/queryUserInfo = authc表示需要登录认证才可以访问
    /user/deleteUser/** = authc,perms[CUSTOMER_USER:DELETE]表示需要登录后,并拥有角色CUSTOMER_USER的DELETE权限才可访问
  • 在本文中,我们的服务权限是配置在数据库中,后文会有详细的讲解

因此在spring-shiro-web.xml文件中,还需要定义SecurityManager,loginCheckPermissionFilter,及permissionsAuthorizationFilter

     <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    	<property name="realm" ref="userShiroRealm"/>
    </bean>
    <!-- 自定义域 -->
    <bean id="userShiroRealm" class="com.bug.realm.UserShiroRealm">
    	<property name="credentialsMatcher" ref="credentialsMatcher"/>
    	<property name="cachingEnabled" value="true"/>
    </bean>
    <!-- 自定义登录验证过滤器 -->
    <bean id="loginCheckPermissionFilter" class="com.bug.filter.LoginCheckPermissionFilter"></bean>
    <!-- 自定义权限认证器 -->
    <!--
    <bean id="permissionsAuthorizationFilter" class="com.bug.filter.PermissionsAuthorizationFilter"></bean>
    -->
         <!-- 自定义凭证(密码)匹配器 -->
    <bean id="credentialsMatcher" class="com.bug.credentials.BugCredentialsMatcher"></bean>

补充说明:

  • 在Shiro权限控制中,securityManager是负责权限的管理,但是真正的权限的校验是交给Realm去处理,用户可以自定义Realm
  • 在自定义的userShiroRealm中,引用了自定义的身份匹配器credentialsMatcher,用户是否登录认证通过,通过credentialsMatcher实现

到此,整个spring-shiro-web.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" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:p="http://www.springframework.org/schema/p"
	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-4.0.xsd
           http://www.springframework.org/schema/tx 
           http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    	<property name="realm" ref="userShiroRealm"/>
    </bean>
    
    <!-- 自定义域 -->
    <bean id="userShiroRealm" class="com.bug.realm.UserShiroRealm">
    	<property name="userService" ref="userService"/>
    	<property name="credentialsMatcher" ref="credentialsMatcher"/>
    	<property name="cachingEnabled" value="true"/>
    </bean>
    
     <!-- 自定义凭证(密码)匹配器 -->
    <bean id="credentialsMatcher" class="com.bug.credentials.BugCredentialsMatcher"></bean>

    <!-- Shiro的web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    	<property name="securityManager" ref="securityManager"></property>
    	<property name="filterChainDefinitions">
    		<value>
                /user/checkLogin = anon
                /user/queryUserInfo = authc
                /user/deleteUser/** = authc,perms[CUSTOMER_USER:DELETE]
    		</value>
    	</property>
    </bean>
    
    <!-- 启用Shiro注解,如果不用注解,使用shiroFilter中设置的规则也可以权限控制 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
    
</beans>

5.自定义Realm

Realm主要用于登录前的身份校验,如果登录成功,再进行权限的校验,在上面的配置中,我们注入了自定义的UserShiroRealm,此Realm需要继承AuthorizingRealm,并实现doGetAuthorizationInfo权限验证方法和doGetAuthenticationInfo身份验证方法,,代码实现如下

package com.bug.realm;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

import com.bug.excption.BugException;
import com.bug.model.user.UserPermissionVO;
import com.bug.model.user.UserVO;
import com.bug.service.user.IUserService;

public class UserShiroRealm extends AuthorizingRealm{
	
	private IUserService userService;
	
	public void setUserService(IUserService userService) {
		this.userService = userService;
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		String userName = (String)principals.getPrimaryPrincipal();
		if(userName == null) {
			throw new BugException("未登录");
		}
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		return info;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken autToken) throws AuthenticationException {

		UsernamePasswordToken userPwdToken = (UsernamePasswordToken) autToken;
		String userName = userPwdToken.getUsername();
        // 根据用户名查询数据库中用户的账号密码
		UserVO user = userService.selectUserByUserName(userName);
		if (null == user) {
			throw new BugException("未知账号");
		}

		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(),
				user.getPassword().toCharArray(), getName());

		return authenticationInfo;
	}
}

补充说明:

  • doGetAuthenticationInfo方法:Shiro进行身份验证时,会调用到doGetAuthenticationInfo方法,在方法内部,我们通过UsernamePasswordToken 获得用户传过来的用户名,再通过userService.selectUserByUserName方法从数据库中查询用户信息,如果用户为空,说账号不存在,否则将查询出来的用户名及密码,封装到SimpleAuthenticationInfo 对象中,并返回,用于接下来的密码验证
  • doGetAuthorizationInfo方法:Shiro角色权限验证,会调用doGetAuthorizationInfo方法,后面进行服务权限控制时会用到此方法,这里先不做任何处理

6.自定义CredentialsMatcher凭证(密码)匹配器*

此过滤器主要用于凭证(密码)匹配,即校验用户输入的密码和从数据库中查询的密码是否相同,相同则返回true,否则返回false,此匹配器继承了SimpleCredentialsMatcher,并重写doCredentialsMatch方法,代码如下

package com.bug.credentials;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.util.SimpleByteSource;

/**
 * 自定义凭证(密码)匹配器
 * @author longwentao
 *
 */
public class BugCredentialsMatcher extends SimpleCredentialsMatcher {

	@Override
	public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
		// 对前台传入的明文数据加密,根据自定义加密规则加密
		Object tokencredential = new SimpleByteSource((char[]) token.getCredentials());
		// 从数据库获取的加密数据
		Object accunt = new SimpleByteSource((char[]) info.getCredentials());
		// 返回对比结果
		return equals(accunt, tokencredential);
	}
}

7.工程结构

到此Shiro配置相关的初始化工作已经完成,工程结构如下
在这里插入图片描述

二、Shiro登录控制

在上面的基础配置中,已经把登录校验用到的自定义UserShiroRealm及BugCredentialsMatcher都介绍完成了,这里只需要写一个登录的服务,并在服务中使用Shiro即可

1.增加登录服务

新增UserController,在controller中增加登录服务checkLogin

@Controller
@RequestMapping("/user")
public class UserController extends BaseController{
	private final static Logger logger = LoggerFactory.getLogger(UserController.class);

	@Autowired
	private IUserService userService;
	
	@RequestMapping(value = "/checkLogin", method = RequestMethod.POST)
	@ResponseBody
	public ResponseVO<UserVO> checkLogin(@RequestBody UserVO user) {
		ResponseVO<UserVO> response = new ResponseVO<UserVO>();
		try {
			UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
			Subject subject = SecurityUtils.getSubject();
			subject.login(token);
			
			UserVO u = new UserVO();
			response.setData(u);
		}catch (Exception e) {
			logger.error("Login Error:",e);
			response.setStatus(ResponseVO.failCode);
			Throwable ex = e.getCause();
			if(ex instanceof BugException) {
				if(ex.getMessage() != null) {
					response.setMessage(ex.getMessage());
				}
			}else if(e instanceof IncorrectCredentialsException) {
				response.setMessage("密码错误");
			}else {
				response.setMessage("登录失败");
			}
		}

		return response;
	}
}

补充说明:
1.在UserController中继承了BaseController,主要是为了处理异常,没有权限时Shiro框架抛出的未登录异常和没有权限异常通过BaseController中定义的方法捕获,代码如下

package com.bug.controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ExceptionHandler;

import com.bug.common.JSONParseUtils;

public abstract class BaseController {
	/**
	 * 登录认证异常
	 * 
	 * @param request
	 * @param response
	 * @return
	 */
	@ExceptionHandler({ UnauthenticatedException.class, AuthenticationException.class })
	public String authenticationException(HttpServletRequest request, HttpServletResponse response) {
		Map<String, Object> map = new HashMap<>();
		map.put("status", "-1000");
		map.put("message", "未登录");
		writeJson(map, response);
		return null;
	}

	/**
	 * 权限异常
	 * 
	 * @param request
	 * @param response
	 * @return
	 */
	@ExceptionHandler({ UnauthorizedException.class, AuthorizationException.class })
	public String authorizationException(HttpServletRequest request, HttpServletResponse response) {
		Map<String, Object> map = new HashMap<>();
		map.put("status", "-1001");
		map.put("message", "无权限");
		writeJson(map, response);
		return null;
	}

	private void writeJson(Map<String, Object> map, HttpServletResponse response) {
		PrintWriter out = null;
		try {
			response.setCharacterEncoding("UTF-8");
			response.setContentType("application/json; charset=utf-8");
			out = response.getWriter();

			out.write(JSONParseUtils.readJsonString(map));
		} catch (IOException e) {
		} finally {
			if (out != null) {
				out.close();
			}
		}
	}
}

2.登录HTML页面及JS

增加登录页面login.html对应用的login.js

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery/jquery-3.4.1.js"></script>
<script type="text/javascript" src="js/jquery/jquery-migrate-1.4.1.js"></script>
<script type="text/javascript" src="js/jquery/jquery-session.js"></script>
<script type="text/javascript" src="login.js"></script>
</head>
<body>
	用户名:<input id="userName" type="text" name="userName"><br/>
	    密码:<input id="password" type="password" name="password"><br/>
	    <input id="login" type="submit" value="登录">
</body>
</html>
$(function() {
	$(document).ready(function(){
		bindEvent();
	});
	
	function bindEvent(){
		$("#login").bind("click",login);
	}
	function login(){
		var data = {
			userName:$("#userName").val(),
			password:$("#password").val()
		}
		var request = $.ajax({
			cache : false,
			async : false,
			url : "/bug.web/user/checkLogin",
			contentType : "application/json; charset=utf-8",
			method : "post",
			data : JSON.stringify(data, null, ""),
			dataType : 'json'
		});
		
		request.done(function(data) {
			if(data.status == "0"){
				window.location.href = "/bug.web/html/user/userManager.html";
			}else{
				alert(data.message);
			}
		});
	}
});

3.验证

访问http://localhost:8080/bug.web/html/login.html,输入账号密码,成功则重定向到userManager.html页面,失败则提示错误信息

三、权限表设计

  • sys_role_t:角色表,例如系统管理员,区域管理员
  • sys_role_perm_t:角色权限表,表示该角色拥有什么样的权限,如增、删、改、查权限
  • user_role_perm_t:用户角色权限关系表,表示用户拥有什么角色的什么权限,如张三拥有区域管理员的查询权限
  1. 在角色表sys_role_t中初始化三个角色,分别为系统管理员,普通用户,区域管理员
    在这里插入图片描述
  2. 在角色权限表sys_role_perm_t中初始化普通用户,区域管理员具有增、删、改、查权限
    在这里插入图片描述
  3. 在用户角色权限关系表user_role_perm_t中配置user_id=1的用户,有普通用户角色的查询、编辑、新增权限;有区域管理员角色的删除权限
    在这里插入图片描述

四、服务权限控制

在配置服务权限之前,需要先增加一个登录验证器LoginCheckPermissionFilter和权限访问器PermissionsAuthorizationFilter,登录验证器用于校验用户是否登录,如果未登录,则拒绝访问;权限访问器用于校验用户是否既有访问此URL的权限

1.LoginCheckPermissionFilter
此过滤器继承了AuthorizationFilter,并重写了isAccessAllowed方法和onAccessDenied方法,代码如下

package com.bug.filter;

import java.io.IOException;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 自定义登录验证过滤器
 * @author longwentao
 *
 */
public class LoginCheckPermissionFilter extends AuthorizationFilter {
	private final static Logger logger = LoggerFactory.getLogger(LoginCheckPermissionFilter.class);

	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object arg2) throws Exception {
		HttpServletRequest req = (HttpServletRequest) request;
		String url = req.getRequestURI();
		try {
			Subject subject = SecurityUtils.getSubject();

			return subject.isPermitted(url);
		} catch (Exception e) {
			logger.error("Check perssion error", e);
		}
		return false;
	}

	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
		Subject subject = getSubject(request, response);
		if (subject.getPrincipal() == null) {
			saveRequestAndRedirectToLogin(request, response);
		} else {
			return true;
		}
		return false;
	}
}

2.PermissionsAuthorizationFilter
此过滤器继承了AuthorizationFilter,重写了isAccessAllowed方法和onAccessDenied方法,代码如下

package com.bug.filter;

import java.io.IOException;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

import com.bug.common.JSONParseUtils;
import com.bug.model.common.ResponseVO;

/**
 * 权限校验过滤器
 * @author longwentao
 *
 */
public class PermissionsAuthorizationFilter extends AuthorizationFilter {

	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
			throws Exception {
		Subject subject = getSubject(request, response);
		String[] perms = (String[]) mappedValue;

		boolean isPermitted = true;
		if (perms != null && perms.length > 0) {
			if (perms.length == 1) {
				if (!subject.isPermitted(perms[0])) {
					isPermitted = false;
				}
			} else {
				if (!subject.isPermittedAll(perms)) {
					isPermitted = false;
				}
			}
		}

		return isPermitted;
	}
	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		httpServletResponse.setHeader("Content-Type", "application/json;charset=UTF-8");
		ResponseVO<String> responseVO = new ResponseVO<String>();
		responseVO.setStatus(ResponseVO.failCode);
		responseVO.setMessage("没有权限,请联系管理员");
		
		byte[] bytes = JSONParseUtils.readJson2Byte(responseVO);
		httpServletResponse.getOutputStream().write(bytes);
		return false;
	}
}

3.在spring-shiro-web.xml中引入上面定义的登录验证器LoginCheckPermissionFilter和权限访问器PermissionsAuthorizationFilter

<!-- 自定义登录验证过滤器 -->
<bean id="loginCheckPermissionFilter" class="com.bug.filter.LoginCheckPermissionFilter"></bean>
 
 <!-- 自定义权限认证器 -->
 <bean id="permissionsAuthorizationFilter" class="com.bug.filter.PermissionsAuthorizationFilter"></bean>
 
 <!-- Shiro的web过滤器 -->
 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
 	<property name="securityManager" ref="securityManager"></property>
 	<property name="filters">
 		<map>
 			<entry key="authc" value-ref="loginCheckPermissionFilter"></entry>
 			<entry key="perms" value-ref="permissionsAuthorizationFilter"></entry>
 		</map>
 	</property>
 	<property name="filterChainDefinitions">
 		<value>
             /user/checkLogin = anon
             /user/queryUserInfo = authc
             /user/deleteUser/** = authc,perms[CUSTOMER_USER:DELETE]
 		</value>
 	</property>
 </bean>

4.修改UserShiroRealm中的doGetAuthorizationInfo方法,从数据库中查询用户权限,并交给Shiro

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
	String userName = (String)principals.getPrimaryPrincipal();
	if(userName == null) {
		throw new BugException("未登录");
	}
	SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
	Set<String> roles = new HashSet<String>();
	Set<String> stringPermissions = new HashSet<String>();
	List<UserPermissionVO> list = userService.queryUserPermission(userName);
	for (UserPermissionVO userPermissionVO : list) {
		roles.add(userPermissionVO.getRoleCode());
		stringPermissions.add(userPermissionVO.getPermCodes());
	}
	
	//roles.add("USER");
	//stringPermissions.add("USER:DELETE");//角色:删除权限
	info.setRoles(roles);
	info.setStringPermissions(stringPermissions);
	
	return info;
}

5.修改UserController中的登录方法,登录校验成功后,查询用户的角色权限,并返回给前台

@RequestMapping(value = "/checkLogin", method = RequestMethod.POST)
@ResponseBody
public ResponseVO<UserVO> checkLogin(@RequestBody UserVO user) {
	ResponseVO<UserVO> response = new ResponseVO<UserVO>();
	try {
		UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
		
		Subject subject = SecurityUtils.getSubject();

		subject.login(token);
		// 成功则查询用户权限保存到前台
		List<UserPermissionVO> list = userService.queryUserPermission(user.getUserName());
		UserVO u = new UserVO();
		u.setPermessionList(list);
		response.setData(u);
	}catch (Exception e) {
		logger.error("Login Error:",e);
		response.setStatus(ResponseVO.failCode);
		Throwable ex = e.getCause();
		if(ex instanceof BugException) {
			if(ex.getMessage() != null) {
				response.setMessage(ex.getMessage());
			}
		}else if(e instanceof IncorrectCredentialsException) {
			response.setMessage("密码错误");
		}else {
			response.setMessage("登录失败");
		}
	}

	return response;
}

6.在UserController中增加addUser服务,并指定拥有CUSTOMER_USER角色的增加权限或AREA_MANAGER角色的增加权限才可访问

@ResponseBody
@RequiresPermissions(value = { "CUSTOMER_USER:ADD","AREA_MANAGER:ADD" },logical=Logical.OR) // 加上注解,默认需要登录
@RequestMapping(value = "/addUser", method = RequestMethod.GET)
public ResponseVO<String> addUser() {
	ResponseVO<String> response = new ResponseVO<String>();
	try {
		response.setMessage("add user success");
	} catch (Exception e) {
		logger.error("add user error:", e);
		response.setStatus(ResponseVO.failCode);
	}

	return response;
}

7.验证
未登录,直接访问http://localhost:8080/bug.web/user/addUser,报未登录错误
在这里插入图片描述
删除用户的CUSTOMER_USER角色的增加权限和AREA_MANAGER角色的增加权限,登录成功后再访问http://localhost:8080/bug.web/user/addUser,报没有权限错误
在这里插入图片描述
增加用户的CUSTOMER_USER角色的增加权限或AREA_MANAGER角色的增加权限,登录成功后再访问http://localhost:8080/bug.web/user/addUser,请求成功
在这里插入图片描述

五、自定义标签控制页面(HTML)按钮权限

服务端的权限上面已经介绍完成,有时候我们需要控制页面上的按钮权限,有权限则显示,无权限则不显示,则于我用的是HTML,Shiro权限标签不生效,但是我又想用标签方式根据权限控制按钮的显示与隐藏,因此我自定义了一套HTML的权限标签

1.自定义权限标签
user:hasPermission:权限标签,满足所有权限才能访问
例:<button user:hasPermission="USER:DELETE" type="button">删除用户</button>
表示有角色名称为USER的DELETE权限才能访问

user:hasRole:角色标签,满足所有角色才能访问
例:<button user:hasRole="USER"> 有 USER 角色权限</button>
表示有角色名称为USER的权限才能访问

user:hasAnyRoles:角色标签,只需要有指定角色中的任何一个就可以访问
例:<button user:hasAnyRoles="SALES,ADMINISTRATOR" type="button">超级管理员</button>
表示只需要有SALES或ADMINISTRATOR任何一个就可以访问

任何HTML标签,只要加了上面的自定义标签,就可以根据权限控制按钮是否显示

2.权限标签控制组件
自定义一个解析权限标签的公共组件user.permession.plugin.js,解析标签中指定的权限值,和后端返回的用户权限做对比,如果有权限,按钮显示,没有权限,删除按钮

$(function() {
	$(document).ready(function(){
		// 取用后端返回的用户权限数据
		var userStr = window.sessionStorage.getItem("User");
		var userData = JSON.parse(userStr);
		
		// 权限数组,如 USER:DELETE,CUSTOMER_USER:ADD
		var perArray = [];
		
		// 角色数组,如 USER,ADMINISTRATOR
		var roleArray = [];
		for(var i = 0;i < userData.permessionList.length;i++){
			perArray.push(userData.permessionList[i].permCodes);
			roleArray.push(userData.permessionList[i].roleCode)
		}
		// 解析页面上所有的user:hasPermission 标签,如果没有权限,则删除页面元素
		$('[user\\:hasPermission]').each(function(){
			var permission = $(this).attr("user:hasPermission");
			// 如果没有权限,则删除页面元素
			if(-1 == $.inArray(permission,perArray)){
				$(this).remove();
			}
		});
		
		// 解析页面上所有的user:hasRole 标签,如果没有权限,则删除页面元素
		$('[user\\:hasRole]').each(function(){
			var role = $(this).attr("user:hasRole");
			// 如果没有权限,则删除页面元素
			if(-1 == $.inArray(role,roleArray)){
				$(this).remove();
			}
		});
		
		// 解析页面上所有的user:hasAnyRoles 标签,如果没有权限,则删除页面元素
		$('[user\\:hasAnyRoles]').each(function(){
			var hasRole = false;
			var roles = $(this).attr("user:hasAnyRoles").split(",");
			for(var i = 0;i < roles.length;i ++){
				if(0 <= $.inArray(roles[i],roleArray)){
					hasRole = true;
					break;
				}
			}
			// 如果没有权限,则删除页面元素
			if(!hasRole){
				$(this).remove();
			}
		});
	});
});

3.验证
创建userManager.html页面,引入user.permession.plugin.js组件,在按钮中通过权限标签指定相应的权限码,用户登录成功后,自动跳转到此页面,有权限的按钮显示,没有权限的按钮不显示

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">  
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="../js/jquery/jquery-3.4.1.js"></script>
<script type="text/javascript" src="../js/jquery/jquery-migrate-1.4.1.js"></script>
<script type="text/javascript" src="../js/jquery/jquery-session.js"></script>
<script type="text/javascript" src="../js/user.permession.plugin.js"></script>
</head>
<body>
	<button user:hasPermission="USER:DELETE"  type="button">删除用户</button>
	
	<button user:hasPermission="CUSTOMER_USER:ADD"  type="button">新增用户</button>
	
	<p user:hasRole="USER"> 有  USER 角色权限</p>
	
	<button user:hasAnyRoles="SALES,CUSTOMER_USER"  type="button">用户管理</button>
	
	<button user:hasAnyRoles="SALES,ADMINISTRATOR"  type="button">超级管理员</button>
</body>
</html>

修改前台介绍过的login.js,成功成功后,将后台返回的用户权限保存到sessionStorage中

request.done(function(data) {
	if(data.status == "0"){
		// 将用户信息保存到全局变量中
		window.sessionStorage.setItem("User",JSON.stringify(data.data, null, ""));
		window.location.href = "/bug.web/html/user/userManager.html";
	}else{
		alert(data.message);
	}
});

登录成功后,跳转到userManager.html页面,没有权限的按钮不显示
在这里插入图片描述
到此,基于Spring整合Shiro的细粒度的权限控制就完成了,以上代码,均来至于项目实践,本人亲自操刀,如有问题,欢迎留言交流,多谢

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值