shiro权限管理框架与springmvc整合
shiro是apache下的一个项目,和spring security类似,用于用户权限的管理‘
但从易用性和学习成本上考虑,shiro更具优势,同时shiro支持和很多接口集成
用户及权限管理是众多系统都需要解决的问题,采用各种不同的方式均可实现。在比较出名的用户权限控制框架中 spring security 和apache shiro 位居前列。但由于spring security使用相对复杂,学习成本较高故不被众多开发人员采用。而apache shiro则在使用上较为简单灵活且容易学习,故在如今新开发的系统中被广泛采用。 http://shiro.apache.org/
如apache官方站点所述,shiro是一个很容易使用的安全框架,提供了各种不同的功能,很容易便可以集成到web项目中,也可以在web项目之外的环境运行,甚至是cmd命令窗口。在web项目中,主要使用shiro的三个功能:
① Authentication 认证
认证是进入系统的第一步操作。通常是通过输入已经在系统中注册的用户名和对应的密码进行登录操作,也可通过指纹等设备进行。 系统对登录信息进行认证,若通过才能进入系统进行已经授权的操作。
② Authorization 授权
登录系统后能否进行操作还与系统对当前用户的具体授权有关。授权即给用户授予具体的操作权限,用户认证成功后才能进行相关操作。授权和认证是紧密关联的。
③ Session Management 会话管理
用户认证成功后,系统需时刻跟踪用户状态。通过对会话状态的跟踪,时刻对用户用户状态进行更新,当用户进行某一操作时,从会话中判断用户是否具有该操作权限,达到只能对授权资源进行访问。
在shiro的实际使用中,shiro提供了众多接口供用户使用,在表现成更提供了众多标签,下面一一说明常用项目:
① AuthorizingRealm
提供了认证和授权方法,在集成时需要集成该类,根据具体的用户权限实体设计重写认证doGetAuthenticationInfo 和 授权doGetAuthorizationInfo方法。
② ShiroFilterFactoryBean之filterChainDefinitionMap
重写getObject()方法,根据具体的设计可以将对应的权限进行封装, 如动态配置用户权限,用户可以访问部分url。
③ shiro标签
在表现层,shiro提供了标签,以便用户使用。
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:guest> 验证当前用户是否为“访客”,即未认证(包含未记住)的用户。
<shiro:user> 认证通过或已记住的用户。
<shiro:authenticated>
已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。
<shiro:notAuthenticated>
未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。
<shiro:principal/>
输出当前用户信息,通常为登录帐号信息。
<shiro:hasRole name="administrator">
验证当前用户是否属于该角色。
<shiro:lacksRole name="administrator">
与hasRole标签逻辑相反,当用户不属于该角色时验证通过。
<shiro:hasAnyRoles name="developer, project manager, administrator">
验证当前用户是否属于以下任意一个角色。
<shiro:hasPermission name="user:create">
验证当前用户是否拥有指定权限。
<shiro: lacksPermission name=”xxx”>
验证当前用户是否拥有不拥有指定权限。
④ shiro注解
如同shiro标签一样,在具体的controller方法中,可以针对特定方法添加注解,以此来限定访问权限。
首先看一下实际运行效果:
以上是实际运行效果,美工很差,请看重点 O(∩_∩)O
开发步骤:
① 增加Filter
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <async-supported>true</async-supported> <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过滤器相关信息,如默认登录页,缓存等,配置请参考如下文件 applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" 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.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <description>apache shiro配置</description> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login.html" /> <property name="successUrl" value="/user/index.html" /> <property name="unauthorizedUrl" value="/401.html" /> <property name="filterChainDefinitions"> <value> <!-- 登录页允许访问 --> /login** = anon /doLogin** = anon <!-- 静态资源允许访问 --> /static/** = anon /assets/** = anon /user = authc /role = authc /permission = authc /** = authc </value> </property> </bean> <!-- 缓存管理器 使用Ehcache实现 --> <!-- <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> --> <!-- <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml" /> --> <!-- </bean> --> <!-- 会话DAO --> <!-- <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.MemorySessionDAO" /> --> <!-- 会话管理器 --> <!-- <bean id="sessionManager" --> <!-- class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> --> <!-- <property name="sessionDAO" ref="sessionDAO" /> --> <!-- </bean> --> <bean id="shiroAuthorizingRealm" class="xiaochangwei.zicp.net.web.utils.ShiroAuthorizingRealm"> <property name="authorizationCacheName" value="shiro-authorizationCache"/> <!-- <property name="cacheManager" ref="shiroEhcacheManager"/> --> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realms"> <list> <ref bean="shiroAuthorizingRealm" /> </list> </property> <!-- cacheManager,集合spring缓存工厂 --> <!-- <property name="cacheManager" ref="shiroEhcacheManager" /> --> <!-- <property name="sessionManager" ref="sessionManager" /> --> </bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true" /> </bean> <!-- Shiro生命周期处理器 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> </beans>
③重写xml引用的shiroAuthorizingRealm,用于具体的认证和权限的判断,根据业务的不同,这里逻辑也有变更,请酌情修改
package xiaochangwei.zicp.net.web.utils; import java.util.List; import javax.annotation.Resource; import org.apache.log4j.Logger; 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.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 xiaochangwei.zicp.net.entity.Permission; import xiaochangwei.zicp.net.entity.Role; import xiaochangwei.zicp.net.entity.User; import xiaochangwei.zicp.net.service.systemMg.PermissionService; import xiaochangwei.zicp.net.service.systemMg.RoleService; import xiaochangwei.zicp.net.service.systemMg.UserService; /** * 用户身份验证,授权 Realm 组件 * **/ public class ShiroAuthorizingRealm extends AuthorizingRealm { private static final Logger LOGGER = Logger .getLogger(ShiroAuthorizingRealm.class); @Resource private UserService userService; @Resource private RoleService roleService; @Resource private PermissionService permissionService; /** * 权限检查 */ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); String username = String.valueOf(principals.getPrimaryPrincipal()); LOGGER.info("权限检查----" + username + "-----------------"); User param = new User(); param.setUserName(username); final User user = userService.getSigle(param); if (user != null) { Role role = roleService.getSingle(user.getRoleId()); if (role != null) { authorizationInfo.addRole(role.getRoleDefine()); LOGGER.info("角色:" + role.getRoleDefine()); List<Permission> permissions = permissionService .getByRole(role); for (Permission p : permissions) { if (p.getPermissionDefine() != null) { LOGGER.info("权限:" + p.getPermissionDefine()); authorizationInfo.addStringPermission(p .getPermissionDefine()); } if (p.getUrl() != null) { authorizationInfo.addStringPermission(p.getUrl()); } } } } return authorizationInfo; } /** * 登录验证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { String username = String.valueOf(token.getPrincipal()); String password = new String((char[]) token.getCredentials()); SimpleAuthenticationInfo authenticationInfo = null; // 通过数据库进行验证 try { User user = new User(username, password); final User authenticatedUser = userService.getSigle(user); if (authenticatedUser == null) { throw new AuthenticationException("用户名或密码错误."); } else { Subject currentUser = SecurityUtils.getSubject(); Session session = currentUser.getSession(); session.setAttribute("user", authenticatedUser); } authenticationInfo = new SimpleAuthenticationInfo(username, password, authenticatedUser.getRealName()); } catch (Exception e) { e.printStackTrace(); } return authenticationInfo; } }
④ 具体方法的权限定义
方案一: 直接在controller中的方法级别上添加注解标签,代码如下:

package xiaochangwei.zicp.net.web.controller; import java.util.List; 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.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import xiaochangwei.zicp.net.entity.User; import xiaochangwei.zicp.net.service.systemMg.UserService; @Controller @RequestMapping("user") public class UserController { @Autowired private UserService userService; @RequestMapping(value = { "{all}", "list{all}" }) @RequiresPermissions("user:list") public ModelAndView listPage(ModelAndView mv) { List<User> users = userService.getAll(new User()); mv.setViewName("admin/user_list"); mv.addObject("users", users); return mv; } @RequestMapping(value = { "add{all}" }) public ModelAndView addPage(ModelAndView mv) { mv.setViewName("admin/user_update"); return mv; } @RequestMapping(value = { "{uuid}/update{all}" }) public ModelAndView updatePage(@PathVariable("uuid") String uuid,ModelAndView mv) { User param = new User(); param.setUuid(uuid); mv.addObject("user",userService.getSigle(param)); mv.setViewName("admin/user_update"); return mv; } }
方案二:采用url拦截:即在authorizationInfo.addStringPermission中,设置permission中定义的url,这样就不用在每个controller的方法级别上添加注解标签了,减少工作量
只需要再写一个Filter,filter中获取用户的请求地址 URI,通过 SecurityUtils.getSubject().isPermitted(URI)判断是否有权限,有则放行,无责拦截
方案三:重写filterchainfactorybean,并注入到shiroFilter,其实原理和方案二一样
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/admin/login" /> <property name="successUrl" value="/admin/manager" /> <property name="unauthorizedUrl" value="/admin/defined" /> <property name="filterChainDefinitionMap" ref="filterChainFactoryBean" /> </bean> <bean id="filterChainFactoryBean" class="xiaochangwei.zicp.net.web.filter.FilterChainFactoryBean"> <property name="sessionFactory" ref="sessionFactory"></property> </bean>
这里比较粗粒度的进行了方法级别的权限控制,实际情况下,可以做到特别细的拦截,比如页面上字段、按钮等,通过引入shiro提供的标签<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
进行细粒度控制,同时为了更好的用户体验,应该前后端一致,不能前端有删除按钮,点了之后提示没权限,这不科学
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~