SpringBoot中关于Shiro权限管理的整合使用

    最近在公司项目上没有什么事情,就学习一下SpringBoot,发现SpringBoot还是挺好用的,相比原来的Spring少了好多的xml配置文件(但是SpringBoot也是支持配置文件的),通过注解的方式实现MVC的一种微服务框架。这里我把Shiro整合到SpringBoot的实现方式记录,一是方便后面自己的查阅、二是能为其他才入坑的小伙伴解决一哈问题、三是希望大神来指点一哈代码中不足之处。能搜到这篇文章,就相信大家对SpringBoot和Shiro有一定的了解,这里我就不阐述SpringBoot和Shiro是干什么的了,以及原理什么的(实际上自己也是一知半解),直接上干货。

    在整合Shiro的时候,我们先要确定一下我们的步骤:

1.加入Shiro的依赖包,实现自己的Realm类(通过继承AuthorizingRealm类);

2.实现Shiro的配置类

3.实现前端的登录界面以及Controller类

第一步:

在pom.xml中加入依赖包

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

实现Realm类

package ariky.shiro.realm;

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

import javax.servlet.http.HttpServletRequest;

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.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
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.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.web.subject.WebSubject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @ClassName:
* @Description: Realm的配置
* @author fuweilian
* @date 2018-5-12 上午11:36:41
 */
public class MyShiroRealm extends AuthorizingRealm {
	//slf4j记录日志,可以不使用
	private Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);

	/**
	 * 设置授权信息
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		logger.info("开始授权(doGetAuthorizationInfo)");
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		HttpServletRequest request = (HttpServletRequest) ((WebSubject) SecurityUtils
				.getSubject()).getServletRequest();//这个可以用来获取在登录的时候提交的其他额外的参数信息
		String username = (String) principals.getPrimaryPrincipal();//这里是写的demo,后面在实际项目中药通过这个登录的账号去获取用户的角色和权限,这里直接是写死的
		//受理权限
		//角色
		Set<String> roles = new HashSet<String>();
		roles.add("role1");
		authorizationInfo.setRoles(roles);
		//权限
		Set<String> permissions = new HashSet<String>();
		permissions.add("user:list");
		//permissions.add("user:add");
		authorizationInfo.setStringPermissions(permissions);
		return authorizationInfo;
	}

	/**
	 * 设置认证信息
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authenticationToken) throws AuthenticationException {
		logger.info("开始认证(doGetAuthenticationInfo)");
		//UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
		HttpServletRequest request = (HttpServletRequest) ((WebSubject) SecurityUtils
				.getSubject()).getServletRequest();
		UsernamePasswordToken token = new UsernamePasswordToken (request.getParameter("userName"),request.getParameter("password"));
		//获取用户输入的账号
		String userName = (String)token.getPrincipal();
		//通过userName去数据库中匹配用户信息,通过查询用户的情况做下面的处理
		//这里暂时就直接写死,根据登录用户账号的情况做处理
		logger.info("账号:"+userName);
		if("passwordError".equals(userName)){//密码错误
			throw new IncorrectCredentialsException(); 
		}else if("lockAccount".equals(userName)){// 用户锁定
			throw new LockedAccountException(); 
		}else{
			SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
		            userName, //用户名
		            "123456", //密码,写死
		            ByteSource.Util.bytes(userName+"salt"),//salt=username+salt
		            getName()  //realm name
		    );
		    return authenticationInfo;
		}
	}
	
}

第二步 实现Shiro的配置类

package ariky.shiro.configuration;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import ariky.shiro.realm.MyShiroRealm;

/**
* @ClassName: ShiroConfiguration 
* @Description: shiro的配置类 
* @author fuweilian
* @date 2018-5-12 上午11:05:09
 */
@Configuration
public class ShiroConfiguration {
	private static Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
		logger.info("进入shiroFilter......");
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		//设置不需要拦截的路径
		Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
		//按顺序依次判断
		filterChainDefinitionMap.put("/static/**", "anon");
		//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        /************************************初始化所有的权限信息开始******************************************/
        //这里,如果以后再项目中使用的话,直接从数据库中查询
        filterChainDefinitionMap.put("/user/list", "authc,perms[user:list]");
        //filterChainDefinitionMap.put("/user/add", "authc,perms[user:add]");
        /***************************************初始化所有的权限信息开始结束*********************************************/
        filterChainDefinitionMap.put("/**", "authc");
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //未授权界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;
	}
	
	@Bean
	public MyShiroRealm myShiroRealm(){
		 MyShiroRealm myShiroRealm = new MyShiroRealm();
		 //后面这里可以设置缓存的机制
	     return myShiroRealm;
	} 
	
	@Bean
	public SecurityManager securityManager(){
		DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
	}
	
	
	@Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
	
}

第三步:实现Controoler类,这里写俩个类,一个是登录信息的LoginController处理类,一个是测试权限用的UserController

1.LoginController.java

package ariky.controller;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
* @ClassName: LoginController 
* @Description: 登录控制的controller
* @author fuweilian
* @date 2018-5-12 下午01:15:46
 */
