当前位置: 首页 > news >正文

房屋网站模板模板网站的弊端

房屋网站模板,模板网站的弊端,品牌建设理论包括哪些内容,网站开发语言排行榜文章目录一、Shiro概述1、Shiro简介1.1 介绍1.2 Shiro特点2、Shiro与SpringSecurity的对比3、Shiro基本功能4、Shiro原理4.1 Shiro 架构(外部)4.2 shiro架构(内部)二、Shiro基本使用1、环境准备2、登录认证2.1 登录认证概念2.2 登录认证基本流程2.3 登录认证实例2.4 身份认证源… 文章目录一、Shiro概述1、Shiro简介1.1 介绍1.2 Shiro特点2、Shiro与SpringSecurity的对比3、Shiro基本功能4、Shiro原理4.1 Shiro 架构(外部)4.2 shiro架构(内部)二、Shiro基本使用1、环境准备2、登录认证2.1 登录认证概念2.2 登录认证基本流程2.3 登录认证实例2.4 身份认证源码流程3、角色与授权3.1 授权概念3.2 授权方式3.3 授权流程3.4 代码实例4、Shiro加密5、自定义登陆认证三、Shiro整合Springboot1、登录认证准备1.1 环境准备1.2 后端整合1.3 前端thymeleaf整合2、多 realm 认证策略2.1 实现原理2.2 配置修改3、remember me3.1 实现原理3.2 代码实现4、用户注销5、授权、角色认证5.1 后端接口服务注解5.2 授权验证-获取角色验证5.3 授权验证-获取权限验证5.4 权限验证异常处理类5.5 前端页面授权验证6、缓存管理6.1 缓存工具EhCache 6.2 Ehcache简单搭建6.3 Shiro整合EhCache7、会话管理7.1 SessionManager7.2 会话管理实现7.3 获得session方式一、Shiro概述 1、Shiro简介 1.1 介绍 官网https://shiro.apache.org/ Apache Shiro 是一个功能强大且易于使用的 Java 安全(权限)框架。Shiro 可以完成认证、授权、加密、会话管理、与 Web 集成、缓存 等。借助 Shiro 您可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的 Web 和企业应用程序。 1.2 Shiro特点 易于使用使用 Shiro 构建系统安全框架非常简单。就算第一次接触也可以快速掌握全面Shiro 包含系统安全框架需要的功能满足安全需求的“一站式服务”灵活Shiro 可以在任何应用程序环境中工作。虽然它可以在 Web、EJB 和 IoC 环境中工作但不需要依赖它们。Shiro 也没有强制要求任何规范甚至没有很多依赖项强力支持 WebShiro 具有出色的 Web 应用程序支持可以基于应用程序 URL 和 Web 协议例如 REST创建灵活的安全策略同时还提供一组 JSP 库来控制页面输出兼容性强Shiro 的设计模式使其易于与其他框架和应用程序集成。Shiro 与 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 等框架无缝集成社区支持Shiro 是 Apache 软件基金会的一个开源项目有完备的社区支持文档支持。如果需要像 Katasoft 这样的商业公司也会提供专业的支持和服务 2、Shiro与SpringSecurity的对比 SpringSecurity参考Spring Security学习笔记 Spring Security 基于 Spring 开发项目若使用 Spring 作为基础配合 Spring Security 做权限更加方便而 Shiro 需要和 Spring 进行整合开发Spring Security 功能比 Shiro 更加丰富些例如安全维护方面Spring Security 社区资源相对比 Shiro 更加丰富Shiro 的配置和使用比较简单Spring Security 上手复杂些Shiro 依赖性低不需要任何框架和容器可以独立运行。Spring Security 依赖 Spring 容器shiro 不仅仅可以使用在 web 中它可以工作在任何应用环境中。在集群会话时 Shiro 最重要的一个好处或许就是它的会话是独立于容器的 3、Shiro基本功能 Authentication身份认证/登录验证用户是不是拥有相应的身份Authorization授权即权限验证验证某个已认证的用户是否拥有某个权限即判断用 户是否能进行什么操作如验证某个用户是否拥有某个角色。或者细粒度的验证某个用户 对某个资源是否具有某个权限Session Manager会话管理即用户登录后就是一次会话在没有退出之前它的所有信息都在会话中会话可以是普通 JavaSE 环境也可以是 Web 环境的Cryptography加密保护数据的安全性如密码加密存储到数据库而不是明文存储Web SupportWeb 支持可以非常容易的集成到 Web 环境Caching缓存比如用户登录后其用户信息、拥有的角色/权限不必每次去查这样可以提高效率ConcurrencyShiro 支持多线程应用的并发验证即如在一个线程中开启另一个线程能把权限自动传播过去Testing提供测试支持Run As允许一个用户假装为另一个用户如果他们允许的身份进行访问Remember Me记住我这个是非常常见的功能即一次登录后下次再来的话不用登录了 4、Shiro原理 4.1 Shiro 架构(外部) 从外部来看 Shiro 即从应用程序角度的来观察如何使用Shiro 完成工作 Subject应用代码直接交互的对象是 Subject也就是说 Shiro 的对外 API 核心 就是 Subject。Subject 代表了当前“用户” 这个用户不一定 是一个具体的人与当前应用交互的任何东西都是 Subject如网络爬虫 机器人等与 Subject 的所有交互都会委托给 SecurityManager Subject 其实是一个门面SecurityManager 才是实际的执行者SecurityManager安全管理器即所有与安全有关的操作都会与 SecurityManager交互且其管理着所有 Subject可以看出它是** Shiro 的核心**它负责与 Shiro 的其他组件进行交互它相当于 SpringMVC 中 DispatcherServlet 的角色RealmShiro 从 Realm 获取安全数据如用户、角色、权限就是说SecurityManager 要验证用户身份那么它需要从 Realm 获取相应的用户 进行比较以确定用户身份是否合法也需要从 Realm 得到用户相应的角色/ 权限进行验证用户是否能进行操作可以把 Realm 看成 DataSource 4.2 shiro架构(内部) Subject任何可以与应用交互的“用户”SecurityManager 相当于 SpringMVC 中的 DispatcherServlet是 Shiro 的心脏 所有具体的交互都通过 SecurityManager 进行控制它管理着所有 Subject、且负责进 行认证、授权、会话及缓存的管理。Authenticator负责 Subject 认证是一个扩展点可以自定义实现可以使用认证策略Authentication Strategy即什么情况下算用户认证通过了Authorizer授权器、即访问控制器用来决定主体是否有权限进行相应的操作即控制着用户能访问应用中的哪些功能Realm可以有 1 个或多个 Realm可以认为是安全实体数据源即用于获取安全实体的可以是 JDBC 实现也可以是内存实现等等由用户提供所以一般在应用中都需要实现自己的 RealmSessionManager管理 Session 生命周期的组件而 Shiro 并不仅仅可以用在 Web环境也可以用在如普通的 JavaSE 环境CacheManager缓存控制器来管理如用户、角色、权限等的缓存的因为这些数据 基本上很少改变放到缓存中后可以提高访问的性能Cryptography密码模块Shiro 提高了一些常见的加密组件用于如密码加密/解密。 二、Shiro基本使用 1、环境准备 引入shiro依赖 dependencygroupIdorg.apache.shiro/groupIdartifactIdshiro-core/artifactIdversion1.9.0/version /dependencydependencygroupIdcommons-logging/groupIdartifactIdcommons-logging/artifactIdversion1.2/version /dependency创建ini文件因为Shiro获取权限相关信息可以通过数据库获取也可以通过ini配置文件获取后期放数据库 [users] shawn123456 leo1234562、登录认证 2.1 登录认证概念 身份验证一般需要提供如身份ID等一些标识信息来表明登录者的身份如提供email用户名/密码来证明在shiro中用户需要提供**principals身份**和credentials证明给shiro从而应用能验证用户身份principals身份即主体的标识属性可以是任何属性如用户名、邮箱等唯一即可。一个主体可以有多个principals但只有一个Primary principals一般是用户名/邮箱/手机号credentials证明/凭证即只有主体知道的安全值如密码/数字证书等。最常见的principals和credentials组合就是用户名/密码 2.2 登录认证基本流程 收集用户身份/凭证即如用户名/密码调用 Subject.login 进行登录如果失败将得到相应 的 AuthenticationException异常根据异常提示用户错误信息否则登录成功创建自定义的 Realm类继承 org.apache.shiro.realm.AuthenticatingRealm类,实现 doGetAuthenticationInfo() 方法 2.3 登录认证实例 初始化获取SecurityManager获取subject对象创建token对象web应用用户名密码从页面传递完成登录 public static void main(String[] args) {//1初始化获取SecurityManagerFactorySecurityManager factory new IniSecurityManagerFactory(classpath:shiro.ini);SecurityManager securityManager factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//2获取Subject对象Subject subject SecurityUtils.getSubject();//3创建token对象web应用用户名密码从页面传递AuthenticationToken token new UsernamePasswordToken(shawn,123456);//4完成登录try {subject.login(token);System.out.println(登录成功);}catch (UnknownAccountException e) {e.printStackTrace();System.out.println(用户不存在);}catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println(密码错误);}catch (AuthenticationException e) {e.printStackTrace();}} 2.4 身份认证源码流程 首先调用 Subject.login(token) 进行登录其会自动委托给 SecurityManagerSecurityManager负责真正的身份验证逻辑它会委托给 Authenticator进行身份验证Authenticator 才是真正的身份验证者Shiro API 中核心的身份 认证入口点此处可以自定义插入自己的实现Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份验证默认 ModularRealmAuthenticator会调用 AuthenticationStrategy进行多 Realm 身份验证Authenticator 会把相应的 token 传入 Realm从 Realm 获取 身份验证信息如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm将按照相应的顺序及策略进行访问。 3、角色与授权 3.1 授权概念 授权也叫访问控制即在应用中控制谁访问哪些资源如访问页面/编辑数据/页面 操作等。在授权中需了解的几个关键对象主体Subject、资源Resource、权限 Permission、角色Role主体(Subject)访问应用的用户在 Shiro 中使用 Subject 代表该用户。用户只有授权 后才允许访问相应的资源资源(Resource)在应用中用户可以访问的 URL比如访问 JSP 页面、查看/编辑 某些 数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问权限(Permission)安全策略中的原子授权单位通过权限我们可以表示在应用中用户 有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源如访问用 户列表页面查看/新增/修改/删除用户数据即很多时候都是CRUD增查改删式权限控 制等。权限代表了用户有没有操作某个资源的权利即反映在某个资源上的操作允不允 许。Shiro 支持粗粒度权限如用户模块的所有权限和细粒度权限操作某个用户的权限 即实例级别的角色(Role)权限的集合一般情况下会赋予用户角色而不是权限即这样用户可以拥有 一组权限赋予权限时比较方便。典型的如项目经理、技术总监、CTO、开发工程师等 都是角色不同的角色拥有一组不同的权限 3.2 授权方式 //编程式 subject.hasRole(admin) //注解式 RequiresRoles(admin) // JSP/GSP 标签 shiro:hasRole nameadmin /shiro:hasRole 3.3 授权流程 首先调用Subject.isPermitted的/hasRole接口其会委托给SecurityManager而SecurityManager接着会委托给 AuthorizerAuthorizer是真正的授权者如果调用如isPermitted(“user:view”)其首先会通过PermissionResolver把字符串转换成相应的Permission实例在进行授权之前其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限Authorizer会判断Realm的角色/权限是否和传入的匹配如果有多个Realm会委托给ModularRealmAuthorizer进行循环判断如果匹配如isPermitted/hasRole 会返回 true否则返回false表示授权失败 3.4 代码实例 首先修改resource下的shiro.ini文件 [users] zhangsan7174f64b13022acd3c56e2781e098a5f shawn123456,role1,role2[roles] role1user:insert,user:select修改主函数进行测试 public static void main(String[] args) {//1初始化获取SecurityManagerDefaultSecurityManager securityManagernew DefaultSecurityManager();IniRealm iniRealmnew IniRealm(classpath:shiro.ini);securityManager.setRealm(iniRealm);// 其中 shiro.ini 在 resources 的根目录下此方法已经过期// FactorySecurityManager factory new IniSecurityManagerFactory(classpath:shiro.ini);// SecurityManager securityManager factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//2获取Subject对象Subject subject SecurityUtils.getSubject();//3创建token对象web应用用户名密码从页面传递AuthenticationToken token new UsernamePasswordToken(shawn,123456);//4完成登录try {subject.login(token);System.out.println(登录成功);//5判断角色boolean hasRole subject.hasRole(role1);System.out.println(是否拥有此角色 hasRole);//6判断权限boolean permitted subject.isPermitted(user:insert1111);System.out.println(是否拥有此权限 permitted);//也可以用checkPermission方法但没有返回值没权限抛AuthenticationExceptionsubject.checkPermission(user:select1111);}catch (UnknownAccountException e) {e.printStackTrace();System.out.println(用户不存在);}catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println(密码错误);}catch (AuthenticationException e) {e.printStackTrace();} }4、Shiro加密 实际系统开发中一些敏感信息需要进行加密比如说用户的密码。Shiro 内嵌很多 常用的加密算法比如 MD5 加密。Shiro 可以很简单的使用信息加密。 public static void main(String[] args) {//密码明文String password shawn;//使用md5加密Md5Hash md5Hash new Md5Hash(password);System.out.println(md5加密 md5Hash.toHex());//带盐的md5加密盐就是在密码明文后拼接新字符串然后再进行加密Md5Hash md5Hash2 new Md5Hash(password,salt);System.out.println(带盐的md5加密 md5Hash2.toHex());//为了保证安全避免被破解还可以多次迭代加密保证数据安全Md5Hash md5Hash3 new Md5Hash(password,salt,3);System.out.println(md5带盐的3次加密 md5Hash3.toHex());//使用父类进行加密SimpleHash simpleHash new SimpleHash(MD5,password,salt,3);System.out.println(父类带盐的3次加密 simpleHash.toHex());}5、自定义登陆认证 Shiro 默认的登录认证是不带加密的如果想要实现加密认证需要自定义登录认证 自定义 Realm首先是创建自定义Realm public class MyRealm extends AuthenticatingRealm {//自定义登录认证方法shiro的login方法底层会调用该类的认证方法进行认证//需要配置自定义的realm生效在ini文件中配置在Springboot中配置//该方法只是获取进行对比的信息认证逻辑还是按照shiro底层认证逻辑完成Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//1获取身份信息String principal authenticationToken.getPrincipal().toString();//2获取凭证信息String password new String((char[])authenticationToken.getCredentials());System.out.println(认证用户信息principal---password);//3获取数据库中存储的用户信息if(principal.equals(shawn)){//3.1数据库中存储的加盐3次迭代的密码// 第二种方法密码需要自己进行加密 // String pwdInfo 123456 ;// 第一种方法String pwdInfo d1b129656359e35e95ebd56a63d7b9e0;//4创建封装校验逻辑对象封装数据返回AuthenticationInfo info new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),pwdInfo,ByteSource.Util.bytes(salt),authenticationToken.getPrincipal().toString());return info;}return null;} }第一种方法 public static void main(String[] args) {//1初始化获取SecurityManagerFactorySecurityManager factory new IniSecurityManagerFactory(classpath:shiro.ini);SecurityManager securityManager factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//2获取Subject对象Subject subject SecurityUtils.getSubject();//3创建token对象web应用用户名密码从页面传递AuthenticationToken token new UsernamePasswordToken(shawn,123456);//4完成登录try {subject.login(token);System.out.println(登录成功);//5判断角色boolean hasRole subject.hasRole(role1);System.out.println(是否拥有此角色 hasRole);//6判断权限boolean permitted subject.isPermitted(user:insert1111);System.out.println(是否拥有此权限 permitted);//也可以用checkPermission方法但没有返回值没权限抛AuthenticationExceptionsubject.checkPermission(user:select1111);}catch (UnknownAccountException e) {e.printStackTrace();System.out.println(用户不存在);}catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println(密码错误);}catch (AuthenticationException e) {e.printStackTrace();} }同时在Ini文件添加 [main] md5CredentialsMatcherorg.apache.shiro.authc.credential.Md5CredentialsMatcher md5CredentialsMatcher.hashIterations3myrealmcom.atguigu.shirotest.MyRealm myrealm.credentialsMatcher$md5CredentialsMatcher securityManager.realms$myrealm 第二种方法新的方法 public static void main(String[] args) {//1初始化获取SecurityManagerDefaultSecurityManager securityManagernew DefaultSecurityManager(new MyRealm());SecurityUtils.setSecurityManager(securityManager);//2获取Subject对象Subject subject SecurityUtils.getSubject();//3创建token对象web应用用户名密码从页面传递AuthenticationToken token new UsernamePasswordToken(shawn,123456);//4完成登录try {subject.login(token);System.out.println(登录成功);//5判断角色boolean hasRole subject.hasRole(role1);System.out.println(是否拥有此角色 hasRole);//6判断权限boolean permitted subject.isPermitted(user:insert1111);System.out.println(是否拥有此权限 permitted);//也可以用checkPermission方法但没有返回值没权限抛AuthenticationExceptionsubject.checkPermission(user:select1111);}catch (UnknownAccountException e) {e.printStackTrace();System.out.println(用户不存在);}catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println(密码错误);}catch (AuthenticationException e) {e.printStackTrace();} }三、Shiro整合Springboot 1、登录认证准备 1.1 环境准备 首先创建SpringBoot脚手架项目导入依赖 dependencygroupIdorg.apache.shiro/groupIdartifactIdshiro-spring-boot-web-starter/artifactIdversion1.9.0/version /dependency!--mybatis-plus-- dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.2/version /dependency!--mysql-- dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.30/version /dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId /dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-thymeleaf/artifactId /dependency !--配置Thymeleaf与Shrio的整合依赖-- dependencygroupIdcom.github.theborakompanioni/groupIdartifactIdthymeleaf-extras-shiro/artifactIdversion2.0.0/version /dependency!--Shiro整合EhCache-- dependencygroupIdorg.apache.shiro/groupIdartifactIdshiro-ehcache/artifactIdversion1.10.0/version /dependency dependencygroupIdcommons-io/groupIdartifactIdcommons-io/artifactIdversion2.11.0/version /dependencyMysql脚本配置好mysql CREATE DATABASE IF NOT EXISTS shirodb CHARACTER SET utf8mb4; USE shirodb; CREATE TABLE user (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 编号,name VARCHAR(30) DEFAULT NULL COMMENT 用户名,pwd VARCHAR(50) DEFAULT NULL COMMENT 密码,rid BIGINT(20) DEFAULT NULL COMMENT 角色编号, PRIMARY KEY(id) ) ENGINE INNODB AUTO_INCREMENT 2 DEFAULT CHARSET utf8 COMMENT 用户表;配置application.yaml文件 mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath:mapper/*.xml spring:datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/shirodb?characterEncodingutf-8useSSLfalseusername: rootpassword: rootjackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT8 # 未认证的请求重定向地址 shiro:loginUrl: /myController/login 1.2 后端整合 创建各个模块和类 // 实体类 Data NoArgsConstructor AllArgsConstructor public class User {private Integer id;private String name;private String pwd;private Integer rid; }// Mapper层 Mapper public interface UserMapper extends BaseMapperUser {}// Service层接口类自己定义(方法抽取一下即可) Service public class UserServiceImpl implements UserService {Autowiredprivate UserMapper userMapper;Overridepublic User getUserInfoByName(String name) {QueryWrapperUser wrapper new QueryWrapper();wrapper.eq(name,name);User user userMapper.selectOne(wrapper);return user;} }// controller层 Controller RequestMapping(myController) public class MyController {/*** 登录* param username 用户名* param password 密码* return {link String}*/GetMapping(userLogin)ResponseBodypublic String login(RequestParam(username) String username, RequestParam(password) String password){// 默认30分钟过期//永不过期,在登陆最开始加上//SecurityUtils.getSubject().getSession().setTimeout(-1000L);//其他时间 单位毫秒//SecurityUtils.getSubject().getSession().setTimeout(1800000);Subject subject SecurityUtils.getSubject();UsernamePasswordToken usernamePasswordToken new UsernamePasswordToken(username, password);try{subject.login(usernamePasswordToken);return success;}catch (Exception e) {e.printStackTrace();return error;}} } 创建自定义realm Component public class MyRealm extends AuthorizingRealm {Autowiredprivate UserService userService;/*** 自定义授权** param principals 权限* return {link AuthorizationInfo}*/Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}/*** 自定义身份验证** param token 令牌* return {link AuthenticationInfo}* throws AuthenticationException 身份验证异常*/Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//1. 获取用户身份信息String name token.getPrincipal().toString();//2. 调用业务层获取用户信息User user userService.getUserInfoByName(name);//3. 非空判断,将数据封装返回if (user ! null) {return new SimpleAuthenticationInfo(token.getPrincipal(),user.getPwd(),ByteSource.Util.bytes(salt),name);}return null;} } 创建配置类 Configuration public class ShiroConfig {Autowiredprivate MyRealm myRealm;//配置SecurityManagerBeanpublic DefaultWebSecurityManager defaultWebSecurityManager(){//1创建defaultWebSecurityManager 对象DefaultWebSecurityManager defaultWebSecurityManager new DefaultWebSecurityManager();//2创建加密对象设置相关属性HashedCredentialsMatcher matcher new HashedCredentialsMatcher();//2.1采用md5加密matcher.setHashAlgorithmName(md5);//2.2迭代加密次数matcher.setHashIterations(3);//3将加密对象存储到myRealm中myRealm.setCredentialsMatcher(matcher);//4将myRealm存入defaultWebSecurityManager 对象defaultWebSecurityManager.setRealm(myRealm);//5返回return defaultWebSecurityManager;}//配置Shiro内置过滤器拦截范围Beanpublic DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){DefaultShiroFilterChainDefinition definition new DefaultShiroFilterChainDefinition();//设置不认证可以访问的资源definition.addPathDefinition(/myController/userLogin,anon);definition.addPathDefinition(/myController/login,anon);//设置需要进行登录认证的拦截范围definition.addPathDefinition(/**,authc);return definition;} } 1.3 前端thymeleaf整合 引入thymeleaf依赖后在resource/templares创建login.html和main.html !--login.html-- !DOCTYPE html html langen headmeta charsetUTF-8titleTitle/title /head body h1Shiro 登录认证/h1 br form action/myController/userLogindiv用户名input typetext nameusername value/divdiv密码input typepassword namepassword value/divdivinput typesubmit value登录/div /form /body /html!--main.html-- !DOCTYPE html html langen xmlns:thhttp://www.thymeleaf.org headmeta charsetUTF-8titleTitle/title /head body h1brShiro 登录认证后主页面/h1登录用户为 span th:text${session.user}/span /body 改造controller让其返回走视图处理器去除ResponseBody Controller RequestMapping(myController) public class MyController {//跳转登录页面GetMapping(login)public String login(){return login;}GetMapping(userLogin)public String login(RequestParam(username) String username, RequestParam(password) String password,HttpSession session){//1 获取 Subject 对象Subject subject SecurityUtils.getSubject();//2 封装请求数据到 token 对象中AuthenticationToken token new UsernamePasswordToken(username,password);//3 调用 login 方法进行登录认证try {subject.login(token);session.setAttribute(user,token.getPrincipal().toString());return main;} catch (AuthenticationException e) {e.printStackTrace();System.out.println(登录失败);return 登录失败;}}}2、多 realm 认证策略 2.1 实现原理 当应用程序配置多个 Realm 时例如用户名密码校验、手机号验证码校验等等。 Shiro 的 ModularRealmAuthenticator 会使用内部的AuthenticationStrategy 组件判断认证是成功还是失败。 AuthenticationStrategy 是一个无状态的组件它在身份验证尝试中被询问 4 次这 4 次交互所需的任何必要的状态将被作为方法参数 在所有 Realm 被调用之前在调用 Realm 的 getAuthenticationInfo 方法之前在调用 Realm 的 getAuthenticationInfo 方法之后在所有 Realm 被调用之后 认证策略的另外一项工作就是聚合所有 Realm 的结果信息封装至一个AuthenticationInfo 实例中并将此信息返回以此作为 Subject 的身份信息。Shiro 中定义了 3 种认证策略的实现 认证策略类描述AtLeastOneSuccessfulStrategy只要有一个或更多的 Realm 验证成功那么认证将视为成功FirstSuccessfulStrategy第一个 Realm 验证成功整体认证将视为成功且后续 Realm 将被忽略AllSuccessfulStrategy所有 Realm 成功认证才视为成功 ModularRealmAuthenticator 内置的认证策略默认实现是 AtLeastOneSuccessfulStrategy 方式。可以通过配置修改策略 2.2 配置修改 Bean public DefaultWebSecurityManager defaultWebSecurityManager(){//1 创建 defaultWebSecurityManager 对象DefaultWebSecurityManager defaultWebSecurityManager new DefaultWebSecurityManager();//2 创建认证对象并设置认证策略ModularRealmAuthenticator modularRealmAuthenticator new ModularRealmAuthenticator();modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);//3 封装 myRealm 集合ListRealm list new ArrayList();list.add(myRealm);list.add(myRealm2);//4 将 myRealm 存入 defaultWebSecurityManager 对象defaultWebSecurityManager.setRealms(list);//5 返回return defaultWebSecurityManager; } 3、remember me 3.1 实现原理 Shiro 提供了记住我RememberMe的功能比如访问一些网站时关闭了浏览器 下次再打开时还是能记住你是谁 下次访问时无需再登录即可访问。 基本流程 首先在登录页面选中 RememberMe 然后登录成功如果是浏览器登录一般会 把 RememberMe 的 Cookie 写到客户端并保存下来关闭浏览器再重新打开会发现浏览器还是记住你的访问一般的网页服务器端仍然知道你是谁且能正常访问但是如果我们访问电商平台时如果要查看我的订单或进行支付时此时还是需要再进行身份认证的以确保当前用户还是你。 3.2 代码实现 过滤器可以参考Shiro学习之过滤器详解 修改配置类注意未认证的重定向在yml中进行配置 Configuration public class ShiroConfig {Autowiredprivate MyRealm myRealm;//配置 SecurityManagerBeanpublic DefaultWebSecurityManager defaultWebSecurityManager() {//1 创建 defaultWebSecurityManager 对象DefaultWebSecurityManager defaultWebSecurityManager new DefaultWebSecurityManager();//2 创建加密对象并设置相关属性HashedCredentialsMatcher matcher new HashedCredentialsMatcher();//2.1 采用 md5 加密matcher.setHashAlgorithmName(md5);//2.2 迭代加密次数matcher.setHashIterations(3);//3 将加密对象存储到 myRealm 中myRealm.setCredentialsMatcher(matcher);//4 将 myRealm 存入 defaultWebSecurityManager 对象defaultWebSecurityManager.setRealm(myRealm);//4.5 设置 rememberMedefaultWebSecurityManager.setRememberMeManager(rememberMeManager());//5 返回return defaultWebSecurityManager;}//cookie 属性设置public SimpleCookie rememberMeCookie() {SimpleCookie cookie new SimpleCookie(rememberMe);//设置跨域//cookie.setDomain(domain);cookie.setPath(/);cookie.setHttpOnly(true);cookie.setMaxAge(30 * 24 * 60 * 60);return cookie;}//创建 Shiro 的 cookie 管理对象public CookieRememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());cookieRememberMeManager.setCipherKey(1234567890987654.getBytes());return cookieRememberMeManager;}//配置 Shiro 内置过滤器拦截范围Beanpublic DefaultShiroFilterChainDefinition shiroFilterChainDefinition() {DefaultShiroFilterChainDefinition definition new DefaultShiroFilterChainDefinition();//设置不认证可以访问的资源definition.addPathDefinition(/myController/userLogin, anon);definition.addPathDefinition(/myController/login, anon);//设置需要进行登录认证的拦截范围definition.addPathDefinition(/**, authc);//添加存在用户的过滤器rememberMedefinition.addPathDefinition(/**, user);return definition;} }修改controller Controller RequestMapping(myController) public class MyController {//跳转登录页面GetMapping(login)public String login(){return login;}GetMapping(userLogin)public String userLogin(String name, String pwd,RequestParam(defaultValue false)boolean rememberMe,HttpSession session){//1获取subject对象Subject subject SecurityUtils.getSubject();//2封装请求数据到tokenAuthenticationToken token new UsernamePasswordToken(name,pwd,rememberMe);//3调用login方法进行登录认证try {subject.login(token);//return 登录成功;session.setAttribute(user,token.getPrincipal().toString());return main;} catch (AuthenticationException e) {e.printStackTrace();System.out.println(登录失败);return 登录失败;}}//登录认证验证rememberMeGetMapping(userLoginRm)public String userLogin(HttpSession session) {session.setAttribute(user,rememberMe);return main;} }改造login页面 !DOCTYPE html html langen headmeta charsetUTF-8titleTitle/title /head body h1Shiro 登录认证/h1 br form action/myController/userLogindiv用户名input typetext namename value/divdiv密码input typepassword namepwd value/divdiv记住用户input typecheckbox namerememberMe valuetrue/divdivinput typesubmit value登录/div /form /body /html 4、用户注销 用户登录后配套的有登出操作。直接通过Shiro过滤器即可实现登出首先修改main.html body h1Shiro 登录认证后主页面/h1 br 登录用户为span th:text${session.user}/span br a href/logout登出/a /body 配置类中添加logout过滤器 Bean public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition definition new DefaultShiroFilterChainDefinition();//设置不认证可以访问的资源definition.addPathDefinition(/myController/userLogin,anon); definition.addPathDefinition(/myController/login,anon); //配置登出过滤器definition.addPathDefinition(/logout,logout); //设置需要进行登录认证的拦截范围definition.addPathDefinition(/**,authc); //添加存在用户的过滤器rememberMe definition.addPathDefinition(/**,user); return definition; } 5、授权、角色认证 用户登录后需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判断。这个工具就是Realm的doGetAuthorizationInfo方法进行判断触发权限判断的有两种方式 在页面中通过shiro:属性判断在接口服务中通过注解Requires进行判断 5.1 后端接口服务注解 通过给接口服务方法添加注解可以实现权限校验可以加在控制器方法上也可以加 在业务方法上一般加在控制器方法上。常用注解如下 RequiresAuthentication 验证用户是否登录等同于方法subject.isAuthenticated() RequiresUser 验证用户是否被记忆 登录认证成功subject.isAuthenticated()为true 登录后被记忆subject.isRemembered()为true RequiresGuest 验证是否是一个guest的请求是否是游客的请求 此时subject.getPrincipal()为null RequiresRoles 验证subject是否有相应角色有角色访问方法没有则会抛出异常 AuthorizationException。例如RequiresRoles(aRoleName) void someMethod(); 只有subject有aRoleName角色才能访问方法someMethod() RequiresPermissions 验证subject是否有相应权限有权限访问方法没有则会抛出异常 AuthorizationException。 例如RequiresPermissions (file:read,wite:aFile.txt) void someMethod();subject必须同时含有file:read和wite:aFile.txt权限才能访问方someMethod() 5.2 授权验证-获取角色验证 首先添加数据库 CREATE TABLE role (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 编号, name VARCHAR(30) DEFAULT NULL COMMENT 角色名,desc VARCHAR(50) DEFAULT NULL COMMENT 描述,realname VARCHAR(20) DEFAULT NULL COMMENT 角色显示名, PRIMARY KEY (id) ) ENGINEINNODB AUTO_INCREMENT2 DEFAULT CHARSETutf8 COMMENT角色表;CREATE TABLE role_user (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 编号, uid BIGINT(20) DEFAULT NULL COMMENT 用户 id,rid BIGINT(20) DEFAULT NULL COMMENT 角色 id, PRIMARY KEY (id) ) ENGINEINNODB AUTO_INCREMENT2 DEFAULT CHARSETutf8 COMMENT角色用户映射表; mapper Repository public interface UserMapper extends BaseMapperUser {Select(SELECT NAME FROM role WHERE id IN (SELECT rid FROM role_user WHERE uid(SELECT id FROM USER WHERE NAME#{principal})))ListString getUserRoleInfoMapper(Param(principal) String principal); } service服务实现类 Override public ListString getUserRoleInfo(String principal) { return userMapper.getUserRoleInfoMapper(principal); }MyRealm 授权认证方法改造 Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println(进入自定义授权方法); //获取当前用户身份信息String principal principalCollection.getPrimaryPrincipal().toString();//调用接口方法获取用户的角色信息ListString roles userService.getUserRoleInfo(principal); System.out.println(当前用户角色信息roles);//创建对象存储当前登录的用户的权限和角色SimpleAuthorizationInfo info new SimpleAuthorizationInfo(); //存储角色,测试的话可以直接塞String的角色info.addRoles(roles);//返回 return info; }最后controller添加授权方法在mian.html添加a href/myController/userLoginRoles测试授权-角色验证/a测试链接即可进行测试(数据库数据自行添加) //登录认证验证角色 RequiresRoles(admin) GetMapping(userLoginRoles) ResponseBody public String userLoginRoles(){System.out.println(登录认证验证角色);return 验证角色成功; }5.3 授权验证-获取权限验证 创建权限数据表 CREATE TABLE permissions (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 编号, name VARCHAR(30) DEFAULT NULL COMMENT 权限名,info VARCHAR(30) DEFAULT NULL COMMENT 权限信息, desc VARCHAR(50) DEFAULT NULL COMMENT 描述, PRIMARY KEY (id) ) ENGINEINNODB AUTO_INCREMENT2 DEFAULT CHARSETutf8 COMMENT权限表;CREATE TABLE role_ps (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 编号, rid BIGINT(20) DEFAULT NULL COMMENT 角色 id,pid BIGINT(20) DEFAULT NULL COMMENT 权限 id, PRIMARY KEY (id) ) ENGINEINNODB AUTO_INCREMENT2 DEFAULT CHARSETutf8 COMMENT角色权限映射表; 创建方法 //Mapper类 Select({script,select info FROM permissions WHERE id IN ,(SELECT pid FROM role_ps WHERE rid IN (,SELECT id FROM role WHERE NAME IN ,foreach collectionroles itemname open( separator, close),#{name},/foreach,)),/script }) ListString getUserPermissionInfoMapper(Param(roles)ListString roles);// Service层 Override public ListString getUserPermissionInfo(ListString roles) { return userMapper.getUserPermissionInfoMapper(roles); } 修改MyRealm配置类 //自定义授权方法获取当前登录用户权限信息返回给 Shiro 用来进行授权对比 Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println(进入自定义授权方法);//获取当前用户身份信息String principal principalCollection.getPrimaryPrincipal().toString();//调用接口方法获取用户的角色信息ListString roles userService.getUserRoleInfo(principal);System.out.println(当前用户角色信息roles);//调用接口方法获取用户角色的权限信息ListString permissions userService.getUserPermissionInfo(roles);System.out.println(当前用户权限信息permissions);//创建对象存储当前登录的用户的权限和角色SimpleAuthorizationInfo info new SimpleAuthorizationInfo();//存储角色info.addRoles(roles);//存储权限信息info.addStringPermissions(permissions);//返回 return info; }controller层 //登录认证验证权限 RequiresPermissions(user:delete) GetMapping(userPermissions) ResponseBody public String userLoginPermissions(){System.out.println(登录认证验证权限);return 验证权限成功; }修改main.html文件 body h1Shiro 登录认证后主页面/h1 br 登录用户为span th:text${session.user}/span br a href/logout登出/a br a href/myController/userLoginRoles测试授权-角色验证/a br a href/myController/userPermissions测试授权-权限验证/a /body 5.4 权限验证异常处理类 ControllerAdvice public class PermissionsException {ResponseBodyExceptionHandler(UnauthorizedException.class)public String unauthorizedException(Exception e){return 无权限;}ResponseBodyExceptionHandler(AuthorizationException.class)public String authorizationException(Exception e){return 权限认证失败;}}5.5 前端页面授权验证 前端可以根据不同的权限显示不同的信息首先添加依赖 dependencygroupIdcom.github.theborakompanioni/groupId artifactIdthymeleaf-extras-shiro/artifactId version2.0.0/version /dependency 配置类用于解析 thymeleaf 中的 shiro:相关属性 Bean public ShiroDialect shiroDialect(){return new ShiroDialect(); }Thymeleaf 中常用的 shiro:属性 !--guest 标签用户没有身份验证时显示相应信息即游客访问信息-- shiro:guest /shiro:guest!--user标签用户已经身份验证/记住我登录后显示相应的信息-- shiro:user /shiro:user!--authenticated 标签用户已经身份验证通过即 Subject.login 登录成功不是记住我登录的-- shiro:authenticated /shiro:authenticated!--notAuthenticated 标签用户已经身份验证通过即没有调用 Subject.login 进行登录包括记住我自动登录的 也属于未进行身份验证-- shiro:notAuthenticated /shiro:notAuthenticated!--principal 标签当于((User)Subject.getPrincipals()).getUsername()-- shiro: principal/ shiro:principal propertyusername/!--lacksPermission 标签如果当前 Subject 没有权限将显示 body 体内容-- shiro:lacksPermission nameorg:create /shiro:lacksPermission!--hasRole标签如果当前 Subject 有角色将显示 body 体内容-- shiro:hasRole nameadmin /shiro:hasRole!--hasAnyRoles 标签标签如果当前 Subject 有任意一个角色或的关系将显示 body 体内容-- shiro:hasAnyRoles nameadmin,user /shiro:hasAnyRoles!--lacksRole标签如果当前 Subject 没有角色将显示 body 体内容-- shiro:lacksRole nameabc /shiro:lacksRole!--lacksPermission 标签如果当前 Subject 有权限将显示 body 体内容-- shiro:hasPermission nameuser:create /shiro:hasPermission 6、缓存管理 6.1 缓存工具EhCache EhCache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存Java EE和轻量级容器。可以和大部分Java项目无缝整合例如Hibernate中的缓存就是基于EhCache实现的。EhCache支持内存和磁盘存储默认存储在内存中如内存不够时把缓存数据同步到磁盘中。EhCache支持基于Filter的Cache实现也支持Gzip压缩算法。 EhCache直接在JVM虚拟机中缓存速度快效率高EhCache缺点是缓存共享麻烦集群分布式应用使用不方便 6.2 Ehcache简单搭建 引入依赖 dependencygroupIdnet.sf.ehcache/groupIdartifactIdehcache/artifactIdversion2.6.11/versiontypepom/type /dependency创建配置文件 ?xml version1.0 encodingUTF-8? ehcache!--磁盘的缓存位置--diskStore pathjava.io.tmpdir/ehcache/!--默认缓存--defaultCachemaxEntriesLocalHeap10000eternalfalsetimeToIdleSeconds120timeToLiveSeconds120maxEntriesLocalDisk10000000diskExpiryThreadIntervalSeconds120memoryStoreEvictionPolicyLRUpersistence strategylocalTempSwap//defaultCache!--helloworld缓存--cache nameHelloWorldCachemaxElementsInMemory1000eternalfalsetimeToIdleSeconds5timeToLiveSeconds5overflowToDiskfalsememoryStoreEvictionPolicyLRU/!--defaultCache默认缓存策略当ehcache找不到定义的缓存时则使用这个缓存策略。只能定义一个。--!--name:缓存名称。maxElementsInMemory:缓存最大数目maxElementsOnDisk硬盘最大缓存个数。eternal:对象是否永久有效一但设置了timeout将不起作用。overflowToDisk:是否保存到磁盘当系统宕机时timeToIdleSeconds:设置对象在失效前的允许闲置时间单位秒。仅当eternalfalse对象不是永久有效时使用可选属性默认值是0也就是可闲置时间无穷大。timeToLiveSeconds:设置对象在失效前允许存活时间单位秒。最大时间介于创建时间和失效时间之间。仅当eternalfalse对象不是永久有效时使用默认是0.也就是对象存活时间无穷大。diskPersistent是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.diskSpoolBufferSizeMB这个参数设置DiskStore磁盘缓存的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。diskExpiryThreadIntervalSeconds磁盘失效线程运行时间间隔默认是120秒。memoryStoreEvictionPolicy当达到maxElementsInMemory限制时Ehcache将会根据指定的策略去清理内存。默认策略是LRU最近最少使用。你可以设置为FIFO先进先出或是LFU较少使用。clearOnFlush内存数量最大时是否清除。memoryStoreEvictionPolicy:可选策略有LRU最近最少使用默认策略、FIFO先进先出、LFU最少访问次数。FIFOfirst in first out这个是大家最熟的先进先出。LFU Less Frequently Used就是上面例子中使用的策略直白一点就是讲一直以来最少被使用的。如上面所讲缓存的元素有一个hit属性hit值最小的将会被清出缓存。LRULeast Recently Used最近最少使用的缓存的元素有一个时间戳当缓存容量满了而又需要腾出地方来缓存新的元素的时候那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。--/ehcache 测试类 public static void main(String[] args) {//获取编译目录下的资源的流对象InputStream input TestEH.class.getClassLoader().getResourceAsStream(ehcache.xml);//获取EhCache的缓存管理对象CacheManager cacheManager new CacheManager(input);//获取缓存对象Cache cache cacheManager.getCache(HelloWorldCache);//创建缓存数据Element element new Element(name,shawn);//存入缓存cache.put(element);//从缓存中取出数据输出Element element1 cache.get(name);System.out.println(缓存中数据 element1.getObjectValue()); } 6.3 Shiro整合EhCache !--Shiro整合EhCache-- dependencygroupIdorg.apache.shiro/groupIdartifactIdshiro-ehcache/artifactIdversion1.10.0/version /dependencydependencygroupIdcommons-io/groupIdartifactIdcommons-io/artifactIdversion2.11.0/version /dependency然后在resources下添加配置文件ehcache/ehcache-shiro.xml ?xml version1.0 encodingUTF-8? ehcache nameehcache updateCheckfalse !--磁盘的缓存位置--diskStore pathjava.io.tmpdir/ !--默认缓存--defaultCachemaxEntriesLocalHeap1000eternalfalsetimeToIdleSeconds3600timeToLiveSeconds3600overflowToDiskfalse/defaultCache !--登录认证信息缓存缓存用户角色权限--cache nameloginRolePsCachemaxEntriesLocalHeap2000eternalfalsetimeToIdleSeconds600timeToLiveSeconds0overflowToDiskfalsestatisticstrue/ /ehcache最后修改Shiro配置 Configuration public class ShiroConfig {Autowiredprivate MyRealm myRealm;//配置SecurityManagerBeanpublic DefaultWebSecurityManager defaultWebSecurityManager(){//1创建defaultWebSecurityManager 对象DefaultWebSecurityManager defaultWebSecurityManager new DefaultWebSecurityManager();//2创建加密对象设置相关属性HashedCredentialsMatcher matcher new HashedCredentialsMatcher();//2.1采用md5加密matcher.setHashAlgorithmName(md5);//2.2迭代加密次数matcher.setHashIterations(3);//3将加密对象存储到myRealm中myRealm.setCredentialsMatcher(matcher);//4将myRealm存入defaultWebSecurityManager 对象defaultWebSecurityManager.setRealm(myRealm);//4.5设置rememberMedefaultWebSecurityManager.setRememberMeManager(rememberMeManager());//4.6设置缓存管理器defaultWebSecurityManager.setCacheManager(getEhCacheManager());//5返回return defaultWebSecurityManager;}//缓存管理器public EhCacheManager getEhCacheManager(){EhCacheManager ehCacheManager new EhCacheManager();InputStream is null;try {is ResourceUtils.getInputStreamForPath(classpath:ehcache/ehcache-shiro.xml);} catch (IOException e) {e.printStackTrace();}CacheManager cacheManager new CacheManager(is);ehCacheManager.setCacheManager(cacheManager);return ehCacheManager;}//cookie 属性设置public SimpleCookie rememberMeCookie() {SimpleCookie cookie new SimpleCookie(rememberMe);//设置跨域//cookie.setDomain(domain);cookie.setPath(/);cookie.setHttpOnly(true);cookie.setMaxAge(30 * 24 * 60 * 60);return cookie;}//创建 Shiro 的 cookie 管理对象public CookieRememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());cookieRememberMeManager.setCipherKey(1234567890987654.getBytes());return cookieRememberMeManager;}//配置 Shiro 内置过滤器拦截范围Beanpublic DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){DefaultShiroFilterChainDefinition definition new DefaultShiroFilterChainDefinition();//设置不认证可以访问的资源definition.addPathDefinition(/myController/userLogin,anon);definition.addPathDefinition(/myController/login,anon);//配置登出过滤器definition.addPathDefinition(/logout,logout);//设置需要进行登录认证的拦截范围definition.addPathDefinition(/**,authc);//添加存在用户的过滤器rememberMedefinition.addPathDefinition(/**,user);return definition;} } 最后访问权限测试发现其成功缓存 7、会话管理 7.1 SessionManager 会话管理器负责创建和管理用户的会话Session生命周期它能够在任何环境中在本地管理用户会话即使没有Web/Servlet/EJB容器也一样可以保存会话。默认情况下Shiro会检测当前环境中现有的会话机制比如Servlet容器进行适配如果没有比如独立应用程序或者非Web环境它将会使用内置的企业会话管理器来提供相应的会话管理服务其中还涉及一个名为SessionDAO的对象。SessionDAO负责Session的持久化操作CRUD允许Session数据写入到后端持久化数据库 7.2 会话管理实现 SessionManager由SecurityManager管理。Shiro提供了三种实现 DefaultSessionManager用于JavaSE环境ServletContainerSessionManager用于web环境直接使用Servlet容器的会话DefaultWebSessionManager用于web环境自己维护会话不使用Servlet容器的会话管理 7.3 获得session方式 实现 Session session SecurityUtils.getSubject().getSession();session.setAttribute(key,value) 说明 Controller中的request在shiro过滤器中的doFilerInternal方法被包装成ShiroHttpServletRequest。 SecurityManager和SessionManager会话管理器决定session来源于ServletRequest还是由Shiro管理的会话。 无论是通过request.getSession或subject.getSession获取到session操作session两者都是等价的。 参考 shiro框架如何保持登录状态 https://www.bilibili.com/video/BV11e4y1n7BH
http://www.hkea.cn/news/14455809/

