SpringBoot整合Shiro的方法详解
1.Shito简介
1.1 什么是shiro
Apache Shiro是一个java安全(权限)框架
Shiro可以非常容易的开发出足够好的应用,其不仅可以用在javase环境,也可以用在javaee环境
shiro可以完成,认证,授权,加密,会话管理,web集成,缓存等。
1.2 有哪些功能
2.QuickStart
官网下载的可能慢一些
首先新建一个普通的maven项目,然后在项目中导入新的maven项目模块,结构如下:
然后开始创建我们需要的文件,这些文件都可以在官网下载的文件中可以找到:
shiro.ini:
# Users and their assigned roles # # Each line conforms to the format defined in the # org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc # ----------------------------------------------------------------------------- [users] # user 'root' with password 'secret' and the 'admin' role root = secret, admin # user 'guest' with the password 'guest' and the 'guest' role guest = guest, guest # user 'presidentskroob' with password '12345' ("That's the same combination on # my luggage!!!" ;)), and role 'president' presidentskroob = 12345, president # user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz' darkhelmet = ludicrousspeed, darklord, schwartz # user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz' lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- # Roles with assigned permissions # # Each line conforms to the format defined in the # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc # ----------------------------------------------------------------------------- [roles] # 'admin' role has all permissions, indicated by the wildcard '*' admin = * # The 'schwartz' role can do anything (*) with any lightsaber: schwartz = lightsaber:* # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with # license plate 'eagle5' (instance specific id) goodguy = winnebago:drive:eagle5
导入相关依赖 pom.xml,官网未给出详细的依赖,具体的参考给出的git下载的文件,然后做了一些简单的修改。
<?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"> <parent> <artifactId>Shiro</artifactId> <groupId>com.nuist</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>hello-shiro</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.1</version> </dependency> <!-- configure logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> </project>
log4j.prop[erties:
log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
最重要的配置文件:
QuickStart:
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.text.IniRealm; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.naming.InitialContext; public class Quickstart { private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main(String[] args) { // 旧方法,由于shiro更新无法正常使用 // Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); // SecurityManager securityManager = factory.getInstance(); // 新方法 shiro更新问题 解决正常运行 DefaultSecurityManager securityManager = new DefaultSecurityManager(); IniRealm iniRealm = new IniRealm("classpath:shiro.ini"); securityManager.setRealm(iniRealm); SecurityUtils.setSecurityManager(securityManager); // Now that a simple Shiro environment is set up, let's see what you can do: // 获取当前用户对象 Subject currentUser = SecurityUtils.getSubject(); // 通过当前用户拿到session Session session = currentUser.getSession(); session.setAttribute("someKey", "aValue"); String value = (String) session.getAttribute("someKey"); if (value.equals("aValue")) { log.info("subject =>! [" + value + "]"); } // 判断当前用户是否被认证 if (!currentUser.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); // 设置记住我 try { currentUser.login(token); } catch (UnknownAccountException uae) { log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info("Password for account " + token.getPrincipal() + " was incorrect!"); } catch (LockedAccountException lae) { log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } // 最重要的一个异常,认证异常 catch (AuthenticationException ae) { //unexpected condition? error? } } //say who they are: //print their identifying principal (in this case, a username): log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); //test a role: if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); } // 测试一个简单的权限 // 粗粒度的一个权限限制 if (currentUser.isPermitted("lightsaber:wield")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } //a (very powerful) Instance Level permission: if (currentUser.isPermitted("winnebago:drive:eagle5")) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); } //注销功能 currentUser.logout(); System.exit(0); } }
以上主要的几个关键部分代码:
对象初始化部分
// 新方法 shiro更新问题 解决正常运行 DefaultSecurityManager securityManager = new DefaultSecurityManager(); IniRealm iniRealm = new IniRealm("classpath:shiro.ini"); securityManager.setRealm(iniRealm); SecurityUtils.setSecurityManager(securityManager);
获取当前用户
Subject currentUser = SecurityUtils.getSubject();
判断用户是否被认证
!currentUser.isAuthenticated()
判断用户是否具有什么角色
currentUser.hasRole("schwartz")
判断用户是否拥有权限
currentUser.isPermitted("lightsaber:wield")
注销
currentUser.logout();
此时我们运行启动,项目如下,那么一个简单的shiro quickStart 就已经启动好了。
注意
shir最重要的三个部分:
- subject 用户
- SecurityManager 管理所有的用户
- Realm 连接数据
3.SpringBoot中集成
1.导入shiro相关依赖
<!-- subject 用户 SecurityManager 管理所有用户 Realm 连接数据 --> <!--shiro与springboot--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.6.0</version> </dependency>
2.自定义UserRealm
package com.nuist.shirospringboot.config; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; /** * @author liuhuanhuan * @version 1.0 * @date 2022/5/8 17:59 * @Description */ public class UserRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=》授权doGetAuthorizationInfo方法"); return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了-》认证doGetAuthenticationInfo"); return null; } }
3.定义shiroConfig
package com.nuist.shirospringboot.config; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author liuhuanhuan * @version 1.0 * @date 2022/5/8 17:58 * @Description */ @Configuration public class ShiroConfig { // shiroFilterConfiguere @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 设置用户管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); return shiroFilterFactoryBean; } // defaultWebSecurity // 通过@Qualifier 是USerRealm进行绑定 @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userReaml") UserRealm userReaml) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 关联Realm securityManager.setRealm(userReaml); return securityManager; } // 创建realm对象 需要去进行自定义,这样就可以交给spring去进行托管了 @Bean public UserRealm userReaml(){ return new UserRealm(); } }
4.新建页面进行测试
package com.nuist.shirospringboot.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; /** * @author liuhuanhuan * @version 1.0 * @date 2022/5/8 17:52 * @Description */ @Controller public class Mycontrollrt { @RequestMapping({"/","/index"}) public String toIndex(Model model) { model.addAttribute("msg","hello shiro"); return "index"; } @RequestMapping("/user/add") public String add() { return "user/add"; } @RequestMapping("/user/update") public String update() { return "user/update"; } }
index.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页</h1> <p th:text="${msg}"></p> <hr> <a th:href="@{/user/add}">add </a> | <a th:href="@{/user/update}">update</a> </body> </html>
user/add.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>add</h1> </body> </html>
user/update.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>update</h1> </body> </html>
此时一个简单的项目就搭建起来了,然后开始shiro的认证授权的操作。
我们只需要在配置中添加如下代码:
Map<String,String> filterMap = new LinkedHashMap<>(); // filterMap.put("/user/add","authc"); // filterMap.put("/user/update","authc"); filterMap.put("/user/*","authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); shiroFilterFactoryBean.setLoginUrl("/toLogin");
此时就对我们所有的页面请求进行了拦截,然后转发到login的页面
login.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>login</h1> <hr> <form action=""> <p>用户名:<input type="text" name="username"></p> <p>密码:<input type="password" name="password"></p> <p><input type="submit"></p> </form> </body> </html>
此时在页面进行点击的时候,我们就无法正常进入页面,只能进入到我们的登录页面
进行登录验证的拦截,只有输入正确的账号密码才能够进入:
MyCOntroller中新增如下代码:
@RequestMapping("/login") public String login(String username,String password,Model model) { // 获取当前用户 Subject subject = SecurityUtils.getSubject(); // 封装当前用户 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password); try { subject.login(usernamePasswordToken); // 执行登录的方法,有异常进行处理 return "index"; } catch (UnknownAccountException e){ model.addAttribute("msg","用户名错误"); return "login"; } catch (IncorrectCredentialsException e) { // 密码不存在 model.addAttribute("msg","密码错误"); return "login"; } }
然后修改login页面的代码
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>login</h1> <hr> <p th:text="${msg}" style="color: red"></p> <form th:action="@{/login}"> <p>用户名:<input type="text" name="username"></p> <p>密码:<input type="password" name="password"></p> <p><input type="submit"></p> </form> </body> </html>
然后在我们的UserRealm
中doGetAuthenticationInfo方法中新增代码
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行了-》认证doGetAuthenticationInfo"); String name = "root"; String password = "123456"; UsernamePasswordToken authenticationToken1 = (UsernamePasswordToken) authenticationToken; if (!authenticationToken1.getUsername().equals(name)) { return null; // 抛出异常 } // 密码认证 shiro去做 return new SimpleAuthenticationInfo("",password,""); }
此时我们通过UsernamePasswordToken 获取我们封装好的账号和密码,但是我们只需要进行账号的认证,密码的认证交给我们的shiro去做就可以了。
具体的用户授权,我们可以进行连接数据库的设置,但是我为了偷懒,我就不去手动创建与数据库的链接啦。
下面我们来进行页面的授权操作
我们想要进行用户的授权操作
我们需要在shiroConfig中新增如下代码:
filterMap.put("/user/add","perms[user:add]");
此时代表如果用户拥有user:add操作的话,可以显示,如果没有的话就不能正常显示
我们可以自定义一个页面来用于返回信息的显示:
@RequestMapping("/noauth") @ResponseBody public String noauth() { return "未经过授权无法进行访问"; }
当用户没有add权限的时候,我们就提示无法显示:
此时我们就完成了单个的用户授权的操作。此时我们再去进行具体的页面操作
shiro与thymeleaf的结合:
需要将我们进行验证的页面进行如下操作:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页</h1> <p> <a th:href="@{/toLogin}">登录</a> </p> <p th:text="${msg}"></p> <hr> <div shiro:hasPermission="user:add"> <a th:href="@{/user/add}">add </a> </div> | <div shiro:hasPermission="user:update"> <a th:href="@{/user/update}">update</a> </div> </body> </html>
此时应用的方式和springsecurity的方式基本一致。
然后在我们的授权页面进行操作如下:
UserRealm中修改doGetAuthorizationInfo方法,如下:
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行了=》授权doGetAuthorizationInfo方法"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // 新增授权页面 authorizationInfo.addStringPermission("user:add"); // 拿到当前对象 ,然后通过对象中的授权方式进行判断 return authorizationInfo; }
此时我们赋予用户只有add的权限,那么按理说在页面中是无法显示update的按钮,那么我们进行测试下,是否可以正常使用:
此时认证授权部分已经成功啦,以上就是我们进行的一个小小的demo,更深入的学习,后续继续更新。