Shiro安全框架——学习笔记
一、认识Shiro
1.1、Shiro介绍
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Java应用中经常使用的安全框架:Shiro
和Spring Security
, Shiro比Spring Security简单些
1.2、Shiro认证流程
从外部来看Shiro,即从应用程序角度来观察如何使用Shiro完成工作;
三个核心组件:Subject, SecurityManager 和 Realm.
Subject
:主体
,应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject,即代表“当前操作用户”。但是,在Shiro中,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager
:安全管理器
;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm
:域
,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
1.3、主要功能
Authentication
:身份认证/登录
,验证用户是不是拥有相应的身份,例如账号密码登陆;
Authorization
:授权
,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager
:会话管理
,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography
:加密
,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support
:Web支持
,可以非常容易的集成到Web环境;
Caching
:缓存
,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency
:并发
,shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing
:提供测试
支持;
Run As
:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me
:记住我
,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
【注意】
- Shiro不会去维护用户、维护权限;(需要自己设计)
1.4、Shiro 架构
Subject
:主体
,可以看到主体可以是任何可以与应用交互的“用户”;
SecurityManager
:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator
:认证器
,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authrizer
:授权器
,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm
:可以有1个或多个Realm,可以认为是安全实体数据源
,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
SessionManager
:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);
SessionDAO
:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
CacheManager
:缓存控制器
,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
Cryptography
:密码模块
,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
1.5、过滤器
当 Shiro 被运用到 web 项目时,Shiro 会自动创建一些默认的过滤器对客户端请求进行过滤。比如身份验证、授权等 相关的。默认拦截器可以参考 org.apache.shiro.web.filter.mgt.DefaultFilter
中的枚举 拦截器:
以下是 Shiro 提供的过滤器:
类型 | 过滤器简称 | 对应的Java类 |
---|---|---|
认证 | anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
认证 | authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
认证 | authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
认证 | user | org.apache.shiro.web.filter.authc.UserFilter |
授权 | perms | org.apache.shiro.web.filter.authc.PermissionsAuthorizationFilter |
授权 | roles | org.apache.shiro.web.filter.authc.RolesAuthorizationFilter |
授权 | rest | org.apache.shiro.web.filter.authc.HttpMethodPermissionFilter |
授权 | port | org.apache.shiro.web.filter.authc.PortFilter |
授权 | ssl | org.apache.shiro.web.filter.authc.SslFilter |
logout | org.apache.shiro.web.filter.authc.LogoutFilter | |
noSessionCreation | org.apache.shiro.web.filter.session.NoSessionCreationFilter |
例子:
请求路径 | 说明 |
---|---|
/admins/**=anon | 表示该 uri 可以匿名访问 |
/admins/**=authc | 表示该 uri 需要认证 才可访问 |
/admins/**=authcBasic | 表示该 uri 需要 httpBasic 认证 |
/admins/**=user | 表示该 uri 需要认证 或通过记住我认证 才能访问 |
/admins/**=perms[user:add:*] | 表示该 uri 需要认证用户拥有 user:add:* 权限 才能访问 |
/admins/**=roles[admin] | 表示该 uri 需要认证用户拥有 admin 角色 才能访问 |
/admins/**=rest[user] | 相当于 /admin/**=perms[user:method] ,其中method表示 get、post、delete等 |
/admins/**=port[8080] | 表示该 uri 需要使用 8081 端口 |
/admins/**=ssl | 表示该 uri 需要使用 https 协议 |
/admins/**=logout | 表示注销 ,可以当作固定配置 |
1.6、URL 匹配
1.6.1、URL 匹配模式
URL 使用 Ant风格
模式【注意:通配符匹配不包括目录分隔符 " / "】
匹配符名称 | 解释 |
---|---|
? | 匹配文件名中的一个字符 |
* | 匹配文件名中的任意字符 |
** | 匹配多层路径 |
//【 ?】匹配符例子
@RequestMapping(value = "test?")
例1:localhost:8080/testa
例2:localhost:8080/testb
//【 *】匹配符例子
@RequestMapping(value = "*test*")
例1:localhost:8080/aaatestaa
例2:localhost:8080/bbbbbtestbbbbb
//【 ** 】匹配符例子
@RequestMapping(value = "**/test")
例1:localhost:8080/a/b/c/test
例2:localhost:8080/a/b/c/d/e/testb
1.6.2、URL 匹配顺序
URL 权限采取第一次匹配优先的方式,即从头开始使用第一个匹配额 URL 模式对应的拦截器链。例如:
/bb/** --> filter1
/bb/aa --> filter2
/** --> filter3
# 如果请求url是 “/bb/aa”,因为按照声明顺序进行匹配,那么将使用 filter1 进行拦截!!!
二、快速搭建案例
2.1、Spring 中集成 Shiro
2.1.1、添加Shiro依赖
<!-- shiro依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
2.1.2、自定义Realm类
/**
* AuthenticatingRealm : 仅实现身份验证支持(登录)操作并将授权(访问控制)行为留给子类。【只能处理认证】
* AuthorizingRealm : 通过添加授权(访问控制)支持扩展了AuthenticatingRealm的功能。【可处理认证和授权】
*/
public class MyRealm extends AuthorizingRealm {
//处理【授权】的方法(此方法中负责授权,当后续用户在访问某个资源时,就自动从此方法返回的AuthorizationInfo对象中查找是否有相应的权限,有就放行,否则就报异常)
// *对于需要有特定权限才能访问的资源,在每次访问它们时,Shiro都会调用这个方法来获取用户的具体权限信息,
// *可能会造成重复访问数据库获取权限数据,所以需要添加一些优化控制。
// *例如:获取完权限后存入缓存或Session会话,这样下一次进入此方法就不用访问数据库了,但这样不能获取实时权限,
// *如果用户权限被更新了,必须要重新登录才能刷新权限信息
//SimpleAuthorizationInfo对象中存入了【“角色”,“权限编码”】
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//处理【认证】的方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在认证过程中可能会抛出异常
//UsernamePasswordToken : 一个简单的用户名/密码身份验证令牌,用于支持最广泛使用的身份验证机制。
UsernamePasswordToken token =(UsernamePasswordToken)authenticationToken;
//获取浏览器中输入的用户名(账号)
String username = token.getUsername();
//认证账号,正常情况我们需要这里从数据库中获取账号的信息,以及其他关键数据,例如账号是否被冻结等等
if(!"admin".equals(username)&&!"zhangsan".equals(username)){//判断用户账号是否正确
throw new UnknownAccountException("账号错误");
}
if("zhangsan".equals(username)){
throw new LockedAccountException("账号被锁定");
}
//定义一个密码,它应该是数据库中的正确的密码.把该密码传到构造方法中
String dbpassword="123456";
//认证密码是否正确(Shiro会自动的判断dbpassword和浏览器输入的密码是否一致.如果不一致,就抛出密码错误的异常)
return new SimpleAuthenticationInfo(username,dbpassword,getName());
}
}
2.1.3、Shiro配置类
/**
* Shiro安全框架的配置类
*/
@Configuration
public class ShiroConfig {
//配置一个自定义的Realm的bean,最终将使用这个bean来完成我们的认证和授权
@Bean
public Realm myRealm() {
MyRealm realm = new MyRealm();
return realm;
}
//配置Shiro的安全管理器
@Bean
public DefaultWebSecurityManager securityManager(Realm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置一个Realm,这个Realm是最终用于完成我们的认证和授权操作的具体对象
securityManager.setRealm(myRealm);
return securityManager;
}
//配置一个Shiro的过滤器bean,这个bean将配置Shiro相关的一个规则的拦截
//例如什么样的请求可以访问什么样的请求不可以访问等等
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
//创建Shiro的拦截器 ,用于拦截我们的用户请求
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
//设置Shiro的安全管理器,设置管理的同时也会指来定某个Realm 用完成我们权限分配
shiroFilter.setSecurityManager(securityManager);
//用于设置一个登录的请求地址,这个地址可以是一个html或jsp的访问路径,也可以是一个控制器的路径
//作用是用于通知Shiro我们可以使用这个路径转向到登录页面,当Shiro判断到我们当前的用户没有登录时就会自动跳转到这个路径
shiroFilter.setLoginUrl("/");
//登录成功后转向页面,由于用户的登录后期需要交给Shiro完成,因此就需要通知Shiro登录成功之后返回到哪个位置
shiroFilter.setSuccessUrl("/success");
//用于指定没有权限的路径,当用户访问某个功能时如果Shiro判断这个用户没有对应的操作权限,那么Shiro就会将请求
//转向到这个页面,用于提示用户没有操作权限
//页面位置 "webapp/WEB-INF/templates/noPermission.html"【模板解析器中配置】
shiroFilter.setUnauthorizedUrl("/noPermission");
return shiroFilter;
}
//指定能同时加载多个properties配置文件
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
perms.properties
# 【说明】该properties权限配置文件跟数据库中permission表中的 path=perm_id 字段对应
# 管理员权限分配
/admin/studentList=admin:studentList
/admin/courseList=admin:courseList
/admin/teacherList=admin:teacherList
/admin/passwordReset=admin:passwordReset
# 教师权限分配
/teacher/courseList=teacher:courseList
/teacher/passwordReset=teacher:passwordReset
# 学生权限分配
/student/unSelectList=student:unSelectList
/student/selectedCourse=student:selectedCourse
/student/overCourse=student:overCourse
/student/passwordReset=student:passwordReset
2.1.4、web应用初始化类中加shiro的过滤器
/**
* 注册过滤器
* 【代替web.xml配置文件中的<filter>标签</filter>】
* @return
*/
@Override
protected Filter[] getServletFilters() {
//配置一个springmvc的字符编码过滤器【解决post请求中文乱码问题】
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
//与shiro相关的过滤器
DelegatingFilterProxy delegatingFilterProxy=new DelegatingFilterProxy("shiroFilter");
delegatingFilterProxy.setTargetFilterLifecycle(true);
//返回一个过滤器数组
return new Filter[]{encodingFilter,hiddenHttpMethodFilter,delegatingFilterProxy};
}
2.1.5、登录的Controller类(demo)
@Controller
public class LoginController {
//首页登录跳转
@RequestMapping("/")
public String loginPage(){
return "login";
}
//登录
@RequestMapping("login")
public String login(String userName,String password){
//封装一个用户名密码令牌对象
UsernamePasswordToken token = new UsernamePasswordToken(userName,password);
Subject subject = SecurityUtils.getSubject();
try {
//调用自定义Realm类中的doGetAuthenticationInfo()方法进行认证
subject.login(token);
//根据不同的角色请求不同的路径,并重定向到相关的页面
if (subject.hasRole("admin")){
return "redirect:/admin/studentList";
}else if (subject.hasRole("teacher")){
return "redirect:/teacher/courseList";
}else {
return "redirect:/student/unSelectList";
}
} catch (UnknownAccountException e) {
//可以定义全局异常处理器用于捕获异常并处理异常!!!
//尝试使用与帐户主体关联的实际凭据不匹配的凭据进行身份验证时抛出
throw new UnknownAccountException("账号不存在");
} catch (LockedAccountException e){
throw new LockedAccountException("账号已锁定");
} catch (IncorrectCredentialsException e){
throw new IncorrectCredentialsException("密码错误");
}
}
}
三、Shiro高级应用
3.1、认证缓存
当登录成功过一次以后,我们退回到登录页面,输入任意账号和密码都能登录成功, 这是因为Shiro在登录成功以后会将数据写入Shiro的缓存导致,这就是Shiro的认证缓存。Shiro缓存可以避免重复认证。
如果需要用户再次使用正确的账号密码登录,解决方案有: 调subject.logout()退出当前的登录状态,清空会话缓存。
//在跳转到登录页面的控制器中添加注销代码,如下:
//创建一个shiro的Subject对象,利用这个对象来完成用户的登录认证
Subject subject = SecurityUtils.getSubject();
//执行登出,清空认证缓存
subject.logout();
3.2、密码加密
可以对密码进行MD5加密(别的加密方式自己可以去尝试)。当两个用户的密码相同时,单纯使用普通的MD5加密方式,加密后的密文也是一样的,这样也是不安全的。我们希望即便是两个人的原始密码一样,加密后的结果也不一样。这需要加盐,使用盐值加密
。
//处理【认证(登录)】的方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//在认证过程中可能会抛出异常
//UsernamePasswordToken : 一个简单的用户名/密码身份验证令牌,用于支持最广泛使用的身份验证机制。
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//获取浏览器中输入的用户名(账号)
String username = token.getUsername();
//认证账号信息【从数据库中获取账号信息进行校验】
UserLogin loginUser = userLoginService.login(username);
if (null == loginUser) {//判断用户账号是否为空
throw new UnknownAccountException("账号不存");
}else if (!loginUser.getLocked()){
throw new LockedAccountException("账号已锁定,请联系管理员");
}
//认证密码是否正确(Shiro会自动的判断dbpassword和浏览器输入的密码是否一致.如果不一致,就抛出密码错误的异常)
//使用username作为MD5盐值加密的盐值;【保证不同用户相同密码加密后密文不一致】
ByteSource salt = ByteSource.Util.bytes(username);
return new SimpleAuthenticationInfo(loginUser,loginUser.getPassword(), salt, getName());//getName()为该类的简短名称
}
在ShiroConfig配置类中添加密码匹配器
//配置一个自定义的Realm的bean,最终将使用这个bean来完成我们的认证和授权
@Bean
public Realm myRealm() {
MyRealm realm = new MyRealm();
//设置在身份验证尝试期间使用的 CredialsMatcher,以使用系统中存储的凭据验证提交的凭据。
//【hashedCredentialsMatcher:该类中的密码匹配器】
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
//密码匹配器
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashMatcher = new HashedCredentialsMatcher();
//加密方式
hashMatcher.setHashAlgorithmName("MD5");
//加密次数
hashMatcher.setHashIterations(10);
return hashMatcher;
}
【注意】使用密码加密需
要在Shiro的配置类中配置密码匹配器
。(配置如上)
- Shiro的配置类中Realm的bean中注入了密码匹配器后,Shiro会运用此加密规则来对浏览器中输入的密码明文在
doGetAuthenticationInfo()
方法返回new SimpleAuthenticationInfo(loginUser,loginUser.getPassword(), salt, getName())
时进行自动加密,并和数据库中的密码进行匹配验证,错误就抛异常。(自己是这样理解的)
3.3、配置访问权限
3.3.1、分配角色
数据库中的角色表(role表
):
例如该代码:map.put("/admin/**
", “authc,roles[admin]
”);要求以admin开头的请求必须要有admin角色才能访问。添加角色的代码如下演示:
/**
* 对于需要有特定权限才能访问的资源,在每次访问它们时,Shiro都会调用这个方法来获取用户的具体权限信息,
* 可能会造成重复访问数据库获取权限数据,所以需要添加一些优化控制。
* 例如:获取完权限后存入缓存或Session会话,这样下一次进入此方法就不用访问数据库了,但这样不能获取实时权限,
* 如果用户权限被更新了,必须要重新登录才能刷新权限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取当前的认证实体(可以是账号也可以是用户对象),并为他分配权限
UserLogin userLogin = (UserLogin) principalCollection.getPrimaryPrincipal();
//获取当前认证实体的角色【0:管理员,1:教师,2:学生】
Integer roleId = userLogin.getRoleId();
//根据角色id获取角色名,并分配给当前Subject(用户)
//AuthorizationInfo接口的简单 POJO 实现,将角色和权限存储为内部属性
SimpleAuthorizationInfo simpleAuthInfo = new SimpleAuthorizationInfo();
//定义用于存放角色的集合
Set<String> roles = new HashSet<>();
Role roleById = roleService.getRoleById(roleId);
roles.add(roleById.getRoleName());
//设置分配给帐户的角色
simpleAuthInfo.setRoles(roles);
//返回权限验证对象,Shiro会获取该对象中的角色及权限集合来判断当前用户是否有权限访问资源
return simpleAuthInfo;
}
3.3.2、分配权限
数据库中的权限表(permission表
):
(一)在Shiro配置类中的Shiro过滤器Bean实例添加如下配置进行权限读取配置(拦截链
):
//配置一个Shiro的过滤器bean,这个bean将配置Shiro相关的一个规则的拦截
//例如什么样的请求可以访问什么样的请求不可以访问等等
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
//创建Shiro的拦截器 ,用于拦截我们的用户请求
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
//设置Shiro的安全管理器,设置管理的同时也会指来定某个Realm 用完成我们权限分配
shiroFilter.setSecurityManager(securityManager);
//用于设置一个登录的请求地址,这个地址可以是一个html或jsp的访问路径,也可以是一个控制器的路径
//作用是用于通知Shiro我们可以使用这个路径转向到登录页面,当Shiro判断到我们当前的用户没有登录时就会自动跳转到这个路径
shiroFilter.setLoginUrl("/");
//登录成功后转向页面,由于用户的登录后期需要交给Shiro完成,因此就需要通知Shiro登录成功之后返回到哪个位置
shiroFilter.setSuccessUrl("/success");
//用于指定没有权限的路径,当用户访问某个功能时如果Shiro判断这个用户没有对应的操作权限,那么Shiro就会将请求
//转向到这个页面,用于提示用户没有操作权限
shiroFilter.setUnauthorizedUrl("/noPermission");
//【拦截链】定义一个Map集合,这个Map集合中存放的数据全部都是规则,用于设置通知Shiro什么样的请求可以访问什么样的请求不可以访问
Map<String, String> map = new LinkedHashMap<String, String>();
// /login 表示某个请求的路径 anon 表示可以匿名访问(无需登录)
map.put("/login", "anon");
//对项目根路径下的静态资源放行【否则静态资源会被shiro拦截无法正常访问】
map.put("/css/**","anon");
map.put("/fonts/**","anon");
map.put("/images/**","anon");
map.put("/js/**","anon");
//安全退出规则,等价于subject.logout()
map.put("/logout","logout");
/**
* 【我们可以在这里配置所有的[权限规则]这列数据真正是需要】
* 从数据库中读取出来,或者在控制器中添加Shiro的注解
* 【"/admin/add","authc,perms[admin:add]"】表示必须要拥有admin:add权限才能访问/admin/add,
* admin:add仅仅是一个权限编码,表示admin下的add权限
*/
//加载定义的权限配置文件 perms.properties
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("perms.properties");
Properties p = new Properties();
try {
//从输入字节流中读取属性列表(键和元素对)-
p.load(inputStream);
Set<Map.Entry<Object, Object>> entries = p.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
//获取请求路径
String reqUrl = (String)entry.getKey();
//获取请求所需的权限编码
String perm = (String) entry.getValue();
//将请求路径和对应的请求所需权限编码存入定义规则的map中
map.put(reqUrl,"authc,perms["+perm+"]");
}
}catch (Exception e){
e.printStackTrace();
}
//【admin=authc,roles[admin]】表示用户必需已通过认证,并拥有admin角色才可以正常发起'/admin'请求
map.put("/admin/**","authc,roles[admin]");
map.put("/teacher/**", "authc,roles[teacher]");
map.put("/student/**", "authc,roles[student]");
//表示其它所有的请求路径都要登录才能访问,这个必须必须写在Map集合的最后面,这个选项是可选的
//如果没有指定/** 那么如果某个请求不符合上面的拦截规则Shiro将放行这个请求
map.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(map);
return shiroFilter;
}
(二)自定义Realm类添加下列代码:
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取当前的认证实体(可以是账号也可以是用户对象),并为他分配权限
UserLogin userLogin = (UserLogin) principalCollection.getPrimaryPrincipal();
//获取当前认证实体的角色【0:管理员,1:教师,2:学生】
Integer roleId = userLogin.getRoleId();
//AuthorizationInfo接口的简单 POJO 实现,将角色和权限存储为内部属性
SimpleAuthorizationInfo simpleAuthInfo = new SimpleAuthorizationInfo()
//获取角色对应的权限,并分配给subject认证实体
//定义存放权限的集合
Set<String> perms = new HashSet<>();
List<Permission> permsList = permissionService.getPermissionListByRoleId(roleId);
//把权限保存在session域中【左侧菜单显示时使用】
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.setAttribute("menus",permsList);
//将权限集合中的权限编码放入perms集合中
for (Permission permission : permsList) {
perms.add(permission.getPermId());
}
//设置直接分配给帐户的基于字符串的权限
simpleAuthInfo.setStringPermissions(perms);
//返回权限验证对象,Shiro会获取该对象中的角色及权限集合来判断当前用户是否有权限访问资源
return simpleAuthInfo;
}
3.4、Shiro标签
3.4.1、thymeleaf整合Shiro
(一) 先在pom.xml中添加整合shiro的依赖;
<!-- thymeleaf整合shiro依赖 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
(二) 再在Spring MVC配置类中添加一个bean配置;
//注册Shiro方言对象
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
(三) 在SpringMvc配置类中向模板引擎注入Shiro方言对象;
//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver, ShiroDialect shiroDialect) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
//向模板引擎中注入Shiro方言对象
Set<IDialect> dialectSet = new HashSet<>();
dialectSet.add(shiroDialect);
templateEngine.setAdditionalDialects(dialectSet);
return templateEngine;
}
(四) 最后在页面上引入名称空间;
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
3.4.2、Shiro常用标签
guest 标签
<shiro:guest>
</shiro:guest>
用户没有身份验证(认证)时显示相应信息,即游客访问信息。
user 标签
<shiro:user>
</shiro:user>
用户身份验证通过(认证成功)后,显示相应的信息。
authenticated 标签
<shiro:authenticated>
</shiro:authenticated>
用户已经身份验证通过,即使用Subject.login登录成功。
notAuthenticated 标签
<shiro:notAuthenticated>
</shiro:notAuthenticated>
用户未进行身份验证,即没有调用Subject.login进行登录
principal 标签
用于获取认证实体,该实体可以是账号,也可以是用户对象
<shiro:principal />: 获取账号(字符串)
<shiro:principal property="username" />:获取用户对象的username属性值,
它相当于((User)Subject.getPrincipals()).getUsername()。
lacksPermission 标签
<shiro:lacksPermission name="org:create">
</shiro:lacksPermission>
如果当前Subject没有某个权限,显示相应信息
hasPermission 标签
<shiro:hasPermission name="user:create">
</shiro:hasPermission>
如果当前Subject有权限将显示标签体内容
hasRole 标签
<shiro:hasRole name="admin">
</shiro:hasRole>
如果当前Subject有角色将显示标签体内容。
hasAnyRoles 标签
<shiro:hasAnyRoles name="admin,user">
</shiro:hasAnyRoles>
如果当前Subject有任意一个角色(或的关系),将显示标签体内容。
lacksRole 标签
<shiro:lacksRole name="abc">
</shiro:lacksRole>
如果当前Subject没有角色将显示标签体内容。
hasAnyPermissions 标签
<shiro:hasAnyPermissions name="admin:add,admin:update">
</shiro:hasAnyPermissions>
如果当前Subject有任意一个权限(或的关系),将显示标签体内容。
hasAllRoles 标签
<shiro:hasAllRoles name="admin,user"></shiro:hasAllRoles>
必须拥有指定的全部角色,才显示标签体内容
hasAllPermissions 标签
<shiro:hasAllPermissions name="admin:add,admin:update"></shiro:hasAllRoles>
必须拥有指定的全部权限,才显示标签体内容