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;
}
}
posted @   站着说话不腰疼  阅读(605)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示