Shiro简单使用
简介
-
shiro是一个安全框架
-
可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等功能。
-
下载地址
-
官网:http://shiro.apache.org/
-
github:https://github.com/apache/shiro
-
功能
Authentication
:身份认证/登录,验证用户是不是拥有相应的身份Authorization
:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限Session Management
:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web 环境的Cryptography
:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储Web Support
:Web 支持,可以非常容易的集成到Web 环境Caching
:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率Concurrency
:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去Testing
:提供测试支持- “Run As”:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
Remember Me
:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
使用(springboot+shiro实现用户认证)
-
shiro核心API
- Subject:用户主体,包含登陆、注销、判断、授权等方法(把操作交给SecurityManager)
- SecurityManager:安全管理器(关联Realm)
- Realm:Shiro连接数据的桥梁
- Subject 关联 SecurityManager,SecurityManager 关联 Realm
-
Springboot 整合 Shiro 步骤
-
导入 shiro 整合依赖(pom.xml)
<dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis-spring-boot-starter</artifactId> <version>3.3.1</version> </dependency>
-
Controller控制器类
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class XxxxConfroller { @RequestMapping("/xxx") public String xxx(){ return "xxx"; } @RequestMapping("/xxx/{user}") public String user(String user,String pwd){ return user; } @RequestMapping("login") public String loginUser(){ return "login"; } }
-
自定义 Realm 类
创建自定义的 Realm 类:UserRealm.java(要继承 AuthorizingRealm 类)
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; public class UserRealm extends AuthorizingRealm { /** * 执行授权逻辑,获取授权信息(方法名直译) * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权逻辑"); return null; } /** * 执行认证逻辑,获取认证信息(方法名直译) * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行认证逻辑"); return null; } }
-
编写
shiro
配置类(最基本、重要)创建shiro配置类:ShiroConfig.java
- 创建 ShiroFilterFactoryBean(shiro过滤器)
- 创建 DefaultWebSecurityManager(默认web安全管理器)
- 创建 Realm 对象
- Subject 关联 SecurityManager,SecurityManager 关联 Realm
import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; /** * @author wei斯基 * Shiro配置类 * 通过@Bean将方法/类交由spring容器管理,之后利用@Qualifier根据设置的名字(若不设置名字则默认是方法/类名首字母小写)进行调用关联。 */ @Configuration public class ShiroConfig { /** * 第三步:创建 ShiroFilterFactoryBean(shiro过滤器) */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //1.设置安全管理器(关联web安全管理器) shiroFilterFactoryBean.setSecurityManager(securityManager); //2.添加shiro内置过滤器 //拦截,Map第一个值为要拦截的地址,第二个值是要使用的过滤器 Map<String, String> fileMap = new LinkedHashMap<String, String>(); // /xxx地址无需认证即可访问 fileMap.put("/xxx","anon"); // /xxx/user地址需要认证才可访问,拦截成功后会跳转到login.jsp页面 fileMap.put("/xxx/user","authc"); //修改为自定义的login登陆页面(设置登录网址) shiroFilterFactoryBean.setLoginUrl("/login"); // 设置过滤器链定义映射(放置一个用于存储拦截地址和过滤器的map,) shiroFilterFactoryBean.setFilterChainDefinitionMap(fileMap); return shiroFilterFactoryBean; } /** * 第二步:创建 DefaultWebSecurityManager(默认web安全管理器) */ @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); // 关联自定义的 Realm defaultWebSecurityManager.setRealm(userRealm); return defaultWebSecurityManager; } /** * 第一步:创建 Realm 对象,利用@Bean将其交给spring容器管理,以便给安全管理器调用 */ @Bean(name = "userRealm") public UserRealm getRealm() { return new UserRealm(); } }
-
使用 Shiro过滤器(ShiroFilterFactoryBean) 实现认证资源拦截
shiro内置常用过滤器:
- anon:无需认证(登陆)即可访问资源
- authc:必须认证才可访问
- user:如果使用remeberMe(记住我)的功能即可直接进行访问
- perms:该资源必须获得资源权限才可访问
- role:该资源必须获得用户权限才可访问
import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; /** * @author wei斯基 * Shiro配置类 * 通过@Bean将方法/类交由spring容器管理,之后利用@Qualifier根据设置的名字(若不设置名字则默认是方法/类名首字母小写)进行调用关联。 */ @Configuration public class ShiroConfig { /** * 第三步:创建 ShiroFilterFactoryBean(shiro过滤器) */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //1.设置安全管理器(关联web安全管理器) shiroFilterFactoryBean.setSecurityManager(securityManager); //2.添加shiro内置过滤器 //拦截,Map第一个值为要拦截的地址,第二个值是要使用的过滤器 Map<String, String> fileMap = new LinkedHashMap<String, String>(); // /xxx地址无需认证即可访问 fileMap.put("/toLogin","anon"); fileMap.put("/loginUser","anon"); // /xxx/user地址需要认证才可访问,拦截成功后会跳转到login.jsp页面 fileMap.put("/user/*","authc"); fileMap.put("/main","authc"); //修改为自定义的login登陆页面(设置登录网址) shiroFilterFactoryBean.setLoginUrl("/toLogin"); // 设置过滤器链定义映射(放置一个用于存储拦截地址和过滤器的map,) shiroFilterFactoryBean.setFilterChainDefinitionMap(fileMap); return shiroFilterFactoryBean; } /** * 第二步:创建 DefaultWebSecurityManager(默认web安全管理器) */ @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); // 关联自定义的 Realm defaultWebSecurityManager.setRealm(userRealm); return defaultWebSecurityManager; } /** * 第一步:创建 Realm 对象,利用@Bean将其交给spring容器管理,以便给安全管理器调用 */ @Bean(name = "userRealm") public UserRealm getRealm() { return new UserRealm(); } }
-
创建测试用的页面
login.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 color="red" th:text="${loginMsg}"></p> <form action="/loginUser" method="post"> 用户名:<input name="username" type="text"/> <br> 密码:<input name="password" type="password"/> <br> <input type="submit" value="登录"/> <input type="reset" value="重置"> </form> </body> </html>
main.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>main</title> </head> <body> <a href="/user/add">add</a> <a href="/user/update">update</a> </body> </html>
add.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>add</title> </head> <body> add </body> </html>
update.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>update</title> </head> <body> update </body> </html>
-
在Controller类中编写用户认证操作
package com.study.controller; import com.study.service.Impl.UserServiceImpl; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class UserController { @Autowired UserServiceImpl userServiceImpl; @RequestMapping(value = "/user/add") public String add() { return "/user/add"; } @RequestMapping(value = "/user/update") public String update() { return "/user/update"; } @RequestMapping("toLogin") public String toLogin() { return "login"; } @RequestMapping(value = "/loginUser") public String login(String username, String password, Model model) { /** * 使用shiro编写认证操作 */ // 1.获取Subject Subject subject = SecurityUtils.getSubject(); // 2.封装用户数据 UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 3.执行登陆操作 try { // subject.login(token)会跳转到 UserRealm的AuthenticationInfo中执行认证操作 subject.login(token); return "main"; } catch (UnknownAccountException e) { // 此错误(未知的帐号异常)表示没有此用户 model.addAttribute("loginMsg", "用户名不存在"); return "login"; } catch (IncorrectCredentialsException e) { // 此错误(不正确的凭据异常)表示密码错误 model.addAttribute("loginMsg", "密码错误"); return "login"; } } }
-
在UserRealm中编写认证操作
/** * 执行认证逻辑,获取认证信息(方法名直译) * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行认证逻辑"); //模拟用户名和密码 String username = "qweasdzxc"; String password = "qweasdzxc"; /** * 编写shiro判断逻辑,判断用户名和密码 */ // 1.判断用户名(前端传过来的便是一个 UsernamePasswordToken,将 authenticationToken 转换为 UsernamePasswordToken 便可利用方法将其中的用户名和密码取出) UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //将从 token 中取出的用户名和密码与数据库或者模拟的数据进行对比 if (token.getUsername().equals(username)) { // 2.判断密码,第一个参数是返回给.login方法的参数,可以为空;第二个参数是数据库或者模拟的密码;第三个参数是shiro的名字,可以为空 return new SimpleAuthenticationInfo("", password, ""); } else { System.out.println("用户名不存在"); // 此步骤 shiro 底层会抛出一个 UnknownAccountException return null; } }
-
shiro连接数据库进行判断
package com.study.shiro; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.study.pojo.User; import com.study.service.Impl.UserServiceImpl; import com.study.service.UserService; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; /** * @author wei斯基 */ public class UserRealm extends AuthorizingRealm { @Autowired UserServiceImpl userService; /** * 执行授权逻辑,获取授权信息(方法名直译) */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权逻辑"); return null; } /** * 执行认证逻辑,获取认证信息(方法名直译) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行认证逻辑"); QueryWrapper<User> userWrapper = new QueryWrapper<>(); /* 编写shiro判断逻辑,判断用户名和密码 */ // 1.判断用户名(前端传过来的便是一个 UsernamePasswordToken,将 authenticationToken 转换为 UsernamePasswordToken 便可利用方法将其中的用户名和密码取出) UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); System.out.println("token携带的username:" + username); userWrapper.eq("userName", username); User user = userService.getOne(userWrapper); if (user == null) { System.out.println("用戶名不存在"); return null; } else { // 判断密码,第一个参数是返回给.login方法的参数,可以为空;第二个参数是数据库或者模拟的密码;第三个参数是shiro的名字,可以为空 return new SimpleAuthenticationInfo("", user.getPassword(), ""); } } }
-
权限限制
在ShiroConfig,java中编写资源权限过滤
// 授权过滤器,只有获取相应的授权才能访问 fileMap.put("/user/add", "perms[user:add]"); ..... // 设置未授权页面 shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
package com.study.shiro; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; /** * @author wei斯基 * Shiro配置类 * 通过@Bean将方法/类交由spring容器管理,之后利用@Qualifier根据设置的名字(若不设置名字则默认是方法/类名首字母小写)进行调用关联。 */ @Configuration public class ShiroConfig { /** * 第三步:创建 ShiroFilterFactoryBean(shiro过滤器) */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //1.设置安全管理器(关联web安全管理器) shiroFilterFactoryBean.setSecurityManager(securityManager); //2.添加shiro内置过滤器 //拦截,Map第一个值为要拦截的地址,第二个值是要使用的过滤器 Map<String, String> fileMap = new LinkedHashMap<String, String>(); // 授权过滤器,只有获取相应的授权才能访问 fileMap.put("/user/add", "perms[user:add]"); // /xxx地址无需认证即可访问 fileMap.put("/toLogin", "anon"); fileMap.put("/loginUser", "anon"); // /xxx/user地址需要认证才可访问,拦截成功后会跳转到login.jsp页面 fileMap.put("/user/*", "authc"); fileMap.put("/main", "authc"); fileMap.put("/*", "authc"); //修改为自定义的login登陆页面(设置登录网址) shiroFilterFactoryBean.setLoginUrl("/toLogin"); // 设置未授权页面 shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth"); // 设置过滤器链定义映射(放置一个用于存储拦截地址和过滤器的map,) shiroFilterFactoryBean.setFilterChainDefinitionMap(fileMap); return shiroFilterFactoryBean; } /** * 第二步:创建 DefaultWebSecurityManager(默认web安全管理器) */ @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); // 关联自定义的 Realm defaultWebSecurityManager.setRealm(userRealm); return defaultWebSecurityManager; } /** * 第一步:创建 Realm 对象,利用@Bean将其交给spring容器管理,以便给安全管理器调用 */ @Bean(name = "userRealm") public UserRealm getRealm() { return new UserRealm(); } }
在 UserController.java 中添加
@RequestMapping("noAuth") public String noAuth() { return "noAuth"; }
编写未授权跳转页面:noAuth.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>noAuth</title> </head> <body> <h1>未获取该页面的访问权限</h1> </body> </html>
-
执行授权逻辑
/** * 执行授权逻辑,获取授权信息(方法名直译) */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权逻辑"); //给资源进行授权 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //添加资源的授权字符串 simpleAuthorizationInfo.addStringPermission("user:add"); return simpleAuthorizationInfo; }
-
关联数据库进行动态授权
/** * 执行授权逻辑,获取授权信息(方法名直译) */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权逻辑"); //给资源进行授权 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); // 静态,添加资源的授权字符串 // simpleAuthorizationInfo.addStringPermission("user:add"); QueryWrapper<User> userWrapper = new QueryWrapper<>(); /* 动态,从数据库取出用户的权限 */ // 获取当前用户 Subject subject = SecurityUtils.getSubject(); // getPrincipal是从下方的认证 SimpleAuthenticationInfo 的第一个参数传过来的 User user = (User) subject.getPrincipal(); // 将当前用户的用户名设置为查询条件 QueryWrapper<User> username = userWrapper.eq("username", user.getUsername()); User dbUser = userService.getOne(username); //设置授权字符串 simpleAuthorizationInfo.addStringPermission(dbUser.getPerms()); return simpleAuthorizationInfo; }
package com.study.shiro; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.study.pojo.User; import com.study.service.Impl.UserServiceImpl; import com.study.service.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; /** * @author wei斯基 */ public class UserRealm extends AuthorizingRealm { @Autowired UserServiceImpl userService; /** * 执行授权逻辑,获取授权信息(方法名直译) */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权逻辑"); //给资源进行授权 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); // 静态,添加资源的授权字符串 // simpleAuthorizationInfo.addStringPermission("user:add"); QueryWrapper<User> userWrapper = new QueryWrapper<>(); /* 动态,从数据库取出用户的权限 */ // 获取当前用户 Subject subject = SecurityUtils.getSubject(); // getPrincipal是从下方的认证 SimpleAuthenticationInfo 的第一个参数传过来的 User user = (User) subject.getPrincipal(); // 将当前用户的用户名设置为查询条件 QueryWrapper<User> username = userWrapper.eq("username", user.getUsername()); User dbUser = userService.getOne(username); //设置授权字符串 simpleAuthorizationInfo.addStringPermission(dbUser.getPerms()); return simpleAuthorizationInfo; } /** * 执行认证逻辑,获取认证信息(方法名直译) */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("执行认证逻辑"); QueryWrapper<User> userWrapper = new QueryWrapper<>(); /* 编写shiro判断逻辑,判断用户名和密码 */ // 1.判断用户名(前端传过来的便是一个 UsernamePasswordToken,将 authenticationToken 转换为 UsernamePasswordToken 便可利用方法将其中的用户名和密码取出) UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); System.out.println("token携带的username:" + username); userWrapper.eq("userName", username); User user = userService.getOne(userWrapper); if (user == null) { System.out.println("用戶名不存在"); return null; } else { // 判断密码,第一个参数是返回给.login方法的参数(Principal),可以为空;第二个参数是数据库或者模拟的密码;第三个参数是shiro的名字,可以为空 return new SimpleAuthenticationInfo(user, user.getPassword(), ""); } } }
-
shiro与thymeleaf的整合
pom.xml 导入thymeleaf扩展坐标
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
在 ShiroConfig类 中配置 ShiroDialect方法
/** * 配置ShiroDialect,用于thymeleaf和shiro的配合使用 */ @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); }
在页面上使用thymeleaf,main.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>main</title> </head> <body> <div shiro:hasPermission="user:add"> 用户添加:<a href="/user/add">add</a> </div> <div shiro:hasPermission="user:update"> 用户修改:<a href="/user/update">update</a> </div> </body> </html>
-