ssm+shiro细颗粒权限

shiro细颗粒权限说白了就是url的判断,精确到增删改查. 本项目在ssm的基础上集成了shiro.  web端可以参考 shiro教程 上面提到了shiro标签,可以用来前端的权限. 

applicationContent-shiro.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"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <!-- 声明一个密码匹配器 -->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <!-- 设置该密码匹配器使用的算法是 md5 -->
        <property name="hashAlgorithmName" value="md5"/>
    </bean>

    <!-- 声明一个自定义的 Realm -->
    <bean id="myRealm" class="cn.com.ecenter.system.realm.MyRealm">
        <!-- 将上面声明的密码匹配器注入到自定义 Realm 的属性中去 -->
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
        <!-- 将自定义的权限匹配器注入到自定义 Realm 中 -->
        <property name="permissionResolver" ref="permissionResolver"/>

        <!-- 配置缓存相关 -->
        <!-- 启用缓存 -->
        <property name="cachingEnabled" value="true"/>
        <!-- 开启认证缓存-->
        <property name="authenticationCachingEnabled" value="true"/>
        <!-- 指定认证缓存的名字(与 ehcache.xml 中声明的相同) -->
        <property name="authenticationCacheName" value="shiro-authenticationCache"/>
        <!--开启授权缓存-->
        <property name="authorizationCachingEnabled" value="true"/>
        <!-- 指定授权缓存的名字(与 ehcache.xml 中声明的相同) -->
        <property name="authorizationCacheName" value="shiro-authorizationCache"/>
    </bean>

    <!-- 自定义一个权限匹配器 -->
    <bean id="permissionResolver" class="cn.com.ecenter.system.permission.UrlPermissionResolver"/>

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!-- 如果认证不通过,浏览器通过 Get 方式请求到 /login 上 -->
        <property name="loginUrl" value="/login"/>

        <!-- override these for application-specific URLs if you like:

        <property name="successUrl" value="/home.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/> -->
        <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean  -->
        <!-- defined will be automatically acquired and available via its beanName in chain        -->
        <!-- definitions, but you can perform instance overrides or name aliases here if you like: -->
        <!--        <property name="filters">
                   <util:map>
                       <entry key="anAlias" value-ref="someFilter"/>
                   </util:map>
               </property>-->
        <property name="filterChainDefinitions">
            <value>
                /admin/**=authc,resourceCheckFilter
                /login=anon
            </value>
        </property>
    </bean>

    <!-- 声明一个自定义的过滤器 -->
    <bean id="resourceCheckFilter" class="cn.com.ecenter.system.filter.ResourceCheckFilter">
        <!-- 为上面声明的自定义过滤器注入属性值 -->
        <property name="errorUrl" value="/unAuthorization"/>
    </bean>

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
        <!-- 设置安全管理器的安全数据源为自定义的 Realm -->
        <property name="realm" ref="myRealm"/>
        <!-- By default the servlet container sessions will be used.  Uncomment this line
             to use shiro's native sessions (see the JavaDoc for more): -->
        <!-- <property name="sessionMode" value="native"/> -->
        <property name="cacheManager" ref="ehCacheManager"/>
    </bean>
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 配置 shiro 的 ehcache 缓存相关,这个缓存只和 Realm 相关 -->
    <bean id="ehCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"></bean>

    <!-- 配置 Spring 的 EhCacheManagerFactoryBean ,须要 spring-context-support 的支持 -->
    <bean id="ehCacheManagerFactoryBean" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache.xml"/>
    </bean>

    <!-- 配置 Spring 的 EhCacheCacheManager,须要 spring-context-support 的支持 -->
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="ehCacheManagerFactoryBean"/>
    </bean>

</beans>

web.xml配置:

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
		 version="3.0">
         
	<!-- Spring -->
	<!-- 配置Spring配置文件路径 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
            classpath*:applicationContext.xml
        </param-value>
	</context-param>

	<!-- Spring -->

	<!-- 配置Spring字符编码过滤器 -->
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<async-supported>true</async-supported>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>


	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>



	<filter>
		<filter-name>xssFilter</filter-name>
		<filter-class>ecenter.base.xss.XssFilter</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>xssFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-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>

	<!--连接池监控 -->
	<servlet>
		<servlet-name>DruidStatView</servlet-name>
		<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>DruidStatView</servlet-name>
		<url-pattern>/druid/*</url-pattern>
	</servlet-mapping>

	<!-- WebStatFilter用于采集web-jdbc关联监控的数据 -->
	<filter>
		<filter-name>DruidWebStatFilter</filter-name>
		<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
		<init-param>
			<param-name>exclusions</param-name>
			<param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>DruidWebStatFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>


	<!-- 配置Spring上下文监听器 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	<!-- 防止Spring内存溢出监听器 -->
	<listener>
		<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
	</listener>


	<!-- Spring MVC 核心控制器 DispatcherServlet 配置 -->
	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath*:spring-mvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
		<async-supported>true</async-supported>
	</servlet>
	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<!-- 拦截所有 的请求,交给DispatcherServlet处理,性能最好 -->
		<url-pattern>/</url-pattern>
	</servlet-mapping>

	<servlet>
		<servlet-name>initServlet</servlet-name>
		<servlet-class>cn.com.ecenter.system.controller.InitServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>


	<!-- 首页 -->
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>

	<!-- 错误页 -->
	<error-page>
		<error-code>404</error-code>
		<location>/commons/404.jsp</location>
	</error-page>
	<error-page>
		<error-code>500</error-code>
		<location>/commons/500.jsp</location>
	</error-page>

</web-app>

sql:

drop database ssm_shiro;
# 创建数据库 ssm_shiro
CREATE DATABASE	 ssm_shiro DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
# 使用数据库 ssm_shiro
USE ssm_shiro;


drop table if EXISTS t_user;
drop table if EXISTS t_role;
drop table if EXISTS t_user_role;
drop table if EXISTS t_resource;
drop table if EXISTS t_role_resource;




# 创建数据表 t_user
CREATE TABLE t_user(
  id TINYINT PRIMARY KEY AUTO_INCREMENT comment '用户 ID',
  username VARCHAR(30) NOT NULL comment '用户名',
  `password` VARCHAR(32) NOT NULL comment '密码',
  nickname VARCHAR(30) NOT NULL comment '昵称',
  `status` TINYINT not null comment '状态:1 启用,2 禁用'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户信息表';

# 创建数据用于测试
INSERT INTO t_user(username,`password`,nickname,`status`)
VALUES('admin','a66abb5684c45962d887564f08346e8d','超级管理员',1);
INSERT INTO t_user(username,`password`,nickname,`status`)
VALUES('dev','c43812121e594f158520698ba706118f','开发工程师',1);
INSERT INTO t_user(username,`password`,nickname,`status`)
VALUES('test','47ec2dd791e31e2ef2076caf64ed9b3d','测试工程师',1);
INSERT INTO t_user(username,`password`,nickname,`status`)
VALUES('doc','5afd1e481507a2a181decc3860b32d15','文档工程师',1);

# 创建数据表 t_role
# name 字段用于显示给人看, sn 字段用在代码中做角色匹配
CREATE TABLE t_role(
  id TINYINT PRIMARY KEY AUTO_INCREMENT comment '角色表 ID',
  `name` VARCHAR(20) NOT NULL comment '角色名称',
  sn VARCHAR(20) NOT NULL comment '角色字符串'
)engine=innodb auto_increment=1 DEFAULT charset=utf8 comment='角色信息表';

# 创建数据用于测试
INSERT INTO t_role(`name`,`sn`) VALUES('管理员','admin'),('开发工程师','dev'),('测试工程师','test'),('文档工程师','doc');

# 创建数据表 t_user_role
CREATE TABLE t_user_role(
  id TINYINT PRIMARY KEY AUTO_INCREMENT comment '用户角色关联表 ID',
  user_id TINYINT NOT NULL,
  role_id TINYINT NOT NULL
)engine=innodb auto_increment=1 charset=utf8 comment='用户角色关联表';

# 创建数据用于测试
INSERT INTO `t_user_role`(`user_id`,`role_id`)
VALUES(1,1),(2,2),(3,3),(4,4);

# 创建资源表 t_resource
# 资源在本项目中的含义就是 "权限"
CREATE TABLE t_resource(
  id TINYINT PRIMARY KEY AUTO_INCREMENT comment '资源 ID',
  `name` VARCHAR(20) NOT NULL comment '资源名称,一般是中文名称(给人看的)',
  permission VARCHAR(40) NOT NULL comment '资源权限字符串,一般是 Shiro 默认的通配符表示(给人看的)',
  url VARCHAR(40) NOT NULL comment '资源 url 表示,我们设计的系统让 Shiro 通过这个路径字符串去匹配浏览器中显示的路径'
)engine=innodb auto_increment=1 charset=utf8 comment='资源表';

# 创建数据用于测试
INSERT INTO t_resource(`name`,permission,url)
VALUES('系统管理','admin:*','/admin/**'),
('用户管理','user:*','/admin/user/**'),
('用户添加','user:add','/admin/user/add'),
('用户删除','user:delete','/admin/user/delete'),
('用户修改','user:update','/admin/user/update'),
('用户查询','user:list','/admin/user/list'),
('用户资源查询','user:resources:*','/admin/user/resources/*'),
('角色管理','role:*','/admin/role/**'),
('角色添加','role:add','/admin/role/add'),
('角色删除','role:delete','/admin/role/delete'),
('角色修改','role:update','/admin/role/update'),
('角色查询','role:list','/admin/role/list'),
('角色资源查询','role:resources:*','/admin/role/resources/*'),
('资源管理','resource:*','/admin/resource/**'),
('资源增加','resource:add','/admin/resource/add'),
('资源删除','resource:delete','/admin/resource/delete'),
('资源修改','resource:update','/admin/resource/update'),
('资源查询','resource:list','/admin/resource/list');


# 创建角色资源关联表
CREATE TABLE t_role_resource(
  id TINYINT PRIMARY KEY AUTO_INCREMENT comment '角色资源关联 ID',
  role_id TINYINT not null comment '角色 id',
  resource_id TINYINT not null comment '资源 id'
)engine=innodb auto_increment=1 charset=utf8 comment='角色资源关联表';

# 创建数据用于测试
INSERT INTO t_role_resource(role_id,resource_id)
VALUES(1,1),
(2,3),(2,5),(2,6),(2,7),(2,9),(2,11),(2,12),(2,13),(2,15),(2,17),(2,18),
(3,6),(3,7),(3,8),(3,14),
(4,6),(4,7),(4,12),(4,18);

自定义shirofilter:

package cn.com.ecenter.system.filter;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * @program: baseFramework
 * @description: 人间有味是清欢
 * @author: liuSha.pufengjun
 * @create: 2018-08-07 17:31
 **/
