共计 11004 个字符,预计需要花费 28 分钟才能阅读完成。
文 | 平哥 日期 | 20200927
用于记录本人 Spring Boot 整合 Shiro 过程。
官网有篇教程能够参考:Integrating Apache Shiro into Spring-Boot Applications
根本环境和工具
IDE: IntelliJ IDEA
Maven: 3.6.0
JDK: 1.8
Step 1 搭建根底 SSM 环境
1.1 创立 Maven 工程,增加 SSM+Thymeleaf+Shiro 依赖
Step1 创立 Maven 工程:
省略用 IDEA 增加 Maven 工厂我的项目步骤,这个默认大家都懂……
提醒:新建我的项目后记得配置 IDEA 的 Maven 参数,改为本地本人装置的 Maven
Step2 增加 Spring Boot 父依赖、shiro 依赖:
<!-- 增加 Spring Boot 父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<!--shiro 的 Spring Boot 启动器 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.6.0</version>
</dependency>
<!--thymeleaf 的启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--mybatis 的启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- 数据库的驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
</dependencies>
1.2 配置 application.yml 中数据库信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
1.3 编写 MVC 各层代码
编写根本 MVC 各层包和目录以及相干代码:
1.4 编写 Thymeleaf 页面
Spring Boot 整合 Thymeleaf,无需进行任何配置,只需在 resources 文件夹下创立templates 文件夹,在其中创立 html 文件,SpringMVC 即可主动进行跳转:
JS、css、图片等动态资源须放在 static 文件夹 下
对于公共页面的跳转能够利用门路变量创立一个专用的单元办法:
// 公共页面跳转共用单元办法
@RequestMapping("/{path}")
public String getPage(@PathVariable String path){return path;}
Step 2 整合 Shiro,实现登录认证
留神:相干依赖已在第一步导入,不在赘述。
2.1 配置 application.yml
在 Spring Boot 配置文件 application.yml 中配置 Shiro 的默认登录链接:
shiro:
loginUrl: /login
2.2 编写自定义 Realm 类和 Shiro 配置类
Step1 创立 com.gcp.shiro 包
Step2 在其中创立 MyRealm 类,继承 AuthorizingRealm 类,重写认证办法:
/* 重写认证办法 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("认证用户中……");
// 从 token 获取用户登录输出的用户名
String unameFromWeb = token.getPrincipal().toString();
// 利用用户名去数据库查问是否有数据
User user = userService.selectUserByUname(unameFromWeb);
if (user!=null) {AuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(), user.getPwd(),
ByteSource.Util.bytes("gcp"), token.getPrincipal().toString());
return info;
}
return null;
}
Step3 创立 ShiroConfig 类,配置 SecurityManager bean 和 Shiro 内置过滤器 bean:
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
// 配置 Security Manager
@Bean
public DefaultWebSecurityManager getSecurityManager(){
// 实例化 SecurityManager
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 实例化 Shiro 默认的明码匹配器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 设置明码加密算法为 md5
matcher.setHashAlgorithmName("md5");
// 设置迭代次数
matcher.setHashIterations(2);
// 将明码匹配器退出自定义 realm 中:myRealm.setCredentialsMatcher(matcher);
// 将自定义的 realm 集成到 DefaultWebSecurityManager 对象中
securityManager.setRealm(myRealm);
return securityManager;
}
// 配置 Shiro 默认过滤器
@Bean
public ShiroFilterChainDefinition getFilter(){DefaultShiroFilterChainDefinition filterChainDefinition = new DefaultShiroFilterChainDefinition();
// 放行公共的页面和动态资源的拜访
filterChainDefinition.addPathDefinition("/login","anon");
filterChainDefinition.addPathDefinition("/css/**","anon");
filterChainDefinition.addPathDefinition("/js/**","anon");
filterChainDefinition.addPathDefinition("/images/**","anon");
filterChainDefinition.addPathDefinition("/themes/**","anon");
filterChainDefinition.addPathDefinition("/userLogin","anon");
// 其余必须登录能力拜访
filterChainDefinition.addPathDefinition("/**","user");
return filterChainDefinition;
}
}
2.3 编写用户登录验证单元办法
在登录的 PulicController 类中:
/**
* 用户登录办法
* @param uname
* @param pwd
* @return
*/
@RequestMapping("userLogin")
@ResponseBody
public Result userLogin(String uname, String pwd){
// 利用用户名明码实例化 Shiro token
UsernamePasswordToken tonken = new UsernamePasswordToken(uname, pwd);
try {
// 获取 Subject 并进行登录
SecurityUtils.getSubject().login(tonken);
return new Result();}catch (AuthenticationException e) {e.printStackTrace();
return new Result("用户名或明码不匹配");
}
}
此处 login 办法会调用 MyRealm 的认证办法进行匹配。此时我的项目即可实现用户登录性能了。
2.4 用 Shiro 实现 remember me
Shiro 实现记住我性能非常简略:在 Shiro 的配置文件进行如下批改:
Step1 在设置 SecurityManager 办法中批改:(增加设置 Shiro 的 remember me 性能)
// 配置 Security Manager
@Bean
public DefaultWebSecurityManager getSecurityManager(){
// 实例化 SecurityManager
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 实例化 Shiro 默认的明码匹配器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 设置明码加密算法为 md5
matcher.setHashAlgorithmName("md5");
// 设置迭代次数
matcher.setHashIterations(2);
// 将明码匹配器退出自定义 realm 中:myRealm.setCredentialsMatcher(matcher);
// 将自定义的 realm 集成到 DefaultWebSecurityManager 对象中
securityManager.setRealm(myRealm);
// 设置 Shiro 的 remember me 性能
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
rememberMeManager()办法代码:
// 设置 shiro 的 remembermeManager
private RememberMeManager rememberMeManager() {
// 实例化 shiro 的 remembermeManager
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
// 设置 cookie 的有效期
cookieRememberMeManager.setCookie(rememberMeCookie());
// 增加 Cookie 加密设置
cookieRememberMeManager.setCipherKey("123123123".getBytes());
return cookieRememberMeManager;
}
rememberMeCookie()办法代码:
// 设置 Cookie 参数
private SimpleCookie rememberMeCookie() {SimpleCookie cookie = new SimpleCookie();
cookie.setPath("/");
cookie.setHttpOnly(true);
// 单位是秒
cookie.setMaxAge(3*24*60*60);
return cookie;
}
Step2 设置好后,再次批改用户登录的单元办法:增加一个 boolean 类型的 rememberme 参数,并且设置默认值为 false:
/**
* 用户登录办法
*/
@RequestMapping("userLogin")
@ResponseBody
public Result userLogin(String uname, String pwd,@RequestParam(defaultValue = "false") Boolean rememberme){
// 利用用户名明码实例化 Shiro token
UsernamePasswordToken tonken = new UsernamePasswordToken(uname, pwd,rememberme);
// 省略余下代码,余下代码没变动,相见上部
}
Step 3 Shiro 实现后盾性能及页面显示受权
鉴权 就是判断用户是否有权限执行相应办法或看到页面具体内容
受权 就是授予认证用户指定的角色或指定的权限。
3.1 后盾性能办法鉴权
Shiro 在后盾能够用在控制器办法,也能够用在业务办法。通常都在控制器办法上增加注解进行鉴权。
本我的项目中具体用户的增删改查四个办法,利用注解 @RequirePermissions("要求的权限")
进行别离鉴权,具体代码如下:
新建 UserController 类:
@Controller
public class UserController {
// 申明单元办法: 用户新增
@RequiresPermissions("user:add")
@RequestMapping("userAdd")
@ResponseBody
public String userAdd(){System.out.println("新增用户单元办法执行。");
return "祝贺,新增用户胜利!";
}
// 申明单元办法: 用户删除
@RequiresPermissions("user:del")
@RequestMapping("userDel")
@ResponseBody
public String userDel(){System.out.println("用户信息删除单元办法执行");
return "祝贺,用户删除胜利!";
}
// 申明单元办法: 用户批改
@RequiresPermissions("user:edit")
@RequestMapping("userEdit")
@ResponseBody
public String userEdit(){System.out.println("用户信息批改单元办法执行");
return "祝贺,用户批改胜利!";
}
}
3.2 Thymeleaf 页面中鉴权
在须要鉴权的页面中,在 <html>
标签中增加属性:
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
在具体的须要进行鉴权的页面元素中应用 shiro 标识,例如:
<ul >
<li><a href="/userAdd" shiro:hasPermission="user:add"> 增加用户 </a></li>
<li><a href="/userDel" shiro:hasPermission="user:del"> 删除用户 </a></li>
<li><a href="/userEdit" shiro:hasPermission="user:edit"> 批改用户 </a></li>
<li><a href="/userSel" shiro:hasPermission="user:sel"> 查问用户 </a></li>
</ul>
还须要再增加 Thymeleaf 整合 Shiro 的依赖:
<!-- 配置 Thymeleaf 整合 shiro-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
在 shiro 配置类中增加 shiro 标识解析 bean:
/**
* 配置页面的 shiro 标识的解析 bean
*/
@Bean
public ShiroDialect shiroDialect() {return new ShiroDialect();
}
3.3 Shiro 受权
首先,重写 MyRealm 中的受权办法:
/* 重写受权办法 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取以后认证用户的用户名
String uname = (String)principalCollection.getPrimaryPrincipal();
// 从数据库查问以后用户的权限信息
List<String> permissionList = userService.getPermissions(uname);
// 从数据库查问以后用户的角色信息
List<String> roleList = userService.getRoles(uname);
// 将查问到的权限、角色信息给 Shiro
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissionList);
info.addRoles(roleList);
return info;
}
而后,增加相应的两个查询方法,此处代码省略
最初增加鉴权失败后,进行异样拦挡的告诉类和办法:
新建 ExpController 类:
@ControllerAdvice
public class ExpController {
@ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public String handleShiroException(Exception ex) {return "无权限";}
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public String AuthorizationException(Exception ex) {return "权限认证失败";}
}
Step 4 Shiro 整合 EhCache
在受权过程中,咱们会发现,Shiro 每次都会去拜访数据库,较为消耗资源,引入缓存即可解决问题,Shiro 反对很多第三方缓存工具。官网提供了 shiro-ehcache,实现了把 EHCache 当 做 Shiro 的缓存工具的解决方案。其中最好用的一个性能是就是缓存认证执行的 Realm 方 法,缩小对数据库的拜访。
4.1 增加依赖
<!--shiro 整合 EhCache 依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
4.2 编写 ehcache 缓存配置
在 resources 下新建 ehcache/ehcache-shiro.xml:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="ehcache" updateCheck="false">
<!-- 磁盘缓存地位 -->
<diskStore path="java.io.tmpdir"/>
<!-- 默认缓存策略 -->
<!--timeToIdleSeconds:缓存钝化工夫 -->
<!--timeToLiveSeconds:缓存无效工夫 -->
<defaultCache
maxEntriesLocalHeap="1000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="false">
</defaultCache>
</ehcache>
4.3 批改配置文件 shiroconfig
在 ShiroConfig 类的 setSecurityManager 办法中退出如下代码:
getCacheManager()办法具体代码:
/**
* 设置 shiro 的 CacheManager
*/
private CacheManager getCacheManager() {
// 1. 实例化 Shiro 本身的 CacheManager,EhCache 的实现类
EhCacheManager shiroCacheManager = new EhCacheManager();
// 2. 获取 EhCache 的配置类文件并转成输出流
InputStream is = null;
try {is = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml");
} catch (IOException e) {e.printStackTrace();
}
// 3. 实例化 EhCacheManager 本身对象
net.sf.ehcache.CacheManager ehCacheManager =new net.sf.ehcache.CacheManager(is);
// 4. 将 EhCacheManager 本身对象赋值给 Shiro 的 CacheManager
shiroCacheManager.setCacheManager(ehCacheManager);
// 5. 返回
return shiroCacheManager;
}
Step 5 实现屡次输错明码锁定账号
5.1 在 ehcache-shiro.xml 中配置缓存策略
<!-- 登录记录缓存策略:锁定 10 分钟 -->
<cache name="loginRecordCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
5.2 创立自定义凭证匹配器
创立凭证匹配器 RetryLimitHashedCredentialsMatcher 继承 HashedCredentialsMatcher:
@Component
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
// 申明缓存对象
private Ehcache passwordRetryCache;
// 获取 EhCache 缓存管理器并获取缓存策略
public RetryLimitHashedCredentialsMatcher(EhCacheManager ehCacheManager) {this.passwordRetryCache = ehCacheManager.getCacheManager().getCache("loginRecordCache");
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
// 申明变量记录谬误次数
int i = 0;
// 1. 获取用户登录次数的缓存信息
// 获取用户的身份信息(身份信息为缓存数据的键名)
String username = token.getPrincipal().toString();
// 获取缓存对象
Element element = passwordRetryCache.get(username);
// 判断是否有缓存数据
if (element==null) {
// 没有缓存数据,新建
Element ele = new Element(username,new AtomicInteger(0));
passwordRetryCache.put(ele);
} else {
// 有缓存,提取自增
AtomicInteger atomicInteger = (AtomicInteger) element.getObjectValue();
i = atomicInteger.incrementAndGet();}
System.out.println("验证次数:"+i);
// 3. 判断 i 次数
if (i >= 4) {throw new ExcessiveAttemptsException();
}
// 4. 进行本次登录判断
boolean match = super.doCredentialsMatch(token,info);
// 5. 如果登录胜利,则移除登录记录
if (match) {passwordRetryCache.remove(username);
}
return match;
}
}
5.3 批改配置类 ShiroConfig
- 将 EhCacheManager 的实例化交给 Spring 容器治理
在获取 EhCacheManager 的 getCacheManager 办法前增加 @Bean 注解:
把此对象的实例化交给 Spring 容器托管,以便 RetryLimitHashedCredentialsMatcher 结构器应用
- 批改明码匹配器:
应用在 ShiroConfig 的设置 SecurityManager 办法中把原来默认的明码匹配器替换为新自定义的类:
5.4 批改登录单元办法
至此,即实现性能开发。
具体代码请详见集体 gitee 仓库:https://gitee.com/chenpingclo…