创建一个Maven工程
4.0.0 org.example demo 1.0-SNAPSHOT war 8 8 org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE org.apache.shiro shiro-spring-boot-web-starter 1.9.0 com.baomidou mybatis-plus-boot-starter 3.0.5 mysql mysql-connector-java 5.1.46 org.projectlombok lombok org.springframework.boot spring-boot-starter-thymeleaf
添加application.yml配置文件,注意修改数据库的连接参数。
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.jdbc.Driverurl: jdbc:mysql://localhost:3306/demo?characterEncoding=utf8&useSSL=falseusername: rootpassword: rootjackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8
shiro:loginUrl: /login/login
package com.shiro.demo;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.shiro.demo.mapper")
public class ShiroApplication {public static void main(String[] args) {SpringApplication.run(ShiroApplication.class, args);}
}
创建用户表,这个在Shiro笔记02-基本使用里有。
创建实体类User.java
,使用Lombok插件简化代码。
package com.shiro.demo.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private Long id;private String username;private String password;private String passwordSalt;
}
创建mapper、service、serviceImpl。
package com.shiro.demo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shiro.demo.entity.User;public interface UserMapper extends BaseMapper {
}
package com.shiro.demo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shiro.demo.entity.User;
import org.springframework.stereotype.Repository;@Repository
public interface UserMapper extends BaseMapper {
}
package com.shiro.demo.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.shiro.demo.entity.User;
import com.shiro.demo.mapper.UserMapper;
import com.shiro.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User getUserByUsername(String username) {QueryWrapper queryWrapper = new QueryWrapper<>();queryWrapper.eq("username", username);return userMapper.selectOne(queryWrapper);}
}
自定义Realm
package com.shiro.demo.realm;import com.shiro.demo.entity.User;
import com.shiro.demo.service.UserService;
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class MyRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;/*** 自定义授权方法*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}/*** 自定义登录认证方法*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {String username = authenticationToken.getPrincipal().toString();User user = userService.getUserByUsername(username);if (user != null) {return new SimpleAuthenticationInfo(username, user.getPassword(), ByteSource.Util.bytes("salt"), username);}return null;}
}
编写配置类
package com.shiro.demo.config;import com.shiro.demo.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;/*** 配置DefaultWebSecurityManager*/@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager() {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("MD5");hashedCredentialsMatcher.setHashIterations(3);myRealm.setCredentialsMatcher(hashedCredentialsMatcher);defaultWebSecurityManager.setRealm(myRealm);return defaultWebSecurityManager;}/*** 配置DefaultShiroFilterChainDefinition*/@Beanpublic DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition() {DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();defaultShiroFilterChainDefinition.addPathDefinition("/login", "anon");defaultShiroFilterChainDefinition.addPathDefinition("/**", "authc");return defaultShiroFilterChainDefinition;}
}
这里扩展一下:Shiro支持的Filter有哪些,通过官方文档可以看到。
编写Controller
package com.shiro.demo.controller;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
@RequestMapping("/login")
public class LoginController {@GetMapping("/login")@ResponseBodypublic String login(String username, String password) {Subject subject = SecurityUtils.getSubject();AuthenticationToken authenticationToken = new UsernamePasswordToken(username, password);try {subject.login(authenticationToken);return "登录成功";} catch (AuthenticationException e) {e.printStackTrace();return "登录失败";}}
}
启动服务,测试代码,首先把数据库里的密码改成md5加盐加密3次后的结果,浏览器访问:http://localhost:8080/login/login?username=username1&password=password查看结果,可以看到“登录成功”,表示验证通过了。
下面我们再来添加前端页面,这里使用Thymeleaf,所以要有Thymeleaf的相关依赖。
在resources目录下添加templates文件夹,添加login.html页面和main.html页面。
登录
Shiro 登录认证
Title
Shiro 登录认证后主页面
登录用户为:
修改Controller方法。
package com.shiro.demo.controller;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpSession;@Controller
@RequestMapping("/login")
public class LoginController {@GetMapping("/toLogin")public String login() {return "login";}@GetMapping("/login")public String login(String username, String password, HttpSession httpSession) {Subject subject = SecurityUtils.getSubject();AuthenticationToken authenticationToken = new UsernamePasswordToken(username, password);try {subject.login(authenticationToken);httpSession.setAttribute("user", authenticationToken.getPrincipal().toString());return "main";} catch (AuthenticationException e) {e.printStackTrace();return "login";}}
}
同时,还要修改defaultShiroFilterChainDefinition
里的策略。
@Bean
public DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition() {DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();defaultShiroFilterChainDefinition.addPathDefinition("/login/**", "anon");defaultShiroFilterChainDefinition.addPathDefinition("/**", "authc");return defaultShiroFilterChainDefinition;
启动项目,浏览器访问http://localhost:8080/login/toLogin就来到了登录页面,输入正确的用户名密码,会跳转到main页面,并且在main页面上,可以看到当前登录的用户名信息。
当应用程序配置多个Realm时,例如:用户名密码校验、手机号验证码校验等等。
Shiro的ModularRealmAuthenticator会使用内部的AuthenticationStrategy组件判断认证是成功还是失败。
AuthenticationStrategy是一个无状态的组件,它在身份验证尝试中被询问4次(这4次交互所需的任何必要的状态将被作为方法参数):
(1)在所有Realm被调用之前
(2)在调用Realm的getAuthenticationInfo
方法之前
(3)在调用Realm的getAuthenticationInfo
方法之后
(4)在所有Realm被调用之后
认证策略的另外一项工作就是聚合所有Realm的结果信息封装至一个AuthenticationInfo实例中,并将此信息返回,以此作为Subject的身份信息。
Shiro中定义了 3 种认证策略的实现:
AuthenticationStrategy | 描述 |
---|---|
AtLeastOneSuccessfulStrategy | 只要有一个(或更多)的Realm验证成功,那么认证将视为成功 |
FirstSuccessfulStrategy | 第一个Realm验证成功,整体认证将视为成功,且后续Realm将被忽略 |
AllSuccessfulStrategy | 所有Realm成功,认证才视为成功 |
ModularRealmAuthenticator内置的认证策略默认实现是AtLeastOneSuccessfulStrategy方式。可以通过配置修改策略。
首先需要定义多个Realm,然后创建DefaultWebSecurityManager的实例,指定AuthenticationStrategy,并设置多个Realm。
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);List realmList = new ArrayList<>();realmList.add(myRealm1);realmList.add(myRealm2);defaultWebSecurityManager.setRealms(realmList);return defaultWebSecurityManager;
}
Shiro提供了记住我(RememberMe)的功能,比如访问一些网站时,关闭了浏览器,下次再打开时还是能记住你是谁,下次访问时无需再登录即可访问。
修改配置类,增加RememberMe的设置。
package com.shiro.demo.config;import com.shiro.demo.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;/*** 配置DefaultWebSecurityManager*/@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager() {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("MD5");hashedCredentialsMatcher.setHashIterations(3);myRealm.setCredentialsMatcher(hashedCredentialsMatcher);defaultWebSecurityManager.setRealm(myRealm);defaultWebSecurityManager.setRememberMeManager(rememberMeManager());return defaultWebSecurityManager;}public RememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(simpleCookie());cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());return cookieRememberMeManager;}public SimpleCookie simpleCookie() {SimpleCookie simpleCookie = new SimpleCookie("rememberMe");// cookie.setDomain(domain);simpleCookie.setPath("/");simpleCookie.setHttpOnly(true);simpleCookie.setMaxAge(30 * 24 * 60 * 60);return simpleCookie;}/*** 配置DefaultShiroFilterChainDefinition*/@Beanpublic DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition() {DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();defaultShiroFilterChainDefinition.addPathDefinition("/login/toLogin", "anon");defaultShiroFilterChainDefinition.addPathDefinition("/login/login", "anon");defaultShiroFilterChainDefinition.addPathDefinition("/**", "authc");defaultShiroFilterChainDefinition.addPathDefinition("/**", "user");return defaultShiroFilterChainDefinition;}// @Bean
// public DefaultWebSecurityManager defaultWebSecurityManager() {
// DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
// modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
// defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);
// List realmList = new ArrayList<>();
// realmList.add(myRealm1);
// realmList.add(myRealm2);
// defaultWebSecurityManager.setRealms(realmList);
// return defaultWebSecurityManager;
// }
}
修改登录方法,传递rememberMe参数。
package com.shiro.demo.controller;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;import javax.servlet.http.HttpSession;@Controller
@RequestMapping("/login")
public class LoginController {@GetMapping("/toLogin")public String login() {return "login";}@GetMapping("/login")public String login(String username, String password, @RequestParam(defaultValue = "false") boolean rememberMe, HttpSession httpSession) {Subject subject = SecurityUtils.getSubject();AuthenticationToken authenticationToken = new UsernamePasswordToken(username, password, rememberMe);try {subject.login(authenticationToken);httpSession.setAttribute("user", authenticationToken.getPrincipal().toString());return "main";} catch (AuthenticationException e) {e.printStackTrace();return "login";}}@GetMapping("/loginRememberMe")public String loginRememberMe(HttpSession httpSession) {httpSession.setAttribute("user", "rememberMe");return "main";}
}
修改login.html页面,添加rememberMe的checkbox。
登录
Shiro 登录认证
修改application.yml里的shiro.loginUrl为:/login/toLogin。
启动项目进行测试,访问:http://localhost:8080/login/loginRemeberMe,页面会自动跳转到登录页:/login/toLogin。
然后,我们输入账户密码,并勾选“记住用户”的checkbox选项框,点击登录,然后在新标签页打开/login/loginRememberMe请求,可以看到页面也跳到了main.html里。
关掉浏览器,再次访问http://localhost:8080/login/loginRememberMe,可以发现,并没有进到登录页,而是直接进到了main.html,说明rememberMe效果实现了。
观察两次登录(勾选和不勾选RememberMe)请求里cookie的值,可以发现,不勾选的时候,rememberMe的cookie值是deleteMe,勾选的时候,rememberMe的cookie值是一个加密字符串,这就是区别,Shiro通过这个加密字符串可以解析出来用户信息,就没有跳转到登录页面。
用户登录后,配套的有登出操作。直接通过Shiro过滤器即可实现登出。
修改mian.html
Title
Shiro 登录认证后主页面
登录用户为:
登出
修改配置类,添加logout过滤器。
defaultShiroFilterChainDefinition.addPathDefinition("/logout", "logout");
登录后,点击”登出“超链接,就回到了登录页面,观察/logout请求里cookie的rememberMe值,可以发现是deleteMe,也就是把rememberMe的信息给清除掉。
用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判断。
这个工具就是Realm的doGetAuthorizationInfo
方法进行判断。触发权限判断的有两种方式:
通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加在业务方法上,一般加在控制器方法上。常用注解如下:
subject.isAuthenticated()
subject.isAuthenticated() && subject.isRemembered()
subject.getPrinciple()
的值是nullAuthorizationException
异常,如果有,执行方法AuthorizationException
异常,如果有,执行方法添加一个角色验证的方法。
package com.shiro.demo.controller;import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
@RequestMapping("/index")
public class IndexController {@GetMapping("/home")@ResponseBodypublic String home() {return "hello world";}@RequiresRoles("role1")@GetMapping("/testRoles")@ResponseBodypublic String testRoles() {return "角色验证通过";}
}
修改main.html,添加一个超链接,用于触发刚刚的请求。
Title
Shiro 登录认证后主页面
登录用户为:
登出
测试角色
修改MyRealm,重写授权方法。
/*** 自定义授权方法*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("进入自定义授权方法");return null;
}
重启服务,点击超链接进行访问,此时会报错,控制台可以看到,已经走了自定义的授权方法,下面我们就需要再自定义授权方法里写一些代码,实现角色判断和权限判断。
先尝试写死,手动给用户提供一个角色,正常情况下,这个角色是从数据库里获取的,再次通过浏览器访问,查看效果,此时可以看到角色验证通过了。
/*** 自定义授权方法*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();simpleAuthorizationInfo.addRole("role1");return simpleAuthorizationInfo;
}
下面把这块改成活的,也就是从数据库获取角色,相关SQL参考这里。
编写mapper方法,service实现,修改MyRealm。
@Select("SELECT role_name FROM user_role WHERE username=#{username}")
List getRoleListByUsername(String username);@Override
public List getRoleListByUsername(String username) {return userMapper.getRoleListByUsername(username);
}/*** 自定义授权方法*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();List roleList = userService.getRoleListByUsername(principalCollection.getPrimaryPrincipal().toString());simpleAuthorizationInfo.addRoles(roleList);return simpleAuthorizationInfo;
}
进行测试,此时数据就是活的啦。
权限验证同理,相关SQL参考这里。
编写mapper方法,service实现,修改MyRealm。
@Select("SELECT permission FROM role_permission WHERE role_name IN (SELECT role_name FROM user_role WHERE username=#{username});")
List getPermissionListByUsername(String username);@Override
public List getPermissionListByUsername(String username) {return userMapper.getPermissionListByUsername(username);
}@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();List roleList = userService.getRoleListByUsername(principalCollection.getPrimaryPrincipal().toString());simpleAuthorizationInfo.addRoles(roleList);List permissionList = userService.getPermissionListByUsername(principalCollection.getPrimaryPrincipal().toString());simpleAuthorizationInfo.addStringPermissions(permissionList);return simpleAuthorizationInfo;
}
修改controller方法,添加权限验证注解。
@RequiresPermissions("video:get")
@GetMapping("/testPermissions")
@ResponseBody
public String testPermissions() {return "权限验证通过";
}
修改main.html,添加请求入口。
测试权限
通过浏览器访问进行测试。
对于没有角色,没有权限这样的情况,会报错误页面,这样不够友好,我们可以创建异常处理类,将错误信息提示出来。
创建GlobalException.java。
package com.shiro.demo.exception;import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
public class GlobalException {@ResponseBody@ExceptionHandler(UnauthorizedException.class)public String unauthorizedException(Exception e) {return "无权限";}@ResponseBody@ExceptionHandler(AuthorizationException.class)public String authorizationException(Exception e) {return "权限认证失败";}
}
要想让Thymeleaf支持Shiro语法,需要添加依赖。
com.github.theborakompanioni thymeleaf-extras-shiro 2.0.0
添加配置类用于解析shiro属性。
/*** 配置ShiroDialect解析Shiro相关属性*/
@Bean
public ShiroDialect shiroDialect() {return new ShiroDialect();
}
shiro属性,下面写成了标签的样子,实际是shiro属性。
用户没有身份验证时显示相应信息,即游客访问信息
用户已经身份验证/记住我登录后显示相应的信息
用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的
用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证
相当于((User)Subject.getPrincipals()).getUsername()
如果当前Subject没有权限将显示body体内容
如果当前Subject有角色将显示body体内容
如果当前Subject有任意一个角色(或的关系)将显示body体内容
如果当前Subject没有角色将显示body体内容
如果当前Subject有权限将显示body体内容
修改main.html,可以实现将无权限的标签进行隐藏。
测试角色
测试权限
EhCache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。可以和大部分Java项目无缝整合,例如:Hibernate中的缓存就是基于EhCache实现的。
EhCache支持内存和磁盘存储,默认存储在内存中,如内存不够时把缓存数据同步到磁盘中。EhCache支持基于Filter的Cache实现,也支持Gzip压缩算法。
EhCache直接在JVM虚拟机中缓存,速度快,效率高,EhCache缺点是缓存共享麻烦,集群分布式应用使用不方便。
给项目加入Ehcache,添加pom依赖。
net.sf.ehcache ehcache 2.6.11 pom
在resources下添加ehcache.xml配置文件。
创建测试类进行测试。
package com.shiro.demo.test;import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;import java.io.InputStream;public class Test {public static void main(String[] args) {InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("classpath:ehchace.xml");CacheManager cacheManager = new CacheManager(inputStream);Cache cache = cacheManager.getCache("HelloWorldCache");Element element = new Element("name", "value");cache.put(element);Element cacheElement = cache.get("name");System.out.println(cacheElement.getObjectValue());}
}
Shiro官方提供了shiro-ehcache,实现了整合EhCache作为Shiro的缓存工具。可以缓存认证执行的Realm方法,减少对数据库的访问,提高认证效率。
添加pom依赖。
org.apache.shiro shiro-ehcache 1.4.2
commons-io commons-io 2.6
在resources下添加配置文件ehcache/ehcache-shiro.xml。
修改ShiroConfig类。
/*** 配置DefaultWebSecurityManager*/
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("MD5");hashedCredentialsMatcher.setHashIterations(3);myRealm.setCredentialsMatcher(hashedCredentialsMatcher);defaultWebSecurityManager.setRealm(myRealm);defaultWebSecurityManager.setRememberMeManager(rememberMeManager());defaultWebSecurityManager.setCacheManager(getEhCacheManager());// 设置缓存管理器return defaultWebSecurityManager;
}
private EhCacheManager getEhCacheManager() {EhCacheManager ehCacheManager = new EhCacheManager();try(InputStream inputStream = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml")) {CacheManager cacheManager = new CacheManager(inputStream);ehCacheManager.setCacheManager(cacheManager);} catch (IOException e) {e.printStackTrace();}return ehCacheManager;
}
启动项目进行测试,查看日志,执行了3条SQL。
SELECT id,username,password,password_salt FROM user WHERE username=?
SELECT role_name FROM user_role WHERE username=?
SELECT permission FROM role_permission WHERE role_name IN (SELECT role_name FROM user_role WHERE username=?)
再次点击main.html页面的权限验证,角色验证,控制台并没有打印内容,说明此时的信息是从cache里获取的,并没有查询数据库。
会话管理器,负责创建和管理用户的会话(Session)生命周期,它能够在任何环境中在本地管理用户会话,即使没有Web/Servlet/EJB容器,也一样可以保存会话。默认情况下,Shiro会检测当前环境中现有的会话机制(比如Servlet容器)进行适配,如果没有(比如独立应用程序或者非Web环境),它将会使用内置的企业会话管理器来提供相应的会话管理服务,其中还涉及一个名为SessionDAO的对象。SessionDAO负责Session的持久化操作(CRUD),允许Session数据写入到后端持久化数据库。
SessionManager由SecurityManager管理。Shiro提供了三种实现。
顺便说下这个图怎么生成的,先找到SessionManager接口,右键选择Diagrams-Show Diagram…,选择Java class diagram。
右键SessionManager,选择Show Implementations,按住Ctrl+鼠标左键,勾选需要的类,最后按下回车。
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("key", "value");
Object value = session.getAttribute("key");
Controller中的request,在Shiro过滤器的doFilterInternal()
方法,被包装成ShiroHttpServletRequest
,SecurityManager和 SessionManager会话管理器决定session来源于ServletRequest还是由Shiro管理的会话。无论是通过request.getSession
或 subject.getSession
获取到session,操作session,两者都是等价的。