public class ResourceCheckFilter extends AccessControlFilter {
    private String errorUrl;
    private static final Logger logger = LoggerFactory.getLogger(ResourceCheckFilter.class);

    public String getErrorUrl() {
        return errorUrl;
    }

    public void setErrorUrl(String errorUrl) {
        this.errorUrl = errorUrl;
    }

    /**
     * 表示是否允许访问 ,如果允许访问返回true,否则false;
     * @param servletRequest
     * @param servletResponse
     * @param o 表示写在拦截器中括号里面的字符串 mappedValue 就是 [urls] 配置中拦截器参数部分
     * @return
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        Subject subject = getSubject(servletRequest,servletResponse);
        String url = getPathWithinApplication(servletRequest);
        logger.debug("当前用户正在访问的 url => " + url);
        boolean flag = subject.isPermitted(url);
        return subject.isPermitted(url);
    }

    /**
     * onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回 true 表示需要继续处理;如果返回 false 表示该拦截器实例已经处理了,将直接返回即可。
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        logger.debug("当 isAccessAllowed 返回 false 的时候,才会执行 method onAccessDenied ");

        HttpServletRequest request =(HttpServletRequest) servletRequest;
        HttpServletResponse response =(HttpServletResponse) servletResponse;
        response.sendRedirect(request.getContextPath() + this.errorUrl);

        // 返回 false 表示已经处理,例如页面跳转啥的,表示不在走以下的拦截器了(如果还有配置的话)
        return false;
    }
}

realm自定义:

package cn.com.ecenter.system.realm;

import cn.com.ecenter.system.entity.SysResource;
import cn.com.ecenter.system.entity.SysUser;
import cn.com.ecenter.system.service.SysUserService;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

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

/**
 * @program: baseFramework
 * @description: 人间有味是清欢
 * @author: liuSha.pufengjun
 * @create: 2018-08-01 14:45
 **/
public class MyRealm extends AuthorizingRealm {

