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

文 | 平哥 日期 | 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…

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理