1 shiro
Apache shiro 是一个 Java 安全框架。
功能:认证、授权、加密和会话管理功能
应用环境:JavaEE、JavaSE
Subject 可看做成一个用户
SecurityManager 框架的核心API
Reaim 域对象,用于取数据库中的数据,进行权限比对。
| 名词 | 名词中文解释 | | -------------- | ------------ | | Subject | 主体 | | Security | 安全 | | Realm | 领域、范围 | | Authenticator | 认证器 | | Authentication | 认证 | | Authorizer | 授权器 | | Authorization | 授权 | | Cryptography | 密码、凭证 | | Credential | 证书、凭证 | | Matcher | 匹配器 | | Principal | 身份 |
shiro 配置文件
shiro.ini 文件放在 claspath 中,shiro会自动查找。其中格式是key/value键值对配置。
Ini 配置文件一般适合与用户少且不需要在运行时动态创建的情景下使用。
ini 配置文件有四大类:main,users,roles,urls
[main]
main 主要配置shiro 的一些对象,例如:securityManager,Realm,authenticator,authcStrategy
[users]
作用:配置一组静态的用户
格式:key:value ,key表示密码的名字,value 表示密码的值
zeros=1233
[roles]
作用:将角色和权限关联操作
格式:资源:操作
role1= printer:create,printer:query
[urls]
作用:拦截对应的 URL 请求。
2 实现简单认证
认证:验证用户是否合法。
2.1 实现步骤
导入 jar 包
junit-4.9.jar log4j-1.2.17.jar shiro-all-1.3.2.jar slf4j-api-1.7.5.jar slf4j-log4j12-1.7.5.jar
编写 shiro.ini 配置文件
[users]zeros=1233
测试
package com.szxy.shiro;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;public class SampleAuthentication {@Testpublic void sampleAuthentication(){//构建 SecurityManagerFactory 工厂IniSecurityManagerFactory factory = new IniSecurityManagerFactory();//利用 SecurityManagerFactory 实例化 SecurityManager 对象SecurityManager securityManager = factory.getInstance();// 将 SecurityManager 设置到运行环境中SecurityUtils.setSecurityManager(securityManager);//通过SecurityUtils, 创建主体 SubjectSubject subject = SecurityUtils.getSubject();//创建用户密码令牌认证UsernamePasswordToken token = new UsernamePasswordToken("admin1","passwd");//验证身份subject.login(token);//查看结果System.out.println(subject.isAuthenticated());}
}
3 Realm 域
shiro 默认使用 IniRealm 源,默认从 init 配置文件中读取用户信息。
3.1 实现步骤
建立 users 表
注意:表名必须是 users 。jdbcRealm 查找数据库中的表就是 users 表。
导入 jar 包
com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar commons-beanutils-1.9.2.jar commons-logging-1.1.1.jar junit-4.9.jar log4j-1.2.17.jar mysql-connector-java-5.1.30.jar shiro-all-1.3.2.jar slf4j-api-1.7.5.jar slf4j-log4j12-1.7.5.jar
编写 shiro.ini 配置文件
[main]
#配置Realm
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
#配置数据源
dataSource = com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.driverClass = com.mysql.jdbc.Driver
dataSource.jdbcUrl = jdbc:mysql:///rbac
dataSource.user = root
dataSource.password = rootjdbcRealm.dataSource = $dataSource#将 Realm 注入给 SecurityManager
securityManager.realm = $jdbcRealm
测试
同简单认证的测试方法
4 自定义 Realm
好处:可以给 securityManager 更加灵活的安全数据源(eg。jdbcRealm 中表和字段都限定了)
方式:通过实现 Realm 接口,或根据需要继承他的响应子类即可。 AuthenticatingRealm
需求: 使用自定义 Realm ,从而实现认证。
实现步骤
导入 jar 包
com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar commons-beanutils-1.9.2.jar commons-logging-1.1.1.jar junit-4.9.jar log4j-1.2.17.jar mysql-connector-java-5.1.30.jar shiro-all-1.3.2.jar slf4j-api-1.7.5.jar slf4j-log4j12-1.7.5.jar
编写自定义 Realm
package com.szxy.realm;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;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.realm.AuthenticatingRealm;
import org.apache.shiro.subject.PrincipalCollection;import com.mysql.jdbc.Driver;public class CustRealm extends AuthenticatingRealm {private String principals; //用户名private String credentials; //密码private String driverName = "com.mysql.jdbc.Driver";private String url = "jdbc:mysql:///rbac";private String user = "root";private String password = "root";@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// 使用 jdbc 连接数据库Connection conn = null;Statement stat = null;ResultSet rs = null;try {//注册驱动/*Driver driver = new Driver();DriverManager.registerDriver(driver);*/Class.forName(driverName);//获取数据库连接conn = DriverManager.getConnection(url, user, password);//操作数据库对象stat = conn.createStatement();//定义 sql 语句String sql = "select username,password from users";//执行 查询语句rs = stat.executeQuery(sql);//获取 结果集if(rs.next()){principals = rs.getString("username");credentials = rs.getString("password");}} catch (SQLException | ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}finally{if(rs!=null){try {rs.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if(stat!=null){try {stat.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if(conn !=null){try {conn.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}//创建 AuthenticationInfo 接口 实现类SimpleAuthenticationInfo info =new SimpleAuthenticationInfo(principals, credentials,"custRealm");return info;}
}
编写 shiro.ini 配置文件
[main]
#配置Realm
custRealm = com.szxy.realm.CustRealm#将 Realm 注入给 SecurityManager
securityManager.realm = $custRealm
测试
同上测试
5 加密方式
对称加密算法
加密的密钥与解密的密钥相同
非对称算法算法
加密的密钥与解密的密钥不相同
公开密钥(publickey:简称公钥)和私有密钥(privatekey:简称私钥)。
公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。
对称与非对称算法
散列算法比较
MD5加密、加盐与迭代
加盐
原因:相同的 password 生产相同的 Hash 值是相同
原理:给原文加入随机数生成新的MD5
加盐 salt:指往原文加入的随机数
迭代
加密的次数
| Md5Hash的构造方法 | 参数名用法 | | ------------------------------------------------------------ | ----------------------- | | public Md5Hash(Object source) | source指加密的数据 | | public Md5Hash(Object source, Object salt) | salt 加盐 | | public Md5Hash(Object source, Object salt, int hashIterations) | hashIterations 迭代次数 | | public Md5Hash() | 无参构造器 |
代码示例
package com.szxy.md5;import org.apache.shiro.crypto.hash.Md5Hash;public class TestMd5 {public static void main(String[] args) {//直接加密 Md5Hash md5 = new Md5Hash("123");System.out.println(md5);//202cb962ac59075b964b07152d234b70//加盐 salt md5 = new Md5Hash("123", "zwz");System.out.println(md5); //3b52ecfc4719fcb6c863208063548792//迭代,即加密的次数md5 = new Md5Hash("123", "zwz", 2);System.out.println(md5); //de231265fe7ec45af1404ded2f365001 }
}
6 凭证匹配器 Authentication
AuthenticatingRealm
修改 realm
//ByteSource.Utilbytes(salt)
package com.szxy.realm;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;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.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;public class CustRealm extends AuthenticatingRealm{private String principals; //用户名private String credentials; //密码private String driverName = "com.mysql.jdbc.Driver";private String url = "jdbc:mysql:///shiro";private String user = "root";private String password = "root";private String salt = null; //加盐的盐值@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// 使用 jdbc 连接数据库Connection conn = null;Statement stat = null;ResultSet rs = null;try {//注册驱动/*Driver driver = new Driver();DriverManager.registerDriver(driver);*/Class.forName(driverName);//获取数据库连接conn = DriverManager.getConnection(url, user, password);//操作数据库对象stat = conn.createStatement();//定义 sql 语句String sql = "select username,password,salt from starUsers";//执行 查询语句rs = stat.executeQuery(sql);//获取 结果集if(rs.next()){principals = rs.getString("username");credentials = rs.getString("password");salt = rs.getString("salt");}} catch (SQLException | ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}finally{if(rs!=null){try {rs.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if(stat!=null){try {stat.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if(conn !=null){try {conn.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}ByteSource byteSource = ByteSource.Util.bytes(salt); //加盐//创建 AuthenticationInfo 接口 实现类SimpleAuthenticationInfo info =new SimpleAuthenticationInfo(principals, credentials,byteSource,"customRealm");return info;}
}
配置 shiro 配置文件
[main]#配置凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher#设置凭证匹配器的相关属性
credentialsMatcher.hashAlgorithmName=MD5
credentialsMatcher.hashIterations=2#配置Realm
customRealm=com.szxy.realm.CustRealm#配置Realm的凭证匹配器属性
customRealm.credentialsMatcher=$credentialsMatcher#将Realm注入给SecurityManager
securityManager.realm=$customRealm
7 授权 Authorization
授权,又称访问控制,是对资源访问管理的过程。
即对于认证通过的用户,授予他可以访问某些资源的权限
授权方式
代码触发、注解触发、标签触发
授权流程图
配置文件
[main]#配置凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher#设置凭证匹配器的相关属性
credentialsMatcher.hashAlgorithmName=MD5
credentialsMatcher.hashIterations=2#配置Realm
customRealm=com.szxy.realm.CustRealm#配置Realm的凭证匹配器属性
customRealm.credentialsMatcher=$credentialsMatcher#将Realm注入给SecurityManager
securityManager.realm=$customRealm
代码示例
package com.szxy.realm;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;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.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;public class CustRealm extends AuthorizingRealm{private String principals; //用户名private String credentials; //密码private String driverName = "com.mysql.jdbc.Driver";private String url = "jdbc:mysql:///shiro";private String user = "root";private String password = "root";private String salt = null; //加盐的盐值private String roleName; //角色名称private String remark;// 验证的方法@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// 使用 jdbc 连接数据库Connection conn = null;Statement stat = null;ResultSet rs = null;try {//注册驱动/*Driver driver = new Driver();DriverManager.registerDriver(driver);*/Class.forName(driverName);//获取数据库连接conn = DriverManager.getConnection(url, user, password);//操作数据库对象stat = conn.createStatement();//定义 sql 语句String sql = "select username,password,salt from starUsers";//执行 查询语句rs = stat.executeQuery(sql);//获取 结果集if(rs.next()){principals = rs.getString("username");credentials = rs.getString("password");salt = rs.getString("salt");}} catch (SQLException | ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}finally{if(rs!=null){try {rs.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if(stat!=null){try {stat.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if(conn !=null){try {conn.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}ByteSource byteSource = ByteSource.Util.bytes(salt); //加盐//创建 AuthenticationInfo 接口 实现类SimpleAuthenticationInfo info =new SimpleAuthenticationInfo(principals, credentials,byteSource,"customRealm");return info;}// 授权的方法@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 使用 jdbc 连接数据库Connection conn = null;Statement stat = null;ResultSet rs = null;try {//注册驱动/*Driver driver = new Driver();DriverManager.registerDriver(driver);*/Class.forName(driverName);//获取数据库连接conn = DriverManager.getConnection(url, user, password);//操作数据库对象stat = conn.createStatement();//定义 sql 语句//String sql = "select rolename from roles";String sql = "select * from permission"; //执行 查询语句rs = stat.executeQuery(sql);//获取 结果集if(rs.next()){//roleName = rs.getString("rolename");remark = rs.getString("remark");}} catch (SQLException | ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}finally{if(rs!=null){try {rs.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if(stat!=null){try {stat.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if(conn !=null){try {conn.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //info.addRole(roleName); //添加角色信息info.addStringPermission(remark); //添加权限信息return info;}
}
测试
package com.szxy.shiro;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;public class TestShiro {@Testpublic void TestAuthorization(){//构建 SecurityManagerFactory 工厂IniSecurityManagerFactory factory = new IniSecurityManagerFactory();//利用 SecurityManagerFactory 实例化 SecurityManager 对象SecurityManager securityManager = factory.getInstance();// 将 SecurityManager 设置到运行环境中SecurityUtils.setSecurityManager(securityManager);//通过SecurityUtils, 创建主体 SubjectSubject subject = SecurityUtils.getSubject();//创建用户密码令牌认证UsernamePasswordToken token = new UsernamePasswordToken("admin","123");//验证身份subject.login(token);//查看结果System.out.println(subject.isAuthenticated());//验证权限//基于角色授权boolean flag = subject.hasRole("管理员1");System.out.println(flag);//权限授权boolean flag2 = subject.isPermitted("一级菜单,基本设置操作权限");System.out.println(flag2);}}
8 spring 整合Shiro
新建项目,导入 jar 包
编写配置文件
编写 web.xml
DelegatingFilterProxy
通过代理模式将 servlet 容器中 filter 同 spring 容器的 bean 关联起来
<?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" id="WebApp_ID" version="2.5"><display-name>RBACDemo</display-name><welcome-file-list><welcome-file>index.html</welcome-file><welcome-file>index.htm</welcome-file><welcome-file>index.jsp</welcome-file><welcome-file>default.html</welcome-file><welcome-file>default.htm</welcome-file><welcome-file>default.jsp</welcome-file></welcome-file-list><!-- 监听 ServletContext ,注册 Spring 容器 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- 指定 spring 配置文件的名称及位置 --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext-*.xml</param-value></context-param><!-- 注册 SpringMVC 容器 --><servlet><servlet-name>DispatcherServlet</servlet-name><servlet-class> org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 指定 SpringMVC 配置文件的位置 --><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:SpringMVC.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>DispatcherServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping><!-- 开启编码过滤器 --><filter><filter-name>CharacterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><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>CharacterEncodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 注册 DelegatingFilterProxy 通过代理模式将 servlet 容器中 filter 同 spring 容器的 bean 关联起来--><filter><filter-name>delegatingFilterProxy</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class><!--该属性为 true 表名启用引用 filter 的init() 和 destroy() --><init-param><param-name>targetFilterLifecycle</param-name><param-value>true</param-value></init-param><!--该属性设置 spring 容器中 filter 的bean的 id--><init-param><param-name>targetBeanName</param-name><param-value>shiroFilter</param-value></init-param> </filter><filter-mapping><filter-name>delegatingFilterProxy</filter-name><url-pattern>/*</url-pattern></filter-mapping>
</web-app>
spring-shiro.xml
<!-- 注册凭证匹配器,并为其注入属性值-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"><!-- 加密方式 --><property name="hashAlgorithmName" value="md5"></property><!-- 迭代次数 --><property name="hashIterations" value="2" ></property>
</bean><!-- 注册自定义 realm -->
<bean id="customRealm" class="com.szxy.realm.CustomRealm"><property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean><!-- 注册 SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="customRealm"></property>
</bean><!--注册 shiroFilterFactoryBean 注意:bean 的id 名称必须与过滤器中的名称一致-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager"></property><property name="loginUrl" value="/login.do"></property><property name="successUrl" value="/jsp/users.jsp"></property><property name="unauthorizedUrl" value="/jsp/refuse.jsp"></property><!-- 设置过滤器链属性 --><property name="filterChainDefinitions"><value>/login.do=authc/**=anon</value></property>
</bean>
代码示例
9 DelegatingFilterProxy
DelegatingFilterProxy 源码分析
void initFilterBean() 初始化过滤器 bean 对象,并调用 Filter initDelegate(WebApplicationContext wac) 方法,
从 spring 容器取出 目标 target 的bean 对象,并对其他进行初始化操作。
@Override
protected void initFilterBean() throws ServletException {synchronized (this.delegateMonitor) {if (this.delegate == null) {// If no target bean name specified, use filter name.if (this.targetBeanName == null) {this.targetBeanName = getFilterName();}// Fetch Spring root application context and initialize the delegate early,// if possible. If the root application context will be started after this// filter proxy, we'll have to resort to lazy initialization.WebApplicationContext wac = findWebApplicationContext();if (wac != null) {this.delegate = initDelegate(wac); }}}//Initialize the Filter delegate, defined as bean the given Spring
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);if (isTargetFilterLifecycle()) {//该属性为 true 表名启用引用 filter 的init() 和 destroy() 交给 spring 容器管理 delegate.init(getFilterConfig());}return delegate;
}
10 spring 整合 Shiro 之注册
密码使用 md5 方式加密
表单序列化
使用该操作,可以直接获取表单数据,简化操作。
<script type="text/javascript" src="js/jquery-1.7.2.min.js"></script><script type="text/javascript" >$(function(){$("#btn").click(function(){$.ajax({url:"/user/register",type:"POST",data:$("#myForm").serialize(),//表单序列化,传递表单数据success:function(data){alert(data);}});});});
</script>
md5加密
@Override
public int userRegister(User user) {//使用 md5 加密用户的密码,并使用用户名作为 salt 值Md5Hash newPassword = new Md5Hash(user.getPassword(),user.getUsername(), 2);user.setPassword(newPassword.toString());user.setSalt(user.getUsername());return userMapper.insertUser(user);
}
9 实现菜单授权
11 SessionMapper 的使用
sessionMamanager 会话管理器管理着应用中所有的 Subject 的会话的创建、维护、删除、失效、验证等工作。
Shiro 提供了三个默认实现类:
| 类名 | 功能 | | ------------------------------ | ------------------------------------------------------------ | | DefaultSessionManager | 用于 JavaSE 环境 | | ServletContainerSessionManager | 用于 Web 环境,直接使用 tomcat 容器管理对象 | | DefaultWebSessionManager | 用于Web 环境,替代 类名功能DefaultSessionManager用于 JavaSE 环境ServletContainerSessionManager,自己维护会话。 |
<!-- 注册 sessionManager -->
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager"><!-- 设置 session 超时时间 ,以毫秒为单位 ms 1s 等于 1000ms --><property name="globalSessionTimeout" value="500000"></property><property name="deleteInvalidSessions" value="true"></property>
</bean>
12 remeber me 功能实现
即登录成功后,下次免登录
实现步骤
用户类即及其引用类的都需要序列化
修改页面 index .jsp
修改 Spring-shiro.xml
13 Shiro 内置过滤器
1 认证过滤器
anon authcBasic auchc user
2 授权过滤器
perms roles ssl rest port
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager"></property><property name="loginUrl" value="/user/login.do"></property><property name="successUrl" value="/user/loginSuccess.do"></property><property name="unauthorizedUrl" value="/jsp/refuse.jsp"></property><!-- 设置过滤器链属性 authc 认证过滤器,表示表单认证anon 放行,可以匿名访问,不需要认证user 用户过滤器,用于识别当前用户是否登录并保存密码 RememberMe--><property name="filterChainDefinitions"><value>/user/login.do=authc /user/loginSuccess.do=user/menu/showMenu.do=user/**=anon</value></property>
</bean>
注意
使用 Shiro 框架做用户登录的时候,若登录成功后,在使用浏览器回来之前的登录界面,这时就登录不上了,不仅是使用之前登录的账号,还是其他账号。
推测:可能是同一 浏览器登录后存在 cookie ,影响用户的二次登录。