文 | 平哥 日期 | 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.Driverurl: jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8username: rootpassword: 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类,重写认证办法:
/*重写认证办法*/@Overrideprotected 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:
@Configurationpublic class ShiroConfig {@Autowiredprivate MyRealm myRealm;// 配置Security Manager@Beanpublic DefaultWebSecurityManager getSecurityManager(){// 实例化SecurityManagerDefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 实例化Shiro默认的明码匹配器HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();// 设置明码加密算法为md5matcher.setHashAlgorithmName("md5");// 设置迭代次数matcher.setHashIterations(2);// 将明码匹配器退出自定义realm中:myRealm.setCredentialsMatcher(matcher);// 将自定义的realm集成到DefaultWebSecurityManager对象中securityManager.setRealm(myRealm);return securityManager;}// 配置Shiro默认过滤器@Beanpublic 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")@ResponseBodypublic Result userLogin(String uname, String pwd){// 利用用户名明码实例化Shiro tokenUsernamePasswordToken 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@Beanpublic DefaultWebSecurityManager getSecurityManager(){// 实例化SecurityManagerDefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 实例化Shiro默认的明码匹配器HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();// 设置明码加密算法为md5matcher.setHashAlgorithmName("md5");// 设置迭代次数matcher.setHashIterations(2);// 将明码匹配器退出自定义realm中:myRealm.setCredentialsMatcher(matcher);// 将自定义的realm集成到DefaultWebSecurityManager对象中securityManager.setRealm(myRealm);//设置Shiro的remember me性能securityManager.setRememberMeManager(rememberMeManager());return securityManager;}
rememberMeManager()办法代码:
// 设置shiro的remembermeManagerprivate RememberMeManager rememberMeManager() {// 实例化shiro的remembermeManagerCookieRememberMeManager 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")@ResponseBodypublic Result userLogin(String uname, String pwd,@RequestParam(defaultValue = "false") Boolean rememberme){// 利用用户名明码实例化Shiro tokenUsernamePasswordToken tonken = new UsernamePasswordToken(uname, pwd,rememberme);// 省略余下代码,余下代码没变动,相见上部}
Step 3 Shiro实现后盾性能及页面显示受权
鉴权就是判断用户是否有权限执行相应办法或看到页面具体内容
受权就是授予认证用户指定的角色或指定的权限。
3.1 后盾性能办法鉴权
Shiro在后盾能够用在控制器办法,也能够用在业务办法。通常都在控制器办法上增加注解进行鉴权。
本我的项目中具体用户的增删改查四个办法,利用注解@RequirePermissions("要求的权限")
进行别离鉴权,具体代码如下:
新建UserController类:
@Controllerpublic class UserController {//申明单元办法:用户新增@RequiresPermissions("user:add")@RequestMapping("userAdd")@ResponseBodypublic String userAdd(){System.out.println("新增用户单元办法执行。");return "祝贺,新增用户胜利!";}//申明单元办法:用户删除@RequiresPermissions("user:del")@RequestMapping("userDel")@ResponseBodypublic String userDel(){System.out.println("用户信息删除单元办法执行");return "祝贺,用户删除胜利!";}//申明单元办法:用户批改@RequiresPermissions("user:edit")@RequestMapping("userEdit")@ResponseBodypublic 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*/@Beanpublic ShiroDialect shiroDialect() {return new ShiroDialect();}
3.3 Shiro受权
首先,重写MyRealm中的受权办法:
/*重写受权办法*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {// 获取以后认证用户的用户名String uname = (String)principalCollection.getPrimaryPrincipal();// 从数据库查问以后用户的权限信息List<String> permissionList = userService.getPermissions(uname);// 从数据库查问以后用户的角色信息List<String> roleList = userService.getRoles(uname);// 将查问到的权限、角色信息给ShiroSimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addStringPermissions(permissionList);info.addRoles(roleList);return info;}
而后,增加相应的两个查询方法,此处代码省略
最初增加鉴权失败后,进行异样拦挡的告诉类和办法:
新建ExpController类:
@ControllerAdvicepublic 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:缓存无效工夫--><defaultCachemaxEntriesLocalHeap="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 的 CacheManagershiroCacheManager.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:
@Componentpublic class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {// 申明缓存对象private Ehcache passwordRetryCache;// 获取EhCache缓存管理器并获取缓存策略public RetryLimitHashedCredentialsMatcher(EhCacheManager ehCacheManager) {this.passwordRetryCache = ehCacheManager.getCacheManager().getCache("loginRecordCache");}@Overridepublic 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...