@RequestMapping
@Controller
public class LoginController {
	private Logger logger = LoggerFactory.getLogger(LoginController.class);
	
	@RequestMapping(value="/login",method=RequestMethod.GET)
	public String getLogin(){
		logger.info("进入login页面");
		return "login";
	}
	
	@RequestMapping(value="/login",method=RequestMethod.POST)
	public String doLogin(HttpServletRequest req,Map<String, Object> model){
		logger.info("进入登录处理");
		String exceptionClassName = (String) req.getAttribute("shiroLoginFailure");
		logger.info("exceptionClassName:"+exceptionClassName);
		String error = null;
		if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
			error = "用户名/密码错误";
		} else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
			error = "用户名/密码错误";
		}else if(LockedAccountException.class.getName().equals(exceptionClassName)){ 
			error = "用户已锁定或已删除";
		}else if (exceptionClassName != null) {
			error = "其他错误:" + exceptionClassName;
		}
		if(SecurityUtils.getSubject().isAuthenticated()){//没有错误,但是已经登录了,就直接跳转到welcom页面
			model.put("name", req.getParameter("userName"));
			return "index";
		}else{//有错误的
			model.put("error", error);
			return "login";
		}
	}
	@RequestMapping("/index")
	public String index(){
		return "index";
	}
}

2.UserController.java

package ariky.controller;

import java.util.ArrayList;
import java.util.List;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

/**
* @ClassName: UserController 
* @Description: 用户处理Controller
* @author fuweilian
* @date 2018-5-12 下午03:11:06
 */
@Controller
@RequestMapping("/user")
public class UserController {
	Logger logger = LoggerFactory.getLogger(UserController.class);
	@RequiresPermissions("user:list")//这个是配置是否有该权限的,如果是按上面的写法,这个是有权限的
	@RequestMapping(value="/list",method=RequestMethod.GET)
	public String getList(){
		logger.info("进入用户列表");
		return "user/list";
	}
	@RequiresPermissions(value={"user:add"})//这个是没有权限的
	@RequestMapping(value="/add",method=RequestMethod.GET)
	public String getAdd(){
		logger.info("进入新增用户界面");
		return "user/add";
	}
	
}

前端界面:有5个界面 (login.jsp,index.jsp,list.jsp,add.jsp,403.jsp)

目录结构为:

1.login.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Login</title>
</head>

<body>
    <h1>登录页面----${error}</h1>
    <form:form action="${pageContext.request.contextPath }/login"
         method="post">
        用户名:<input type="text" name="userName">
        <br />
        密码:<input type="passwordParam" name="password"/>
        <input type="submit" value="提交"/>
    </form:form>
</body>
</html>

2.index.jsp

<%@ page language="java" pageEncoding="UTF-8"%>
 <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>第一个例子</title>
<script src="${pageContext.request.contextPath }/webjars/jquery/2.1.4/jquery.js"></script>
<script src="${pageContext.request.contextPath }/webjarslocator/jquery/jquery.js"></script>
</head>

<body>
    <h1>${name}:你好,欢迎访问该网页</h1>
    	<shiro:hasPermission name="user:list"><!-- 这个a标签是可以看见的 -->
    		<a href="${pageContext.request.contextPath }/user/list" target="_blank">跳转到用户列表(有权限)</a>
    	</shiro:hasPermission>
    <br/>
    	<shiro:hasPermission name="user:add"><!-- 这个a标签是看不见的 -->
    		<a href="${pageContext.request.contextPath }/user/add" target="_blank">跳转到新增用户列表(无权限)</a>
		</shiro:hasPermission>
</body>
</html>

3.list.jsp和add.jsp以及403.jsp都差不多一样,这里就写一个,这里只是demo所用,在实际项目中,要以实际项目为准

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>userList</title>
</head>

<body>
    <h1>用户列表信息</h1>
</body>
</html>

上面就是全部代码了,如果启动成功,进入login登录界面就可以测试一下shiro的权限认证了。上面的代码都是写死的,如果想要实现动态的权限管理和用户的权限管理的话,还要做一些其他处理,用户的动态权限这个只要在自己的ShiroRealm类里面授权的时候做一下查询数据库,动态的授权和角色就行。关于动态的权限管理的话,下面的方式可以实现,在修改完权限数据后,更新一下shiro里面的配置就行,具体看下面的代码,这里是demo,不是实际项目,在实际项目中最好不要把逻辑写在Controller里面

package ariky.shiro.controller;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
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.ResponseBody;

/**
* @ClassName: PermssionController 
* @Description: 权限操作的controller 
* @author fuweilian
* @date 2018-5-12 下午04:59:15
 */
@Controller
@RequestMapping("permssion")
public class PermssionController {
	
	 @Autowired
	 ShiroFilterFactoryBean shiroFilterFactoryBean;
	 