    private static final Logger logger = LoggerFactory.getLogger(MyRealm.class);

    @Autowired
    private SysUserService userService;
    /**
     * 授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        logger.info("myRealm doGetAuthorizationInfo");
        //获取认证过的主体信息
        SysUser user = (SysUser) principals.getPrimaryPrincipal();
        Integer userId = user.getId();
        List<SysResource> resourceList =  userService.listAllResource(userId);
        List<String> roleSnList = userService.listRoleSnByUser(userId);

        List<String> resStringList = new ArrayList<String>();

        for(SysResource  sysResource: resourceList){
            resStringList.add(sysResource.getUrl());
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(new HashSet<String>(roleSnList));
        info.setStringPermissions(new HashSet<String>(resStringList));

        // 以上完成了动态地对用户授权
        logger.debug("role => " + roleSnList);
        logger.debug("permission => " + resStringList);
        return info;
    }

    /**
     * 认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        logger.info("--- MyRealm doGetAuthenticationInfo ---");
        String username = authenticationToken.getPrincipal().toString();
        String password = new String((char[])authenticationToken.getCredentials());
        // 以后我们使用 Spring 管理 Shiro 的时候,就不必要这样得到 UserService 了
        // userService = (IUserService) InitServlet.getBean("userService");
        // User user = userService.login(username,password);
        // 这里应该使用 load 方法,比对用户名的密码的环节应该交给 Shiro 这个框架去完成
        // 在测试调试的时候发现,这里还是应该使用 login 判断,因为登录不成功的原因有很多,
        // 可以在登录的逻辑里面抛出各种异常
        // 再到 subject.login(token) 里面去捕获对应的异常
        // 显示不同的消息到页面上
        SysUser user = userService.login(username,password);
        if(user!=null){
            // 第 1 个参数可以传一个实体对象,然后在认证的环节可以取出
            // 第 2 个参数应该传递在数据库中“正确”的数据,然后和 token 中的数据进行匹配
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),getName());
            // 设置盐值
            info.setCredentialsSalt(ByteSource.Util.bytes(username.getBytes()));
            return info;
        }
        return null;
    }

    @Override
    protected void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        Cache c = getAuthenticationCache();
        logger.info("清除【认证】缓存之前");
        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }
        super.clearCachedAuthenticationInfo(principals);
        logger.info("调用父类清除【认证】缓存之后");
        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }

        // 添加下面的代码清空【认证】的缓存
        SysUser user = (SysUser) principals.getPrimaryPrincipal();
        SimplePrincipalCollection spc = new SimplePrincipalCollection(user.getUid(),getName());
        super.clearCachedAuthenticationInfo(spc);
        logger.info("添加了代码清除【认证】缓存之后");
        int cacheSize = c.keys().size();
        logger.info("【认证】缓存的大小:" + c.keys().size());
        if (cacheSize == 0){
            logger.info("说明【认证】缓存被清空了。");
        }
    }

    @Override
    protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        logger.info("清除【授权】缓存之前");
        Cache c = getAuthorizationCache();
        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }
        super.clearCachedAuthorizationInfo(principals);
        logger.info("清除【授权】缓存之后");
        int cacheSize = c.keys().size();
        logger.info("【授权】缓存的大小:" + cacheSize);

        for(Object o : c.keys()){
            logger.info( o + " , " + c.get(o));
        }
        if(cacheSize == 0){
            logger.info("说明【授权】缓存被清空了。");
        }

    }

}

文中用的mysql,而自己开发用的sqlserver

其他dao层service层 controller层实现参照:

https://pan.baidu.com/s/1XbRTb9JTfF41O_RA4Oh2vw

猜你喜欢

转载自blog.csdn.net/qq_22899021/article/details/81509280
今日推荐