SpringBoot+Shiro+Thymeleaf框架的整合与配置
前言
- 本次记录的是本人学习开发期间使用SpringBoot+Shiro+Thymeleaf框架的整合与配置过程。其目的主要为:一来是为以后的项目开发提供参考;二来记录自己的学习过程;三来希望本帖能起到沟通交流的作用,为自己的学习增加更多知识点。每个项目都会有独特与独到之处,因此希望本项目整合的分享不要影响到读者的创造力,仅供交流。
- 本项目所涉及到的技术有SSM框架、Maven构建工具、SpringBoot框架、Shiro安全性框架、Thymeleaf静态模板页、layui前端框架、Ajax、MySQL数据库、MD5加密、Json、EhCache等。
- 其中连接池使用的是 Druid ,Json数据使用fastjson,这两个都是阿里的产品,项目依赖包为本地仓库。
关键词:SpringBoot; SSM; Shiro; Maven;
目录:
Maven项目搭建
- Maven环境变量配置
- 本地仓库配置
项目分包
分包说明:
由于很多都是熟悉的分包规则,这里只描述有特点的几个包。
- java包:
(1). shiro:存放的是Shiro的配置类(ShiroFilterFactoryBean等)、继承AuthorizingRealm的用户认证与授权类 、继承于FormAuthenticationFilter的用户自定义拦截器类。
(2). utils:文件上传相关的配置等工具类。 - resource包:
(1). static:存放CSS、JS、Images等静态资源文件
(2). templates:.html视图文件
(3). **.mapper:对应源码包名下Mybatis的xml映射文件,这里需要注意的是,在IDEA中创建这个包时不能通过直接复制源码内的包名创建这个包。由于源码包的特殊性,在resource文件夹中通过直接复制源码包内的包名将只会创建一个文件夹,因此在编译后mapper的.xml文件不会与对应的接口类在同一个包下。所以,在resource文件夹下创建包应该一个一个层叠创建。
(4). application.yml:配置数据库连接、分页、Thymeleaf等配置,在本文后面一节中会提到。
(5).shiro-ehcache.xml:配置shiro缓存,同样在接下来会提到。
pom.xml文件配置SpringBoot+Thymeleaf整合
相关整合xml配置(含注释)如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.gec</groupId>
<artifactId>oa_sys</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!--父工程坐标-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--SpringMVC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- java ee -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</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>1.3.2</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--connect pool-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- file Upload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!--Json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<!-- 上传文件到FTP -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--Shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0-RC2</version>
</dependency>
<!--EhCache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
<!--thymeleaf+shiro页面标签库-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--Page Helper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
<exclusions>
<exclusion>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<!--Maven启动配置-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
项目配置
注意由于application.yml中涉及到多方的配置,在这里分开解说。
MySQL数据库连接配置
在resource包下的application.yml文件中配置
application.yml:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: 'jdbc:mysql://localhost:3306/oa_sys?useUnicode=true&characterEncoding=UTF-8'
username: ----add your MySQL username----
password: ----add your MySQL password----
在数据库连接Url后加上:useUnicode=true&characterEncoding=UTF-8使用UTF-8进行统一编码,防止乱码。
Shiro配置
- Shiro代码配置:
由于在SpringBoot中使用注解代码进行配置,因此在配置类上需要加上@Configuration注解,以“告诉”Spring容器这是一个配置类。同时与Xml文件配置类似的地方是,每一项配置都以Bean为基础单位,不同的是在xml文件中一项配置是一个Bean节点,而配置类中则是@Bean注解方法。
下面开始使用代码配置Shiro(含注释):
@Configuration
public class ShiroConfig {
@Bean
public AuthorizationAttributeSourceAdvisor advisor(){
AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager());
return advisor;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(){
ShiroFilterFactoryBean shiroFilter=new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager());
//设置登录Url
shiroFilter.setLoginUrl("/login");
//设置登录成功后的Url
shiroFilter.setSuccessUrl("/oa_sys/index");
//未授权跳转Url
shiroFilter.setUnauthorizedUrl("/oa_sys/userList");
//shiro过滤器
Map<String,String> filterChain=new LinkedHashMap<>();
filterChain.put("/layui/**","anon");
filterChain.put("/css/**","anon");
filterChain.put("/images/**","anon");
filterChain.put("/jquery-3.3.1/**","anon");
filterChain.put("/js/**","anon");
filterChain.put("/layui_menu/**","anon");
filterChain.put("/zTree/**","anon");
filterChain.put("/font-awesome-4.7.0","anon");
filterChain.put("/ckfinder/**","anon");
filterChain.put("/cron/**","anon");
filterChain.put("/templates/**","anon");
//rememberMe
filterChain.put("/", "user");
filterChain.put("/loginPage","anon");
filterChain.put("/login","anon");
filterChain.put("/logout","logout");
filterChain.put("/**","authc");
shiroFilter.setFilterChainDefinitionMap(filterChain);
return shiroFilter;
}
//配置SecurityManager
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
securityManager.setCacheManager(ehCacheManager());
securityManager.setRememberMeManager(cookieRememberMeManager());
return securityManager;
}
@Bean
public Realm myShiroRealm() {
//自定义Realm,认证及授权规则
CustomShiroRealm myShiroRealm = new CustomShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
//加密方式,Md5,加密2次
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher ();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
//使用EhCache缓存
@Bean
public EhCacheManager ehCacheManager() {
EhCacheManager cacheManager=new EhCacheManager();
//缓存配置文件所在位置
cacheManager.setCacheManagerConfigFile("classpath:shiro-ehcache.xml");
return cacheManager;
}
//配置RememberMe
@Bean
public CookieRememberMeManager cookieRememberMeManager() {
CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager();
cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));
cookieRememberMeManager.setCookie(simpleCookie());
return cookieRememberMeManager;
}
//配置Cookie的属性
@Bean
public SimpleCookie simpleCookie() {
SimpleCookie simpleCookie =new SimpleCookie();
simpleCookie.setName("rememberMe");
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
//Thymeleaf+Shiro视图页面标签的方言配置
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
自定义认证管理器:
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = (String) token.getPrincipal();
if (userName == null) {
//没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
throw new UnknownAccountException("用户不存在");
}
User u= service.selectUserByName(userName);
String db_password=u.getPassword();//数据库查出来的密文
String db_salt=u.getSalt();//盐
SimpleAuthenticationInfo info =new SimpleAuthenticationInfo(u,db_password, ByteSource.Util.bytes(db_salt),"CustomRealm");
return info;
}
自定义授权管理器:
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//认证时SimpleAuthenticationInfo中的第一个参数就是principals.getPrimaryPrincipal();获取的对象
User user=(User) principals.getPrimaryPrincipal();
Integer roleId = user.getRoleId();
List<SysMenu> sysMenus = menuService.selectMenuByRoleId(roleId);
HashSet<String> permission = new HashSet<>();
for (SysMenu menu : sysMenus) {
if(!StringUtils.isEmpty(menu.getPermission())){
permission.add(menu.getPermission());
System.out.println("add permission-->"+menu.getPermission());
}
}
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
info.setStringPermissions(permission);
return info;
}
- EhCache缓存配置:
shiro-ehcache.xml:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--diskStore:缓存数据持久化的目录 地址 -->
<diskStore path="d:\develop" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
Json配置
这项配置保证了前后端Json数据正常进行解析
@Bean
public HttpMessageConverters fastJsonConfigure(){
// 1.创建一个converter对象
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 2.创建配置对象
FastJsonConfig fastJsonConfig = new FastJsonConfig();
// 3.添加配置
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
// 4.将配置添加到转换器对象中
fastConverter.setFastJsonConfig(fastJsonConfig);
// 5. 解决中文乱码问题
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastConverter.setSupportedMediaTypes(mediaTypes);
// 6.将转换器对象转化为HttpMessageConverter对象
return new HttpMessageConverters((HttpMessageConverter) fastConverter);
}
分页配置
application.yml:
pagehelper:
helper-dialect: mysql
reasonable: true
文件上传配置
上传文件的大小限制:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
这项配置的作用是将服务器上某一位置作为映射路径,一提供文件上传空间。
@Configuration
public class FileUploadPathConfigure implements WebMvcConfigurer{
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//addResourceHandler,文件上传映射地址 th:src="@{/upload/}+${item.pic}"
registry.addResourceHandler("/upload/**").addResourceLocations("file:d:/Apache-Tomcat/Note/");
}
}
yml文件
为了方便观看,将yml文件整理如下:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: 'jdbc:mysql://localhost:3306/oa_sys?useUnicode=true&characterEncoding=UTF-8'
username: ----add your MySQL username----
password: ----add your MySQL password----
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
thymeleaf:
suffix: .html
aop:
auto: true
proxy-target-class: true
pagehelper:
helper-dialect: mysql
reasonable: true
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 property="username"/>
lacksPermission标签:如果当前Subject没有权限将显示body体内容。
<shiro:lacksPermission name="org:create"></shiro:lacksPermission>
hasRole标签:如果当前Subject有角色将显示body体内容。
<shiro:hasRole name="admin"></shiro:hasRole>
hasAnyRoles标签:如果当前Subject有任意一个角色(或的关系)将显示body体内容。
<shiro:hasAnyRoles name="admin,user"></shiro:hasAnyRoles>
lacksRole标签:如果当前Subject没有角色将显示body体内容。
<shiro:lacksRole name="abc"></shiro:lacksRole>
hasPermission标签:如果当前Subject有权限将显示body体内容。
<shiro:hasPermission name="user:create"></shiro:hasPermission>
用法示例
加入shiro:标签库:
<html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
如果有user:add权限则< div>之间的内容会显示:
<div shiro:hasPermission="user:add"></div>