不知不觉咱们的SpringBoot已经更新到第十期了,结合之前的Shiro项目,再加上上一节学习的SpringBoot整合数据库的操作,咱们将这些知识点,再 “串一串” ,来个大串烧。
友情提示:如果没有阅览过下面两篇文章的朋友,建议先提前瞅瞅,否则很有可能跟本节的内容串接不起来哦!当然有这方面知识储备或者看过下面文章的朋友请接着往下看啦~
Marco’s Java【Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇(上)】
Marco’s Java【Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇(中)】
Marco’s Java【Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇(下)】
Marco’s Java【SpringBoot进阶(三) 之 SpringBoot数据源配置和自动管理】
Marco’s Java【SpringBoot入门(五) 之 Thymeleaf模板引擎的使用】
Marco’s Java【SpringBoot进阶(四) 之 启用Druid数据源分别集成JdbcTemplate及Mybatis】
Marco’s Java【小工具篇 之 lombok的使用】
第一步:创建Springboot项目
首先在Available中依次搜索下方我们勾选的内容,如Lombok,Thymeleaf等,导入之后就不需要再单独在pom.xml中配置它们的starter启动装置了。
第二步:添加项目依赖jar包
除了上面我们在项目初创的时候引入的一些statrer,还需要手动的引入下方的依赖配置,因为SpringBoot中默认没有提供这些启动装置。
<!-- 引入druid的依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.18</version>
</dependency>
<!-- pagehelper-spring-boot-starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
<!-- 引入shiro依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!--shrio和thymeleaf集成的扩展依赖,为了能在页面上使用xsln:shrio的标签 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
第三步:配置数据源并集成Mybatis配置
接着咱们再配置数据源,数据源的配置在博文 Marco’s Java【SpringBoot进阶(四) 之 启用Druid数据源分别集成JdbcTemplate及Mybatis】 中已经讲到过,在这里就不过多赘述了。
spring:
datasource:
druid:
url: jdbc:mysql://127.0.0.1:3306/shiro?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: password
max-active: 20
initial-size: 5
max-wait: 5000
validation-query: select 'x' #配合定期检查的sql,每隔一段时间检查sql是否正常
keep-alive: true #定期检查开启
enable: true #启用,默认为false
## 监控页面的配置
filters: stat
stat-view-servlet:
login-username: root
login-password: password
allow:
deny:
enabled: true #启用,默认为false
url-pattern: /druid/*
web-stat-filter:
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
enabled: true #启用,默认为false
profile-enable: true
session-stat-enable: true
principal-session-name: USER_SESSION
principal-cookie-name: USER_COOKIE
url-pattern: /druid/*
#集成mybatis
mybatis:
#注入log日志配置,打印sql
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#注入xml的配置
mapper-locations:
- 'classpath:mapper/*Mapper.xml'
第四步:创建项目下所有的包及类
这里的类全部搬迁自Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇中,因此没有变化的代码我就不重复发啦,大家可以去之前的博文中拷贝。
第五步:创建Shrio的自动配置类ShiroAutoConfig
接下来的这一步才是我们的重中之重,虽然Shiro帮我们提供了启动装置,但是依然需要我们自行创建配置类,并注入相应的配置值,以下的操作就是将我们之前创建的application-shiro.xml、springmvc.xml以及web.xml中的关于shiro的配置复刻过来(shiro的xml配置请参考之前的博文),唯一多出来的配置就是ShiroDialect shiroDialect()
这一部分,主要是为了能在html页面引用shiro标签,具体是啥标签咱们后面会给大家演示。
package com.marco.config;
import java.util.HashMap;
import java.util.Map;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.filter.DelegatingFilterProxy;
import com.marco.realms.UserRealm;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import lombok.Data;
/**
* shiro自动配置
* @author Marco
*
*/
@Data
@Configuration
@ConfigurationProperties(value="shiro")
public class ShiroAutoConfig {
/**
* 定义的所有Bean的名称,并声明为常量
*/
private static final String SHIRO_DIALECT = "shiroDialect";
private static final String SHIRO_FILTER = "shiroFilter";
private static final String SECURITY_MANAGER = "securityManager";
private static final String USER_REALM = "userRealm";
private static final String CREDENTIALS_MATCHER = "credentialsMatcher";
/**
* 以下所有的成员属性值全部从application.yml文件中获取,配置头为"shiro"
*/
private String hashAlgorithmName;
private int hashIterations;
private String loginUrl;
private String unauthorizedUrl;
private String[] anonUrl;
private String[] authcUrl;
private String logoutUrl;
/**
* 创建凭证匹配器
* @return
*/
@Bean(CREDENTIALS_MATCHER)
public HashedCredentialsMatcher getHashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName(hashAlgorithmName);//注入算法
credentialsMatcher.setHashIterations(hashIterations);//注入散列次数
return credentialsMatcher;
}
/**
* 创建自定义领域对象
* @param credentialsMatcher :需要依赖credentialsMatcher
* @return
*/
@Bean(USER_REALM)
public UserRealm getUserRealm(HashedCredentialsMatcher credentialsMatcher) {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(credentialsMatcher);//注入credentialsMatcher
return userRealm;
}
/**
* 创建安全管理器
* @param userRealm
* @return
*/
@Bean(SECURITY_MANAGER)
public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);//注入自定义Realm
return securityManager;
}
/**
* 创建Shiro的过滤器链
* @param securityManager
* @return
*/
@Bean(SHIRO_FILTER)
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//创建shiro的过滤器工厂
shiroFilterFactoryBean.setSecurityManager(securityManager);//注入securityManager
shiroFilterFactoryBean.setLoginUrl(loginUrl);//设置用户登录的url地址
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);//设置认证失败的跳转地址
Map<String, String> filterChain = new HashMap<>();
/*
* anon:表示可以匿名使用。
authc:表示需要认证(登录)才能使用,没有参数
roles:参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如/user/query/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
perms:参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/user/update/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
rest:根据请求的方法,相当于/user/query/**=perms[user:method] ,其中method为post,get,delete等。
port:当请求的url的端口不是8082时跳转到schemal://serverName:8081?requestPath,其中schmal是协议http或https等,serverName是你访问的host,8082是url配置里port的端口,requestPath就是你想访问的具体哪个资源路径
authcBasic:没有参数表示httpBasic认证
ssl:表示安全的url请求,协议为https
user:当登入操作时不做检查
*/
//注入匿名放行的路径
if(anonUrl != null && anonUrl.length > 0) {
for (String url : anonUrl) {
filterChain.put(url, "anon");
}
}
//注入拦截的路径
if(authcUrl != null && authcUrl.length > 0) {
for (String url : authcUrl) {
filterChain.put(url, "authc");
}
}
//注入退出的路径
filterChain.put(logoutUrl, "logout");
//将我们的过滤器链注册到SpringBoot的Filter中
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChain);
return shiroFilterFactoryBean;
}
/**
* 加入shiro注解的使用
* @param securityManager
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
return defaultAdvisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 注册shiro的委托过滤器,相当于之前在web.xml里面配置的部分
* @return
*/
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> getFilterRegistrationBean() {
FilterRegistrationBean<DelegatingFilterProxy> registrationBean = new FilterRegistrationBean<>();//创建过滤器注册器
DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();//创建过滤器代理类
delegatingFilterProxy.setTargetFilterLifecycle(true);//设置shiro在SpringBoot中的生命周期范围
delegatingFilterProxy.setTargetBeanName(SHIRO_FILTER);//注入shiro过滤器链
registrationBean.setFilter(delegatingFilterProxy);//注册过滤器
return registrationBean;
}
// 这里是为了能在html页面引用shiro标签,上面两个函数必须添加,不然会报错
@Bean(SHIRO_DIALECT)
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
getFilterRegistrationBean()
方法就是对照着我们之前在web.xml的如下配置,大家可以对比着看看。
关于shiro注解的配置的分析:
这里咱们再单独提一下关于shiro注解的配置,具体配置如下
然后咱们再对比一下之前的这一部分的配置,发现好像少了一部分?
这个配置好像没有用到?
这样,我们把LifecycleBeanPostProcessor的配置加上再试着运行看看会有什么效果
发现抛出Caused by: java.lang.NullPointerException: chainName cannot be null or empty
这么个异常,说chain(过滤器链名)不能为空?可是我们过滤器链不是已经命名为shiroFilter了么?为了解决咱们的困惑,我来debug给大家分析一下,究竟是怎么回事儿~
大家注意看,我们的程序都debug到return shiroFilterFactoryBean
这个部分了,但是ShiroFilterFactoryBean对象中的值一个都没有注入进来?这是什么原因呢?
其实说来说去就是我们配置的LifecycleBeanPostProcessor在搞鬼!
根本的原因是在org.apache.shiro.spring.config包中Spring默认的帮我们整合了LifecycleBeanPostProcessor,当SpringBoot启动的时候,会默认创建这么个类,并且这个类不应该配置在我们ShiroAutoConfig中,因为LifecycleBeanPostProcessor的作用更像是一个 “工具” ,而不是配置!由于IoC容器中已经创建了LifecycleBeanPostProcessor对象,当我们再次去创建一个新的对象时,会覆盖之前的对象,我们知道LifecycleBeanPostProcessor是掌管Bean的生命周期的,被你这么一覆盖,之前的配置是不是都会丢失了?
从源码上分析也能看出来我们创建新的LifecycleBeanPostProcessor之后,之前的LifecycleBeanPostProcessor走销毁流程,之前的数据也会随之清空,大家可以测试一下,当我们没有创建新的LifecycleBeanPostProcessor时,项目启动是不会走这一步的!
因此我们在配置的时候,是不用自己去创建LifecycleBeanPostProcessor对象的!
第六步:修改index页面
此次的index页面和之前在Marco’s Java【Shiro进阶(一) 之 Shiro+SMM集成Maven项目串烧篇(中)】 中的页面有所区别,因为我们这次使用的是thymeleaf模板引擎,是不是比之前简洁了很多?
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>系统首页title>
head>
<body>
系统首页
<hr>
<a href="/user/query" shiro:hasPermission="user:query">查询用户a>
<br>
<a href="/user/add" shiro:hasPermission="user:query">添加用户a>
<br>
<a href="/user/update" shiro:hasPermission="user:update">修改用户a>
<br>
<a href="/user/delete" shiro:hasPermission="user:delete">删除用户a>
<br>
<a href="/user/export" shiro:hasPermission="user:export">导出用户a>
<br>
body>
html>
第七步:修改yml添加shiro配置
接下来就是咱们shiro集成收尾的一步了,通过这种将配置类和具体的配置分离开的方式,可以更加灵活的对我们的权限管理进行操作。
shiro:
anon-url: #匿名访问路径
- /index.html
- /login/toLogin*
- /login/login*
logout-url: /login/logout #登出
login-url: /login/toLogin #登录
authc-url: #登录拦截路径
- /**
unauthorized-url: /unauthorized.html #未授权
hash-algorithm-name: md5 #算法名称
hash-iterations: 2 #散列次数
第七步:代码测试
咱们先登录 http://127.0.0.1:8080/ 测试看看。发现自动跳转到用户登录页面了,证明我们的登录拦截生效了!
账号密码测试都是正常的
然后输入正确的账号密码,就会进入到下方界面,因为我没有export权限(之前的博文有测试过),因此证明权限也没有问题!