	 /**
	 * @Title: updatePermssion 
	 * @author: fuweilian
	 * @Description: 这里暂时直接写在controller里面,,不按规则写了,,到时候在项目中使用的时候,才写
	 * @return  参数说明 
	 * @return Object    返回类型 
	 * @throws
	  */
	@RequestMapping("/updatePermssion")
	@ResponseBody
	public Object updatePermssion(){
		synchronized (shiroFilterFactoryBean){
			AbstractShiroFilter shiroFilter = null;
            try {
                shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean
                        .getObject();
                PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter
                	.getFilterChainResolver();
                DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver
                	.getFilterChainManager();
                // 清空老的权限控制
                manager.getFilterChains().clear();
                shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
                //后面这个可以直接从数据库里面获取
        		Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        		//按顺序依次判断
        		filterChainDefinitionMap.put("/static/**", "anon");
        		//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
                filterChainDefinitionMap.put("/logout", "logout");
                //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
                /************************************初始化所有的权限信息开始******************************************/
                //这里,如果以后再项目中使用的话,直接从数据库中查询
                filterChainDefinitionMap.put("/user/list", "authc,perms[user:list]");
                filterChainDefinitionMap.put("/user/add", "authc,perms[user:add]");
                /***************************************初始化所有的权限信息开始结束*********************************************/
                filterChainDefinitionMap.put("/**", "authc");
                //
                shiroFilterFactoryBean.setLoginUrl("/login");
                // 登录成功后要跳转的链接
                shiroFilterFactoryBean.setSuccessUrl("/index");
                //未授权界面
                shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
                shiroFilterFactoryBean
                	.setFilterChainDefinitionMap(filterChainDefinitionMap);
                // 重新构建生成
                Map<String, String> chains = shiroFilterFactoryBean
                        .getFilterChainDefinitionMap();
                for (Map.Entry<String, String> entry : chains.entrySet()) {
                    String url = entry.getKey();
                    String chainDefinition = entry.getValue().trim()
                            .replace(" ", "");
                    manager.createChain(url, chainDefinition);
                }
                return "更新权限成功";  
            } catch (Exception e) {
                throw new RuntimeException(
                        "更新shiro权限出现错误!");
            }
		}
	}

}

最后附上数据库关于角色权限的表结构,实现动态的代码就自己完成。如果有不理解的,欢迎大家留言,一起讨论学习哈。大笑大笑大笑

下面是mysql库的表结构

/*
Navicat MySQL Data Transfer

Source Server         : arikyDB
Source Server Version : 50721
Source Host           : 47.106.95.168:3306
Source Database       : ariky

Target Server Type    : MYSQL
Target Server Version : 50721
File Encoding         : 65001

Date: 2018-05-14 16:05:51
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for common_permssion
-- ----------------------------
DROP TABLE IF EXISTS `common_permssion`;
CREATE TABLE `common_permssion` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `NAME` varchar(255) DEFAULT NULL COMMENT '权限名称',
  `TYPE` varchar(255) DEFAULT NULL COMMENT '类型按钮(button)或者菜单(menu) ',
  `PARENT_ID` int(11) DEFAULT NULL COMMENT '上级ID',
  `PARENT_IDS` varchar(255) DEFAULT NULL COMMENT '上级PIDs',
  `URL` varchar(255) DEFAULT NULL COMMENT '访问路径',
  `ICONCLS` varchar(255) DEFAULT NULL COMMENT '图标(可以不要)',
  `PERMISSION` varchar(255) DEFAULT NULL COMMENT '权限(如user:list)',
  `ORDER_NUM` int(11) DEFAULT NULL COMMENT '排序',
  `REMARK` varchar(255) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8 COMMENT='该表用来存储资源权限信息';

-- ----------------------------
-- Table structure for common_role
-- ----------------------------
DROP TABLE IF EXISTS `common_role`;
CREATE TABLE `common_role` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `LABEL_ID` varchar(255) DEFAULT NULL COMMENT '标签Id',
  `NAME` varchar(255) DEFAULT NULL COMMENT '角色名称',
  `ROLE` varchar(255) DEFAULT NULL,
  `DESCRIPTION` varchar(255) DEFAULT NULL,
  `IS_SHOW` int(11) DEFAULT '1' COMMENT '判断该角色是否在使用(1:使用,2:禁用)',
  `IS_HANDLER` int(2) DEFAULT NULL COMMENT '判断是什么角色(1:后台角色,2:商家管理员角色,3:商家添加用户角色,4:游客角色)',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8 COMMENT='角色表';

-- ----------------------------
-- Table structure for common_role_permssion
-- ----------------------------
DROP TABLE IF EXISTS `common_role_permssion`;
CREATE TABLE `common_role_permssion` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `ROLE_ID` int(11) DEFAULT NULL COMMENT '角色Id',
  `RESOURCE_ID` int(11) DEFAULT NULL COMMENT '资源(权限)Id',
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=493 DEFAULT CHARSET=utf8 COMMENT='角色资源权限表中间表';


猜你喜欢

转载自blog.csdn.net/fuweilian1/article/details/80309192