关于springboot:Spring-Boot-整合SSMThymeleafShiroEhCache实现登录认证和授权功能

32次阅读

共计 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…

正文完
 0