前言
目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了
一、shiro简介
Apache Shiro 是 Java 的一个安全框架。可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:
Authentication :身份认证/登录,验证用户是不是拥有相应的身份; Authorization: :授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用 户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用 户对某个资源是否具有某个权限; Session Manager: :会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信 息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的; Cryptography :加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; Web Support :Web 支持,可以非常容易的集成到 Web 环境; Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以 提高效率; Concurrency :shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能 把权限自动传播过去; Testing :提供测试支持; Run As :允许一个用户假装为另一个用户(如果他们允许)的身份进行访问; Remember Me: :记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录 了。
注意:Shiro 不会去维护用户、维护权限;这 些需要我们 自己去 设计/ 提供 ; 然后通过 相应的 接口注入给 给 Shiro 即可。
shiro外部及内部结构: 具体参数配置什么意思可查看此文:Shiro完整教程, 附带各种配置
二、shiro简单使用
1.Idea穿件一个Maven项目 引入pom依赖:
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
2.任意创建一个包,在里面创建一个Tutorial类
package me.aihe;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Tutorial {
private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
public static void main(String[] args) {
log.info("My First Apache Shiro Application");
System.exit(0);
}
}
3.使用Shiro Shiro提供了一个通用的方案通过 INI 进行配置 ,当然也可以通过XML,YMAL,JSON等进行配置。 在resource目录下面,创建一个shiro.ini的文件。内容如下:
# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
aihe = aihe, goodguy, client
# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
client = look:*
goodguy = winnebago:drive:eagle5
4.引用Shiro.ini配置进行测试 现在改变我们的Tutorial类文件,内容如下
package me.aihe;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Tutorial {
private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
public static void main(String[] args) {
log.info("My First Apache Shiro Application");
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("key","value");
if (!currentUser.isAuthenticated()){
UsernamePasswordToken token = new UsernamePasswordToken("aihe", "aihe");
token.setRememberMe(true);
try{
currentUser.login(token);
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
if ( currentUser.hasRole( "client" ) ) {
log.info("Look is in your role" );
} else {
log.info( "....." );
}
if ( currentUser.isPermitted( "look:desk" ) ) {
log.info("You can look. Use it wisely.");
} else {
log.info("Sorry, you can't look.");
}
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();
}
catch ( UnknownAccountException uae ) {
} catch ( IncorrectCredentialsException ice ) {
} catch ( LockedAccountException lae ) {
} catch ( AuthenticationException ae ) {
}
}
System.exit(0);
}
}
这个相对来说是一个简单的程序,但也证明了一些shiro的基本用法,我们可以通过shiro进行认证,权限控制等
三、身份验证
身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来 表明他就是他本人,如提供身份证,用户名/密码来证明。 在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能 验证用户身份: principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。 一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名/密码/手机号。 credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。 最常见的 principals 和 credentials 组合就是用户名/密码了。接下来先进行一个基本的身份认 证。 另外两个相关的概念是之前提到的 Subject 及 Realm,分别是主体及验证主体的数据源。
3.1.环境准备
1.引入pom依赖( junit、common-logging 及 shiro-core 依赖)
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
3.2.登录/退出
1.原理:
使用** FormAuthenticationFilter过滤器实现, 原理如下: 1.用户没有认证时, 就会请求 loginurl进行认证, 用户身份和用户密码提交数据到loginurl地址. 2.数据提交到 loginurl 地址后, 由 FormAuthenticationFilter进行拦截, 并取出 request 中的username 和 password. 3.然后 FormAuthenticationFilter** 会调用 realm, 在进行调用时会传入一个 token, 也就是会传入username 和 password. 4.最后 realm 认真时根据 username 查询用户信息.例如我们之前查询了用户菜单和 url. 如果查询不到, realm 返回 null, ** FormAuthenticationFilter**向 request 域填充一个参数, 这个参数记录了异常信息.
2.实现
@RequestMapping("login")
public String login(HttpServletRequest request)throws Exception{
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
if(exceptionClassName!=null){
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
throw new CustomException("账号不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new CustomException("用户名/密码错误");
} else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("验证码错误 ");
}else {
throw new Exception();
}
}
return "login";
}
这里一定要注意我们写的这个 login() 方法, 只是负责处理认证失败的结果, 如果用户认证成功那么就会跳转到上一个请求路径.
什么是上一个请求路径呢? 答: 如果你要访问 xxxx.jsp 但是 shiro 发现你还没有登录, 就会进行拦截, 并跳转到登录界面例如login.jsp, 当认证成功后会跳转到xxxx.jsp
3.退出 不用我们实现退出, 只要访问一个退出 url, 由LogoutFilter拦截住, 清除 session.
/logout.action = logout
4.从 Shiro 的 session 中获取认证信息
Subject subject = SecurityUtils.getSubject();
subject.getPrincipal();
四、shiro授权
4.1.shiro授权的三种方式:
1.编程式:通过写if/else授权代码块完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
} else {
}
2.注解式:通过在执行的Java方法上放置相应的注解完成(没有权限将抛出相应的异常):
@RequiresRoles("admin")
public void hello() {
}
3.JSP/GSP标签:在JSP/GSP页面通过相应的标签完成:
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
4.2.实现
1.在src包下创建一个shiro-permission.ini配置文件,用于模拟数据库中的权限数据,内容如下:
#基于用户的访问控制
[users]
#用户zhangsan的密码是123,此用户具有role1和role2两个角色;用户wang具有role2一个角色
zhangsan=123,role1,role2
wang=123,role2
#基于权限的访问控制
[roles]
#角色role1对资源user拥有create、update权限
role1=user:create,user:update
#角色role2对资源user拥有create、delete权限
role2=user:create,user:delete
#角色role3对资源user拥有create权限
role3=user:create
对配置文件中的解释如下:
权限标识符号规则:资源:操作:实例(中间使用半角:分隔)。如下:
user:create:01,表示对用户资源的01实例进行create操作。 user:create,表示对用户资源进行create操作,相当于user:create:,对所有用户资源实例进行create操作。 user::01,表示对用户资源实例01进行所有操作。
然后写一个测试类Authorization.java,代码如下:
public class AuthorizationTest
{
@Test
public void testAuthorization()
{
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro-permission.ini");
SecurityManager securityManager=factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","123");
try {
subject.login(token);
}catch (AuthenticationException e)
{
e.printStackTrace();
}
System.out.println("认证状态:"+subject.isAuthenticated());
boolean ishasRole=subject.hasRole("role1");
System.out.println("单个角色判断"+ishasRole);
boolean hasAllRoles=subject.hasAllRoles(Arrays.asList("role1","role2"));
System.out.println("多个角色判断"+hasAllRoles);
subject.checkRole("role3");
boolean isPermitted=subject.isPermitted("user:create");
System.out.println("单个权限判断"+isPermitted);
boolean isPermittedAll=subject.isPermittedAll("user:create:1","user:update");
System.out.println("多个权限判断:"+isPermittedAll);
}
}
从测试代码中我们可以知道,只有当用户信息得到认证后才能对用户进行授权操作。
上面只是一个简单的入门程序,从代码中直接读取数据库(即配置文件)中的权限数据,然后将其与代码中的给定权限进行判断看该用户是否具有该权限,而实际操作中我们是通过Realm进行读取数据库中的数据的。所以接下来讲自定义Realm进行授权。
4.3.自定义Realm进行授权
1.需求 上边的程序通过shiro-permission.ini 对权限信息进行静态配置,实际开发中从数据库中获取权限数据。就需要自定义realm,由realm从数据库查询权限数据。
realm根据用户身份查询权限数据,将权限数据返回给authorizer (授权器)。
2.自定义Realm 在上篇文章自定义的realm中即CustomRealm.java ,修改doGetAuthorizationInfo() 方法,代码如下:
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userCode= (String) principals.getPrimaryPrincipal();
List<String> permissions=new ArrayList<>();
permissions.add("user:create");
permissions.add("items:add");
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
}
然后要对该自定义Realm进行配置,即将自定义Realm放入SecurityManager中,上篇文章中的shiro-realm.ini配置文件中我们已实现。
然后便可以进行测试了,测试代码如下:
@Test
public void testAuthorizationCustomRealm()
{
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:config/shiro-realm.ini");
SecurityManager securityManager=factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","111111");
try {
subject.login(token);
}catch (AuthenticationException e)
{
e.printStackTrace();
}
System.out.println("认证状态:"+subject.isAuthenticated());
boolean isPermitted=subject.isPermitted("user:create:1");
System.out.println("单个权限判断"+isPermitted);
boolean isPermittedAll=subject.isPermittedAll("user:create:1","user:create");
System.out.println("多个权限判断:"+isPermittedAll);
}
授权流程讲解:
1.对subject 进行授权,调用方法isPermitted (“permission串”)。 2.SecurityManager 执行授权,通过ModularRealmAuthorizer 执行授权。3. 3.ModularRealmAuthorizer 执行realm(自定义的CustomRealm)从数据库查询权限数据。即调用realm的授权方法:doGetAuthorizationInfo() 。 4.realm 从数据库查询权限数据,返回ModularRealmAuthorizer 。 5.ModularRealmAuthorizer 调用PermissionResolver 进行权限串比对。 6.如果比对后,isPermitted 中"permission串"在realm查询到权限数据中,说明用户访问permission串有权限,否则 没有权限,抛出异常。
五、与SpringBoot集成案例
SpringBoot整合Shiro(Java安全框架)案例(含源码)
六、shiro各个拦截器对比及使用配置
6.1.各种拦截器的特点
简写(加粗为常用) | 名称 | 优先级(1为最高) | 说明 | 对应Java类 |
---|
anon | 匿名拦截器 | 1 | 不需要登录就能访问,一般用于静态资源,或者移动端接口 | org.apache.shiro.web.filter.authc.AnonymousFilter | authc | 登录拦截器 | 2 | 需要登录认证才能访问的资源 | org.apache.shiro.web.filter.authc.FormAuthenticationFilter | authcBasic | Http拦截器 | 3 | Http身份验证拦截器,非常用类型,不太了解 | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter | logout | 登出拦截器 | 4 | 用户登出拦截器,主要属性:redirectURL退出登录后重定向的地址 | org.apache.shiro.web.filter.authc.LogoutFilter | noSessionCreation | 不创建会话拦截器 | 5 | 调用 subject.getSession(false) 不会有什么问题,但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常 | org.apache.shiro.web.filter.authc.NoSessionCreationFilter | prems | 权限拦截器 | 6 | 验证用户是否拥有资源权限 | org.apache.shiro.web.filter.authc.PermissionsAuthorizationFilter | port | 端口拦截器 | 7 | 其主要属性: port(80) 如果用户访问该页面是非 80,将自动将请求端口改为 80 并重定向到该 80 端口 | org.apache.shiro.web.filter.authc.PortFilter | rest | rest风格拦截器 | 8 | rest 风格拦截器,自动根据请求方法构建权限字符串构建权限字符串;非常用类型拦截器 | org.apache.shiro.web.filter.authc.HttpMethodPermissionFilter | roles | 角色拦截器 | 9 | 验证用户是否拥有资源角色 | org.apache.shiro.web.filter.authc.RolesAuthorizationFilter | ssl | SSL拦截器 | 10 | 只有请求协议是https才能通过,否则你会自动跳转到https端口(443) | org.apache.shiro.web.filter.authc.SslFilter | user | 用户拦截器 | 11 | 用户拦截器,用户已经身份验证 / 记住我登录的都可; | org.apache.shiro.web.filter.authc.UserFilter |
6.2.INI文件配置拦截器
1.配置之前,我们还需要讲一下 拦截器的通配符的写法,如下所示:
?:匹配一个字符 *:匹配零个或多个字符 **:匹配零个或多个路径
2.然后,我们看一下[urls]模块的示例.这里我只用到了一些常用的拦截器:
[urls]
#不需要登录
/login=anon
/static
3.但是,我们发现一个问题,假设用户没有该权限和没有该角色,或者没有登录的时候,还有登出的时候,我们都需要给他们配置对应的重定向路径.这时候,我们就不能在[urls]模块中使用了,我们需要在[main]模块中进行各个情况路径的重定向设置了.示例代码如下所示:
[main]
#用户登录的地址
authc.loginUrl = /login
#用户没有对应角色的跳转重指向
roles.unauthorizedUrl = /login
#用户没有对应权限的跳转重指向
perms.unauthorizedUrl = /login
#用户登出的跳转重指向
logout.redirectUrl = /login
注意:INI文件中 [urls] 模块拦截顺序是从上往下依次执行.
6.3.代码形式配置拦截器
使用INI文件配置拦截器是较为简洁的配置形式,其实质就是通过Bean注入的形式配置拦截器 接下来我们看一下我们如何通过代码形式配置拦截器.
配置了核心安全事务管理器 和自定义的权限登录器.代码如下所示:
@Configuration
public class ShiroConfiguration {
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager(@Qualifier("myShiroRealm") MyShiroRealm myShiroRealm) {
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
manager.setRealm(myShiroRealm);
return manager;
}
@Bean(name="myShiroRealm")
public MyRealm authRealm() {
MyRealm myShiroRealm=new MyRealm();
return myShiroRealm;
}
}
我们继续配置拦截器模块的代码.整体代码如下所示:
@Bean(name="shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) {
ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
bean.setSecurityManager(manager);
bean.setLoginUrl("/login");
bean.setSuccessUrl("/home");
bean.setUnauthorizedUrl("/login");
LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
filterChainDefinitionMap.put("/loginUser", "anon");
filterChainDefinitionMap.put("/static/*", "anon");
filterChainDefinitionMap.put("/logout*","anon");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
比较过来,依然发现,使用INI文件形式配置拦截器要比代码形式更加简洁.
6.4.简单使用
现在我们来拿/html/login = anon和/html/home = authc以及设置重定向的authc.loginUrl = /html/login来简单说明一下,具体应该如何使用.
我们想让SpringBoot项目支持Html格式的访问,那么我们需要在pom.xml文件中加入如下的Maven依赖.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
同时,我们要在application.properties文件中配置如下信息.设置HTML的存放路径
spring.thymeleaf.prefix=classpath:/templates/
spring.mvc.view.suffix=.html
目录结构如下:
然后,我们把ShiroConfiguration配置文件进行如下的配置:
@Configuration
public class ShiroConfiguration {
@Bean(name="shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) {
ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
bean.setSecurityManager(manager);
bean.setLoginUrl("/index");
bean.setSuccessUrl("/home");
bean.setUnauthorizedUrl("/index");
LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
filterChainDefinitionMap.put("/index", "anon");
filterChainDefinitionMap.put("/home", "authc");
filterChainDefinitionMap.put("/**","anon");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
return manager;
}
}
我们在resources目录下创建两个html页面,一个login.html,一个home.html,其中代码如下所示.
login:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
</head>
<body>
<div style="text-align: center;margin-top: 100px; font-size: 20px;">用户登录界面</div>
</body>
</html>
home:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页面</title>
</head>
<body>
<div style="text-align: center;margin-top: 100px; font-size: 20px;">Home界面</div>
</body>
</html>
然后我们需要编写两个接口来访问login.html和home.html.这里我创建了一个名为HtmlController的控制器.整体代码较为简单,这里就直接黏贴出所有代码了.如下所示:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map;
@Controller
public class HtmlController {
@RequestMapping(value = "/index",method = RequestMethod.GET)
public String userLoginHtmlAction (){
return "index";
}
@RequestMapping(value = "/home",method = RequestMethod.GET)
public String userHomeHtmlAction (){
return "home";
}
}
然后我们就会猜想有如下情况出现,假设我们可以直接通过浏览器访问/html/login,那么我们会直接访问到login.html ,但是当我们访问/html/home的时候,由于我们没有进行用户登录认证(根本就没有做登录接口.😁),所以我们不可能通过认证,通过登录重指向,浏览器仍然会展示login.html 页面,下面我们就启动项目来验证一下.如下所示,拦截成功.
七、集成Shiro之Shiro标签(.jsp和.ftl)
7.1.JSP的Shiro标签
在JSP中使用Shiro标签比较简单,我们只需要注意用法即可.下面我就把所以Shiro标签放在下面了,各位看官自行查考:
<shiro:guest>
游客访问 <a href = "login.jsp"></a>
</shiro:guest>
user 标签:用户已经通过认证\记住我 登录后显示响应的内容
<shiro:user>
欢迎[<shiro:principal/>]登录 <a href = "logout">退出</a>
</shiro:user>
authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的
<shiro:authenticted>
用户[<shiro:principal/>] 已身份验证通过
</shiro:authenticted>
notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证
<shiro:notAuthenticated>
未身份验证(包括"记住我")
</shiro:notAuthenticated>
principal 标签:显示用户身份信息,默认调用
Subjec.getPrincipal()获取,即Primary Principal
<shiro:principal property = "username"/>
hasRole标签:如果当前Subject有角色将显示body体内的内容
<shiro:hashRole name = "admin">
用户[<shiro:principal/>]拥有角色admin
</shiro:hashRole>
hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容
<shiro:hasAnyRoles name = "admin,user">
用户[<shiro:pricipal/>]拥有角色admin 或者 user
</shiro:hasAnyRoles>
lacksRole:如果当前 Subjec没有角色将显示body体内的内容
<shiro:lacksRole name = "admin">
用户[<shiro:pricipal/>]没有角色admin
</shiro:lacksRole>
hashPermission:如果当前Subject有权限将显示body体内容
<shiro:hashPermission name = "user:create">
用户[<shiro:pricipal/>] 拥有权限user:create
</shiro:hashPermission>
lacksPermission:如果当前Subject没有权限将显示body体内容
<shiro:lacksPermission name = "org:create">
用户[<shiro:pricipal/>] 没有权限org:create
</shiro:lacksPermission>
7.2.Freemark的Shiro标签
1.在SpringBoot里面并不是直接支持JSP文件的,然后我就在项目中使用了Freemark,但是Shiro标签并不能直接支持.ftl文件,所以我们需要先引入一个Maven依赖:
<dependency>
<groupId>net.mingsoft</groupId>
<artifactId>shiro-freemarker-tags</artifactId>
<version>0.1</version>
</dependency>
2.然后,我们写一个名为ShiroTagsFreeMarkerCfg配置类来对Freemark使用Shiro标签进行配置.当然了,要确定配置类能被正确注入到Bean中,代码如下所示:
import com.jagregory.shiro.freemarker.ShiroTags;
import freemarker.template.TemplateModelException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import javax.annotation.PostConstruct;
@Component
public class ShiroTagsFreeMarkerCfg {
@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;
@PostConstruct
public void setSharedVariable() throws TemplateModelException {
freeMarkerConfigurer.getConfiguration().setSharedVariable("shiro", new ShiroTags());
}
}
3.配置类搞好之后,我们就可以在.ftl文件中使用Shiro的权限标签了,标签格式如下所示:
<@shiro.guest>
游客访问 <a href = "login.jsp"></a>
</@shiro.guest>
user 标签:用户已经通过认证\记住我 登录后显示响应的内容
<@shiro.user>
欢迎[<@shiro.principal/>]登录,<a href="/logout.html">退出</a>
</@shiro.user>
authenticated标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的
<@shiro.authenticated>
用户[<@shiro.principal/>]已身份验证通过
</@shiro.authenticated>
notAuthenticated标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证
<@shiro.notAuthenticated>
当前身份未认证(包括记住我登录的)
</@shiro.notAuthenticated>
principal 标签:显示用户身份信息,默认调用
Subjec.getPrincipal()获取,即Primary Principal
<@shiro.principal property="username"/>
hasRole标签:如果当前Subject有角色将显示body体内的内容
<@shiro.hasRole name="admin">
用户[<@shiro.principal/>]拥有角色admin<br/>
</@shiro.hasRole>
hasAnyRoles标签:如果Subject有任意一个角色(或的关系)将显示body体里的内容
<@shiro.hasAnyRoles name="admin,user,member">
用户[<@shiro.principal/>]拥有角色admin或user或member<br/>
</@shiro.hasAnyRoles>
lacksRole:如果当前 Subjec没有角色将显示body体内的内容
<@shiro.lacksRole name="admin">
用户[<@shiro.principal/>]不拥有admin角色
</@shiro.lacksRole>
hashPermission:如果当前Subject有权限将显示body体内容
<@shiro.hasPermission name="user:add">
用户[<@shiro.principal/>]拥有user:add权限
</@shiro.hasPermission>
lacksPermission:如果当前Subject没有权限将显示body体内容
<@shiro.lacksPermission name="user:add">
用户[<@shiro.principal/>]不拥有user:add权限
</@shiro.lacksPermission>
八、会话管理
会话管理器管理着应用中所有 Subject 的会话的创建、维护、删除、失效、验证等工作。是 Shiro 的核心组件,顶层组件 SecurityManager 直接继承了 SessionManager ,且提供了 SessionsSecurityManager 实 现 直 接 把 会 话 管 理 委 托 给 相 应 的 SessionManager , DefaultSecurityManager 及 DefaultWebSecurityManager 默认 SecurityManager 都继承了 SessionsSecurityManager 。
8.1.会话相关API
①Subject.getSession() 获取会话,等价于Subject.getSession(true) ,即如果当前没有创建session对象会创建一个;Subject.getSession(false) ,如果当前没有创建session对象则返回null。 ②Subject.getSession(true) ③session.getId() 获取当前会话的唯一标识。 ④session.getHost() 获取当前会话的主机地址。 ⑤session.getTimeout() & session.setTimeout(毫秒) 设置/获取当前Session的过期时间。 ⑥session.getStartTimestamp() & session.getLastAccessTime() 获取会话的启动时间及最后访问时间;如果是J2SE环境需要自己定期调用session.touch()去更新最后访问时间;如果是Web环境,每次进入ShiroFilter都会自动调用session.touch()来更新最后访问时间。 ⑦session.touch() & session.stop() 更新会话最后访问时间以及销毁会话;Subject.logout()会自动调用session.stop()。在Web应用中,调用HttpSession.invalidate()也会自动调用session.stop()来销毁shiro的会话。 ⑧session.setAttribute(key,val) & session.getAttribute(key) & session.removeAttribute(key) 设置/获取/删除 会话属性。
8.2.会话监听器(SessionListener接口)
①onStart(Session) 监听会话创建事件 ②onStop(Session) 监听会话销毁事件 ③onExpiration(Session) 监听会话过期事件
8.3.SessionDao(会话持久化、提供CRUD操作)
①AbstractSessionDAO 提供了 SessionDAO 的基础实现,如生成会话ID等。 ②CachingSessionDAO 提供了对开发者透明的会话缓存的功能,需要设置相应的 CacheManager。 ③MemorySessionDAO 直接在内存中进行会话维护。 ④EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用 ConcurrentHashMap 保存缓存的会话。
tips:在实际开发中,如果要用到SessionDAO组件,可以自定义类实现自EnterpriseCacheSessionDAO类,为其注入sessionIdGenerator属性,如果用到缓存的话还可以注入一个缓存的名字。最后将这个SesionDAO组件注入给SessionManager(会话管理器),最后将SessionManager配置给SecurityManager。下图是一个完整的配置细节。
8.4.会话验证
①Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话。 ②出于性能考虑,一般情况下都是获取会话的同时来验证会话是否过期并停止会话的;但是如果在Web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定义的检测会话是否过期,Shiro提供了会话验证调度器来定期检查会话是否过期,SessionValidationScheduler 。 ③Shrio也提供了使用Quartz 会话验证调度器 QuartzSessionValidationScheduler 。
九、缓存
方法一:
在securityManager配置中添加cacheManager配置项,会注入到realm中。
方法二:在realm中配置。
realm本身实现了CacheManagerAware接口
public interface CacheManagerAware {
void setCacheManager(CacheManager cacheManager);
}
securityManager不只会帮realm注入cacheManager,还会帮sessionManager注入cacheManager
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="redisCacheManager" />
<property name="realm" ref="myRealm" />
<property name="sessionManager" ref="sessionManager"/>
</bean>
这样的话,realm和sessionManager就不用再配缓存
9.1. 集中式缓存
基于Redis的集中式缓存方案:https://github.com/alexxiyang/shiro-redis
基于Memcached的集中式缓存方案:https://github.com/mythfish/shiro-memcached
基于Ehcache集群模式的存放方案:添加链接描述
9.2. 本地缓存
本地缓存的实现有几种方式:
(1)直接存放到JVM堆内存 (2)使用NIO存放在堆外内存,自定义实现或者借助于第三方缓存组件。
不论是采用集中式缓存还是使用本地缓存,shiro的权限数据本身都是直接存放在本地的,不同的是缓存标志的存放位置。采用本地缓存方案是,我们将缓存标志也存放在本地,这样就避免了查询缓存标志的网络请求,能更进一步提升缓存效率。
十、shiro修仙进阶
本篇对于shiro的学习到这就结束了 如果有不全或者不足的地方
|