2、shiro+spring+springmvc 整合
shiro+spring+springmvc 整合
一般我们企业开发都是用ssm框架来完成,shiro框架有专门对spring集成的实现。
创建一个工程
导入项目maven依赖包:
<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>cn.zj</groupId> <artifactId>spring-shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <spring.version>4.3.2.RELEASE</spring.version> <slf4j.version>1.6.6</slf4j.version> <log4j.version>1.2.17</log4j.version> <shiro.version>1.2.3</shiro.version> </properties> <dependencies> <!-- spring --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.7.4</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <!-- log4j日志 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <!-- 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.4</version> </dependency> <!-- mybatis和spring集成包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <!-- 加入servlet和jsp的依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.2.1</version> <scope>provided</scope> </dependency> <!-- 引入shiro框架的依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-quartz</artifactId> <version>${shiro.version}</version> </dependency> <!-- shiro和spring集成包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <!-- MySQL数据库驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.26</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> </plugins> </build> </project>
web.xml中配置shiro过滤器
在web系统中,shiro也通过filter进行拦截。filter拦截后将操作权交给spring中配置的filterChainDefitions
shiro提供很多filter。
<!-- Shiro过滤器,DelegatingFilterProxy类是为了让代理模式将Spring和Shiro的过滤器关联起来 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 设置为true以后filter的生命周期交给Servlet管理 --> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <!-- 设置Spring容器中Filter对应Bean的id,可以不设,如果不设,必须保证当前Filter的name和Springbean的id值一致 --> <init-param> <param-name>targetBeanName</param-name> <param-value>shiroFilter</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
spring-shiro.xml配置
在spring-shiro.xml 中配置web.xml中fitler对应spring容器中的bean。
<!-- web.xml 中配置的Filter对应的Bean --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager" /> <!-- 注入认证页面, 如果访问一个页面没有认证,会自动跳转到此页面进行认证 --> <property name="loginUrl" value="/login.do" /> <!-- 注入认证成功页面 --> <property name="successUrl" value="/index.do" /> <!-- 认证通过,没有权限访问跳转到的页面 --> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- 设置shiro的过滤器链 ,过滤器链的顺序从上到下执行 所有 一般 /**最好配置到最后面 --> <property name="filterChainDefinitions"> <value> <!-- 放行静态资源 --> /images/**=anon /js/**=anon /css/**=anon /login.jsp = anon <!-- logout:退出登录过滤器,清除Session数据 --> /logout.do=logout <!-- /**所有请求 ,authc 表示所有请求都需要认证通过才可以访问 --> /** = authc </value> </property> </bean>
认证登录
登录原理
用户点击登录提交到 的 /user/login.do 匹配到shiro的 /**
使用的表单认证过滤器 FormAuthenticationFilter ,此过滤器内部会获取当前用户提交的表单的
账号和密码(注意:账号表单名称一定叫做username,密码表单名称一定叫password),封装到token令牌中,并且调用自定义的Realm中的doGetAuthenticationInfo
方法,进行认证,
1.如果认证通过,直接跳转到
<property name="successUrl" value="/index.do"/>
后台首页
2.如果认证失败,跳转到
<property name="loginUrl" value="/user/login.do"/>
并且把认证失败的错误异常类型,设置HttpServletRequest请求对象中,作用域的名称就是
FormAuthenticationFilter 中 shiroLoginFailure
我们可以在/user/login.do中注入一个HttpServletRequest接收这个错误信息
登录页面
由于FormAuthenticationFilter的用户身份和密码的input的默认值(username和password),修改页面的账号和密码 的input的名称为username和password
登录代码的实现
package cn.zj.shiro.controller; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/user") public class UserController { @RequestMapping("/login") public String login(HttpServletRequest request,Model m) { //获取认证失败的错误信息,在Shiro框架的 FormAuthenticationFilter 过滤器中共享 // 共享的属性名称 shiroLoginFailure // 共享的 shiro 异常的字节码 类型 String shiroLoginFailure = (String) request.getAttribute("shiroLoginFailure"); System.out.println("异常类型 :"+shiroLoginFailure); if(shiroLoginFailure !=null) { if(UnknownAccountException.class.getName().equals(shiroLoginFailure)) { m.addAttribute("errorMsg", "亲。账号不存在"); }else if(IncorrectCredentialsException.class.getName().equals(shiroLoginFailure)) { m.addAttribute("errorMsg", "亲。密码错误"); } } return "forward:/login.jsp"; } }
认证拦截过滤器
在spring-shiro.xml中配置-
红色背景部分
<!-- web.xml 中配置的Filter对应的Bean --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager" /> <!-- 注入登录页面, 如果没有认证,会自动跳转到此页面进行认证 --> <property name="loginUrl" value="/login.do" /> <!-- 注入认证成功页面 --> <property name="successUrl" value="/index.do" /> <!-- 认证通过,没有权限访问跳转到的页面 --> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- 设置shiro的过滤器链 ,过滤器链的顺序从上到下执行 所有 一般 /**最好配置到最后面 --> <property name="filterChainDefinitions"> <value> <!-- 放行静态资源 --> /images/**=anon /js/**=anon /css/**=anon /login.jsp = anon <!-- logout:退出登录过滤器,清除Session数据 --> /logout.do=logout <!-- /**所有请求 ,authc 表示所有请求都需要认证通过才可以访问 --> /** = authc </value> </property> </bean> <!-- 配置安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- 配置realms --> <property name="realm" ref="customRealm" /> </bean> <!-- 配置自定义realm --> <bean id="customRealm" class="cn.zj.shiro.realm.CustomRealm"> <!-- 注入凭证匹配器 --> <property name="credentialsMatcher" ref="credentialsMatcher" /> </bean> <!-- 配置凭证匹配器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!-- 加密算法 --> <property name="hashAlgorithmName" value="md5" /> <!-- 散列次数 --> <property name="hashIterations" value="2" /> </bean>
测试认证代码
登录一个认证过程,在自定义的realm中开发者自己完成认证操作
认证思路
1.根据账号查询数据库对应的用户信息并封装成对象
2.获取用户对象对应的密码,和登录操作提交的用户密码进行(加盐+散列)后就进行比对
3.返回认证对象
(1)如果认证成功,会跳转到成功页面一般是首页
(2)如果认证失败,继续认证
package cn.zj.shiro.realm; 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.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; public class CustomRealm extends AuthorizingRealm { /** * 认证方法,开发者自己实现具体认证操作 如果认证成功,返回一个 AuthenticationInfo 封装有认证对信息的对象 * */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 1.获取token的身份信息(账号)--用户输入 String principal = (String) token.getPrincipal();// 当事人(当前登录的账号) UsernamePasswordToken uToken = (UsernamePasswordToken) token; // 认证思路 // --1.根据账号从数据库中查询出是否有此账号,有封装成一个对象,没有返回null // --2.如果有的对象的话,把对象的密码作为品质传递到下面认证对象中进行认证操作 // 模拟从数据库中查询的的(凭证)密码 String hashedCredentials = "662b46d0168aa5353771f45084378881"; String stla = "query"; // 3.创建返回认证信息的对象 ByteSource credentialsSalt = ByteSource.Util.bytes(stla); //数据库 身份 账号 String principal = "admin"; System.out.println("principals :"+principal); if("admin".equals(username)) { //加密后的 认证信息对象 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, this.getName()); return authenticationInfo; } return null; } //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } }
认证信息在页面显示
认证后用户菜单在首页显示
认证后用户的信息在页头显示
认证成功以后的首页
一般认证成功以后,都跳转到首页,首页一般显示菜单用户登录信息,和用户操作其他模块相关界面
package cn.zj.shiro.controller; import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class IndexController { @RequestMapping("/index.do") public String list(HttpServletRequest req, Model model) { return "index"; } }
退出登录
使用LogoutFilter
不用我们去实现退出,只要去访问一个退出的url,由LogoutFilter拦截住,清除session。并且默认跳转重定向到项目的根目录 : /
在spring-shiro.xml配置LogoutFilter
<!-- web.xml 中配置的Filter对应的Bean --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager" /> <!-- 注入登录页面, 如果没有认证,会自动跳转到此页面进行认证 --> <property name="loginUrl" value="/user/login.do" /> <!-- 注入认证成功页面 --> <property name="successUrl" value="/index.do" /> <!-- 认证通过,没有权限访问跳转到的页面 --> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- 设置shiro的过滤器链 ,过滤器链的顺序从上到下执行 所有 一般 /**最好配置到最后面 --> <property name="filterChainDefinitions"> <value> <!-- 放行静态资源 --> /images/**=anon /js/**=anon /css/**=anon /login.jsp = anon <!-- logout:退出登录过滤器,清除Session数据 --> /logout.do=logout <!-- /**所有请求 ,authc 表示所有请求都需要认证通过才可以访问 --> /** = authc </value> </property> </bean>
自定义退出登录跳转的页面
Shiro的退出登录过滤器 :logout 默认退出跳转到项目根目录 : / , 但是一般可能我们需要直接跳转到 登录页面 /login.jsp
我们可以在Spring中并重新配置org.apache.shiro.web.filter.authc.LogoutFilter 并且通过属性设置退出登录跳转的默认页面
<!--重新配置退出登录过滤器,并且设置默认的退出登录跳转页面 --> <bean id="customLogOut" class="org.apache.shiro.web.filter.authc.LogoutFilter"> <property name="redirectUrl" value="/login.jsp"/> </bean> <!-- 配置Shiro框架的过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 重新定义过滤器,将自定义配置过的过滤器设置为默认过滤器别名 --> <property name="filters"> <map> <!-- 设置 customLogOut 为 logout 过滤器,重新配置了退出登录跳转页面 --> <entry key="logout" value-ref="customLogOut"/> </map> </property> <!-- 配置安全管理器 --> <property name="securityManager" ref="securityManager"/> <!-- 认证失败以后的跳转页面 --> <property name="loginUrl" value="/user/login.do"/> <!-- 认证成功以后的跳转页面 --> <property name="successUrl" value="/index.do"/> <!-- 认证通过后,强制访问一个没有权限,跳转到的提示页面 --> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <!-- 过滤器链 Shiro框架基于过滤器编写,安全框架有很多过滤规则 Shiro针对不同的过滤规则,编写不同的过滤器(内置),开发者可以根据不同开发场景选择配置 常见过滤器 anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名过滤器,不需要任何安全,直接放行,放行静态资源(html,css,js...) authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter 表单认证过滤,访问的必须认证通过才放行 用户的请求匹配此过滤器,过滤器会去接受请求是否是包含请求参数 username,password,rememberMe 1,如果有参数:说明当前请求是在做认证操作(登录操作) 会自动调用 自动Realm 中 认证方法 doGetAuthenticationInfo 认证成功:跳转 <property name="successUrl" value="/index.do"/> 认证失败:跳转 <property name="loginUrl" value="/user/login.do"/> 并且把认证失败的错误信息共享到HttpServletRequest 请求对象 共享认证失败的错误信息的 名称 :shiroLoginFailure 2,如果没有参数,直接判断当前是否有认证, 如果已经认证 : 放行 如果没有认证:跳转认证失败页面 <property name="loginUrl" value="/user/login.do"/> logout org.apache.shiro.web.filter.authc.LogoutFilter 退出登录过滤器,退出登录过滤器会自动清除当前认证成功存储在Session的共享数据,并且 默认跳转到 项目的根目录 / --> <property name="filterChainDefinitions"> <value> <!-- 配置Shiro内置过滤器规则 /资源 = 过滤器别名 /目录/** = 过滤器 过滤器的匹配是从上到下的,上面匹配到,就不会在往下执行了 --> <!-- 放行静态资源 --> /lib/**=anon /static/**=anon <!-- 登录页面放行 --> /login.jsp=anon <!-- 退出登录过滤器 --> /logout.do=logout <!-- 表单认证过滤器,匹配所有请求 --> /** = authc </value> </property> </bean>
shiro的过虑器
Shiro针对不同的过滤规则,编写不同的过滤器(内置),开发者可以根据不同开发场景选择配置
1、anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
2、authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,没有参数
3、perms:
(1) 例子/admins/user/=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/=perms["user:add:,user:modify:"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
4、user:例如/admins/user/**=user没有参数表示必须存在用户, 身份认证通过或通过记住我
设置凭证匹配器
数据库中存储到的md5的散列值,在realm中需要设置数据库中的散列值它使用散列算法 及散列次数,让shiro进行散列对比时和原始数据库中的散列值使用的算法 一致。
<!-- 配置凭证匹配器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!-- 加密算法 --> <property name="hashAlgorithmName" value="md5" /> <!-- 散列次数 --> <property name="hashIterations" value="2" /> </bean>
授权
在自定义Realm中授权
授权思路,从当前登录用户信息中获取用户的所有权限,并添加到 SimpleAuthorizationInfo对象中
如果shiro配置的授权表达式匹配到了,授权成功,放行。如果没有匹配,跳转到没有权限访问提示页面
//授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //授权思路,从当前登录用户信息中获取用户的所有权限,并添加到 SimpleAuthorizationInfo对象中 //如果shiro配置的授权表达式匹配到了,授权成功,放行。如果没有匹配,跳转到没有权限访问提示页面 authorizationInfo.addStringPermission("user:list"); authorizationInfo.addStringPermission("role:list"); authorizationInfo.addStringPermission("permission:list"); return authorizationInfo; }
使用PermissionsAuthorizationFilter 方式
在spring-shiro.xml中配置url所对应的权限。
1、在spring-shiro.xml中配置filter规则
(1)/user/update.do=perms[user:update]
(2)/user/list.do=perms[user:list]
2、用户在认证通过后,请求/user/list.do
3、被PermissionsAuthorizationFilter拦截,发现需要“user:list”权限
4、PermissionsAuthorizationFilter调用realm中的doGetAuthorizationInfo获取数据库中正确的权限
5、PermissionsAuthorizationFilter对user:list 和从realm中获取权限进行对比,如果“user:list”在realm返回的权限列表中,授权通过。
如果没有权限,就会跳转到一个没有权限访问的提示页面
创建/unauthorized.jsp
如果授权失败,跳转到/unauthorized.jsp 红色背景部分
<!-- web.xml 中配置的Filter对应的Bean --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager" /> <!-- 注入登录页面, 如果没有认证,会自动跳转到此页面进行认证 --> <property name="loginUrl" value="/login.do" /> <!-- 注入认证成功页面 --> <property name="successUrl" value="/index.do" /> <!-- 认证通过,没有权限访问跳转到的页面 --> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- 设置shiro的过滤器链 ,过滤器链的顺序从上到下执行 所有 一般 /**最好配置到最后面 --> <property name="filterChainDefinitions"> <value> <!-- 放行今天资源 --> /images/**=anon /js/**=anon /css/**=anon /login.jsp = anon <!-- logout:退出登录过滤器,清除Session数据 --> /logout.do=logout <!-- /**所有请求 ,authc 表示所有请求都需要认证通过才可以访问 --> /** = authc </value> </property> </bean>
问题总结
1、在spring-shiro.xml中配置过虑器链接,需要将全部的url和权限对应起来进行配置,实际开发项目中项目有很多权限配置,非常的麻烦(不建议使用)
(1)解决方案,使用注解配置
2、每次授权都需要调用realm查询数据库,对于系统性能有很大影响,可以通过shiro缓存来解决。
(1)解决方案,使用shiro的缓存技术-ehcache
支持注解配置授权
在springmvc.xml中配置:
使用注解开发,首先在spring配置中开启shiro注解配置
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <!-- 没有权限异常跳转的页面 --> <prop key="org.apache.shiro.authz.UnauthorizedException">/unauthorized.jsp</prop> </props> </property> </bean> <!-- 开启aop,对代理类 --> <aop:config proxy-target-class="true"></aop:config> <!-- 开启shiro的注解支持 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager"></property> </bean>
在controller方法中添加注解
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/list.do") @RequiresPermissions("user:list") public String list(HttpServletRequest req, Model model) { return "index"; } @RequestMapping("/update.do") @RequiresPermissions("user:updae") public String update(HttpServletRequest req, Model model) { return "redirect:/user/list.do"; } @RequestMapping("/delete.do") @RequiresPermissions("user:delete") public String delete(HttpServletRequest req, Model model) { return "redirect:/user/list.do"; } }
jsp标签 授权
标签介绍
Jsp页面添加:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>
标签名称 | 标签条件(均是显示标签内容) |
---|---|
shiro:authenticated | 登录之后 |
shiro:notAuthenticated | 不在登录状态时 |
shiro:guest | 用户在没有RememberMe时 |
shiro:user | 用户在RememberMe时 |
<shiro:hasAnyRoles name="abc,123" > | 在有abc或者123角色时 |
<shiro:hasRole name="abc"> | 拥有角色abc |
<shiro:lacksRole name="abc"> | 没有角色abc |
<shiro:hasPermission name="abc"> | 拥有权限资源abc |
<shiro:lacksPermission name="abc"> | 没有abc权限资源 |
shiro:principal | 显示用户身份名称 |
<shiro:principal property="username"/> | 显示用户身份中的属性值 |
index.jsp页面标签控制权限
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> 首页<br> <a href="${pageContext.request.contextPath}/logout.do">退出登录</a> <hr> <!-- 判断是否有 user:list 权限,如果有,显示标签内部内容,没有不显示 --> <shiro:hasPermission name="user:list"> <a href="${pageContext.request.contextPath}/user/list.do">用户管理</a><br> </shiro:hasPermission> <shiro:hasPermission name="role:list"> <a href="#">角色管理</a><br> </shiro:hasPermission> <shiro:hasPermission name="permission:list"> <a href="#">权限管理</a><br> </shiro:hasPermission> </body> </html>
授权测试
如果当前登录用户没有授权,用户的界面就不会显示对应的菜单
如果用户强制访问某一个没有授权的页面,那么会跳转到没有授权提示页面
shiro缓存
问题:为什么要有缓存?
答:因为认证通过以后访问的页面,每访问一次都要进行一次授权,都会执行一次授权方法,授权相关的权限数据都是从数据库查询的,会造成频繁的查询数据库,影响系统性能
所以:针对上边授权频繁查询数据库,需要使用shiro缓存技术。
缓存流程
shiro中提供了对认证信息和授权信息的缓存。shiro默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认开启的。主要研究授权信息缓存,因为授权的数据量大。
前提:用户认证通过(登录成功)。
用户第一次授权:调用realm查询数据库
用户第二次授权:不调用realm查询数据库,直接从缓存中取出授权信息(权限标识符)。
使用ehcache
Shiro推荐使用 ehcache第三方缓存
添加Ehcache的依赖包
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.0</version> </dependency>
配置cacheManager
<!-- 配置安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- 注入realm --> <property name="realm" ref="customRealm" /> <!-- 注入缓存管理器 --> <property name="cacheManager" ref="cacheManager"></property> </bean>
<!-- 缓存管理器 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <!-- 读取ehcache配置文件 --> <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"></property> </bean>
shiro-ehcache.xml
在 classpath 路径下面创建 ehcache缓存的配置文件
<ehcache> <!--diskStore:缓存数据持久化的目录 地址 --> <diskStore path="D:\develop\ehcache" /> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
Session会话管理器
和shiro整合后,使用shiro的session管理,shiro提供sessionDao操作 会话数据。
配置sessionManager
<!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customRealm" /> <property name="sessionManager" ref="sessionManager" /> </bean> <!-- 会话管理器 --> <!-- Session管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- 设置session的失效时长,单位毫秒 --> <property name="globalSessionTimeout" value="#{1000 * 3600 * 24 * 7}"></property> <!-- 删除失效的session --> <property name="deleteInvalidSessions" value="true"/> </bean>
记住我
Shiro框架认证成功以后,默认的信息是存储在Session中的,Session的缺点就是如果数据浏览器关闭,Session失效,下次必须再次登录。所以,Shiro提供了记住我的功能,记住我功能底层使用的Cookie技术
用户登陆选择“自动登陆”本次登陆成功会向cookie写身份信息,下次登陆从cookie中取出身份信息实现自动登陆
用户身份实现java.io.Serializable接口
向cookie记录身份信息需要用户身份信息对象实现序列化接口,如下:
public class User implements Serializable{
配置rememeberMeManager
使用记住我功能需要设置Shiro的rememberMeManage 管理器
<!-- 配置安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- 注入realm --> <property name="realm" ref="comteRealm" /> <!-- 缓存管理 --> <property name="cacheManager" ref="cacheManager"/> <!-- 会话管理 --> <property name="sessionManager" ref="sessionManager" /> <!-- 记住我 --> <property name="rememberMeManager" ref="rememberMeManager"/> </bean> <!-- 记住我 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <!-- 注入cookie --> <property name="cookie"> <bean class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- 使用构造器设置cookie名称 --> <constructor-arg value="rememberMe"/> <!-- 设置最大有效期 :单位秒 --> <property name="maxAge" value="#{3600 * 24 * 7}"/> <!-- 保存到本地Cookie的名称 --> <!-- <property name="name" value="rememberMe"/> --> </bean> </property> </bean>
登陆页面
记住我的表单的name默认是 remeberMe
<form action="${pageContext.request.contextPath}/login.do" method="post"> 账号:<input name="username"><br> 密码:<input name="password"><br> 记住我:<input type="checkbox" name="rememberMe"> <button type="submit">登录</button> </form>
自定义FormAuthenticationFilter
表单认证 使用的是 org.apache.shiro.web.filter.authc.FormAuthenticationFilter 中对应
那么这个过滤器接受的认证参数都是有规则的
认证的身份(账号)默认叫做 username
认证的凭证(密码)默认叫做 password
认证的记住我 默认叫做 rememberMe
所以,在开发者编写的认证(登录)表单的对应的名称必须和默认保持一致
但是开发者也可以自定这些认证的表单名称规则
但是开发者也可以自定义配置对应的名称
配置rememberMe的input名称
<!-- 自定义表单认证过滤器 --> <bean id="formAuthenticationFilter" class="cn.zj.shiro.MyFormAuthenticationFilter"> <!-- 设置表单提交的账号表单名称 --> <property name="usernameParam" value="username"/> <!-- 设置表单提交的账号表单名称 --> <property name="passwordParam" value="password"/> <!-- 设置表单提交的账号表单名称 --> <property name="rememberMeParam" value="rememberMe"/> </bean>
<!-- web.xml 中配置的Filter对应的Bean --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager" /> <!-- 注入登录页面, 如果没有认证,会自动跳转到此页面进行认证 --> <property name="loginUrl" value="/login.do" /> <!-- 注入认证成功页面 --> <property name="successUrl" value="/index.do" /> <!-- 自定义Filter --> <property name="filters"> <map> <!-- 使用自定义的表单认证过滤器--> <entry key="authc" value-ref="formAuthenticationFilter"></entry> </map> </property> <!-- 认证通过,没有权限访问跳转到的页面 --> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- 设置shiro的过滤器链 ,过滤器链的顺序从上到下执行 所有 一般 /**最好配置到最后面 --> <property name="filterChainDefinitions"> <value> <!-- 放行今天资源 --> /images/**=anon /js/**=anon /css/**=anon /login.jsp = anon <!-- logout:退出登录过滤器,清除Session数据 --> /logout.do=logout <!-- 配置记住我访问的页面 --> /index.do=user <!-- /**所有请求 ,authc 表示所有请求都需要认证通过才可以访问 --> /** = authc </value> </property> </bean>
测试
自动登陆后,需要查看 cookei是否有rememberMe
使用UserFilter
如果设置记住我,下次访问某些url时可以不用登陆。将记住我即可访问的地址配置让UserFilter拦截。
<!-- web.xml 中配置的Filter对应的Bean --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager" /> <!-- 注入登录页面, 如果没有认证,会自动跳转到此页面进行认证 --> <property name="loginUrl" value="/user/login.do" /> <!-- 注入认证成功页面 --> <property name="successUrl" value="/index.do" /> <!-- 自定义Filter --> <property name="filters"> <map> <!-- 使用自定义的表单认证过滤器--> <entry key="authc" value-ref="formAuthenticationFilter"></entry> </map> </property> <!-- 认证通过,没有权限访问跳转到的页面 --> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- 设置shiro的过滤器链 ,过滤器链的顺序从上到下执行 所有 一般 /**最好配置到最后面 --> <property name="filterChainDefinitions"> <value> <!-- 放行今天资源 --> /images/**=anon /js/**=anon /css/**=anon /login.jsp = anon <!-- logout:退出登录过滤器,清除Session数据 --> /logout.do=logout <!-- 配置记住我访问的页面 --> /index.do=user <!-- /**所有请求 ,authc 表示所有请求都需要认证通过才可以访问 --> /** = authc </value> </property> </bean>
总集成代码
总结构:
依赖包:
pom.xml代码:
<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>cn.zj.shiro</groupId> <artifactId>Shiro-Spring-StringMvc</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <spring.version>4.3.2.RELEASE</spring.version> <slf4j.version>1.6.6</slf4j.version> <log4j.version>1.2.17</log4j.version> <shiro.version>1.2.3</shiro.version> </properties> <dependencies> <!-- spring --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.7.4</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <!-- log4j日志 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <!-- 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.4</version> </dependency> <!-- mybatis和spring集成包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <!-- 加入servlet和jsp的依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.2.1</version> <scope>provided</scope> </dependency> <!-- 引入shiro框架的依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-quartz</artifactId> <version>${shiro.version}</version> </dependency> <!-- shiro和spring集成包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <!-- MySQL数据库驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.26</version> </dependency> <!-- 支持shiro缓存 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> </plugins> </build> </project>
web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!-- shiro-spring集成 1.将shiro创建对象的权利交给spring --> <!-- shiro security filter --> <filter> <!-- 这里的 filter-name 要和 spring 的 applicationContext-shiro.xml 里的 org.apache.shiro.spring.web.ShiroFilterFactoryBean 的 bean name 相同 --> <filter-name>shiroSecurityFilter</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> <init-param> <param-name>targetBeanName</param-name> <param-value>shiroSecurityFilter</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroSecurityFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 前端控制器 --> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring*.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <!-- Map all requests to the DispatcherServlet for handling --> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
springshiro.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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 记住我时,只有在过滤器中配置了/index.do=user这个放行资源外, 你访问其他的资源是没有用的,还是需要登录, 原因是因为session中没有该数据,访问一个新页面时, 都会去session中获取到权限信息 --> <!-- 需要重写过滤时的规则 --> <bean id="myFormAuthenticationFilter" class="cn.zj.shiro.filter.MyFormAuthenticationFilter"> <!-- 配置请求的身份的name 不同时:attribute:org.apache.shiro.authc.UnknownAccountException--> <property name="usernameParam" value="username"/> <!-- 配置请求的凭证的name 不同时:attribute:org.apache.shiro.authc.AuthenticationException--> <property name="passwordParam" value="pwd"/> <!-- 配置记住我时的name --> <property name="rememberMeParam" value="rememberMe"/> </bean> <!-- 使用注解配置授权管理 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <!-- 没有权限异常跳转的页面:如果SpringMVC配置了视图解析的,按照SpringMVC的视图解析器配置跳转页面--> <prop key="org.apache.shiro.authz.UnauthorizedException">unauthorized</prop> </props> </property> </bean> <!-- 先开启aop对代理类的支持 --> <aop:config proxy-target-class="true"></aop:config> <!-- 开启shiro对springmvc注解的支持 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager" /> </bean> <!-- 退出登录过滤器退出是默认是(重定向到 / 下面的) 如又想重新登录,入会出现403的错误 所有我们要配置自己的跳转页面 --> <bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter"> <property name="redirectUrl" value="/login.jsp" /> </bean> <!-- shiro配置: --> <!-- 配置shiro与spring的桥梁 --> <bean id="shiroSecurityFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--当shiro框架中的过滤器无法满足我们的需求时, 需使用我们自己的定义的过滤器 --> <property name="filters"> <map> <!-- 注入退出登录过滤器 --> <entry key="logout" value-ref="logout" /> <!-- 注入表单过滤器 --> <entry key="authc" value-ref="myFormAuthenticationFilter"/> </map> </property> <!-- 注入安全管理器 --> <property name="securityManager" ref="securityManager" /> <!-- 配置认证失败页面 --> <property name="loginUrl" value="/user/login.do" /> <!-- 配置认证成功页面 --> <property name="successUrl" value="/index.do" /> <!-- 配置权限不足的页面 --> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <!-- 设置过滤器链,配置过滤规则:filterChainDefinitions 拦截规则:资源=拦截规则的别名 1. anon org.apache.shiro.web.filter.authc.AnonymousFilter 对指定的资源放行 2.authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter 表单拦截过滤器 3.logout org.apache.shiro.web.filter.authc.LogoutFilter 退出登录过滤器 4.perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter 权限过滤器 5.user org.apache.shiro.web.filter.authc.UserFilter 用户过滤器,判断用户是否有记住我这个操作,然后该页面就有权限访问了 --> <property name="filterChainDefinitions"> <value> <!-- 放行静态资源 --> /image/**=anon /js/**=anon /css/**=anon /login.jsp=anon <!-- 使用退出登录的过滤器 --> /logout.do=logout <!-- 配置权限过滤器 --> <!-- 实际开发中,这样配在这里,造成项目臃肿,此时应该使用注解配置 --> <!-- /user/list.do=perms[user:list] /role/list.do=perms[role:list] --> <!-- 配置记住我后,再次登录时跳转到哪个页面 --> /index.do=user <!-- 拦截所有的请求 --> /**=authc </value> </property> </bean> <!-- 配置安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- 注入自定义realm --> <property name="realm" ref="myrealm" /> <!-- 注入缓存管理器 --> <property name="cacheManager" ref="cacheManager"/> <!-- 注入会话管理器 --> <property name="sessionManager" ref="sessionManager"/> <!-- 注入记住我管理器 --> <property name="rememberMeManager" ref="rememberMeManager"/> </bean> <!-- 配置rememberMeManager管理器:底层用到cookie技术 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <!-- 注入cookie --> <property name="cookie"> <bean class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- 构造器注入rememberMe --> <constructor-arg value="rememberMe"/> <!-- 设置cookie存放的时间:单位seconds --> <property name="maxAge" value="#{60*60*24*3}"/> </bean> </property> </bean> <!-- 会话管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- 配置最大存活时间:单位是毫秒 --> <property name="globalSessionTimeout" value="#{1000*60*5}"/> </bean> <!-- 配置缓存管理器的配置文件 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <!-- 读出ehcache-shiro配置文件 --> <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"></property> </bean> <!-- 创建自定义realm --> <bean id="myrealm" class="cn.zj.shiro.realm.MyRealm"> <!-- 配置凭证管理器 --> <property name="credentialsMatcher" ref="credentialsMatcher" /> </bean> <!-- 配置凭证管理器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!-- 配置加密的方式 --> <property name="hashAlgorithmName" value="md5" /> <!-- 配置散列次数 --> <property name="hashIterations" value="3" /> </bean> </beans>
shiro-ehcache.xml:
<ehcache> <!--diskStore:缓存数据持久化的目录 地址 --> <diskStore path="D:\develop\ehcache" /> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
controller:
package cn.zj.shiro.controller; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/user") public class UserController { @RequestMapping("/login") public String login(HttpServletRequest request, Model medol) { //因为shiro框架将错误的信息存入到请求对象中,可以根据shiro框架的信息友好的提示 String attribute = (String) request.getAttribute("shiroLoginFailure"); System.out.println("attribute:"+attribute); if(UnknownAccountException.class.getName().equals(attribute)) { System.out.println("账号不存在"); medol.addAttribute("errorMsg", "账号不存在"); } if(IncorrectCredentialsException.class.getName().equals(attribute)) { System.out.println("密码错误"); medol.addAttribute("errorMsg", "密码错误"); } //提示用户名密码错误 return "forward:/login.jsp"; } @RequiresPermissions("user:list") @RequestMapping("/list") public String list() { return "user_list"; } }
MyFormAuthenticationFilter.java:
package cn.zj.shiro.filter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; public class MyFormAuthenticationFilter extends FormAuthenticationFilter{ //重写isAccessAllowed方法,将cookie中的数据存入到session中 @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { //获取主体对象 Subject subject = getSubject(request, response); //获取session Session session = subject.getSession(); //判断是否已认证,没有认证就查看有没有cookie if(!subject.isAuthenticated() && subject.isRemembered()) { String principal = (String) subject.getPrincipal(); session.setAttribute("user", principal); } //当存入到session中,此时就通过了认证。 return subject.isAuthenticated() || subject.isRemembered(); } }
MyRealm.java:
package cn.zj.shiro.realm; import java.util.Arrays; import java.util.List; 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.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; public class MyRealm extends AuthorizingRealm { /* * 自定义的realm 有两个重写的方法, 一个是可以写自己的认证逻辑(doGetAuthenticationInfo), * 还有一个是可以写授权的逻辑(doGetAuthorizationInfo) * */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // System.out.println("doGetAuthenticationInfo"); /* * 1.先获取令牌中的身份和凭证 2.对比身份是否正确,不正确返回null * 3.返回AuthenticationInfo对象并将身份正确返回从数据库中查找到的密码和身份 */ // 假设是从数据库中的数据 List<String> list = Arrays.asList("lili", "lucy", "zhangsan", "lisi"); String principal = (String) token.getPrincipal(); // 没有找到用户名 if (!list.contains(principal)) { return null; } // 返回AuthenticationInfo的对象 ByteSource credentialsSalt = ByteSource.Util.bytes("qwer"); Object hashedCredentials = "142a04d176b6960cf517c6b2bac95630"; SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, this.getName()); return authenticationInfo; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("=========角色授权========="); //模拟数据库查询出来的权限 List<String> permissions = Arrays.asList("user:list","user:insert","user:update","role:list"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //将权限的判断交给shiro框架 authorizationInfo.addStringPermissions(permissions); // System.out.println("=========角色授权========="); return authorizationInfo; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?