相关文章:

  • 工信部 网站开发设计师海阳有没有做企业网站的
  • 主流做网站程序代码成都私人网站制作公司
  • 铜陵app网站做招聘洛阳小程序开发公司
  • 番禺高端网站制作做网红用哪个网站
  • 建设银行的网站用户名是什么问题万网wordpress
  • 浅谈网站建设的目的和意义如何做网站demo
  • 屏蔽收索引擎抓取网站wordpress 评论表情插件
  • 关于插画的网站wordpress中文版主题百度网盘
  • 做仿站如何修改网站管理权限做兼职网站赚钱吗
  • 如何做百万格子网站wordpress菜单与页面关联
  • 手机排行榜网站网站建设及发布的流程
  • 建材类网站建设需要的资料高端上海网站设计公司
  • 淮安做网站建设的网络公司男人和女人床上做性视频网站
  • 网站建设工程师证书网络运营商ip地址
  • 做装修公司的网站校园网站建设培训的心得体会
  • 如何申请做网站php做网站半成品
  • 信阳网站seo怎样做免费网站
  • 在对方网站做友情链接莱芜梆子网站
  • 山西运城给网站做系统的公司wordpress 登陆不跳转
  • 北京网站建设设计公司让wordpress的页面有具体的地址
  • 论述农产品电商网站建设无锡网站建设首选捷搜
  • 产品定制网站阿里云和wordpress
  • 自助游戏充值网站怎么做做直播网站前端
  • 怎么租域名做网站网站动态程序
  • 大连网站开发公司排名wordpress使postid顺序
  • 做旅游网站的产品经理如何定制网站系统开发
  • 做购物商城网站设计ui设计是什么职位
  • 网站收录查询api长沙做优化的公司
  • 重庆建设摩托官方网站图片网址生成器
  • 如何制作自己的视频网站wordpress颜色