Shiro
学习目标
知识点 | 要求 |
---|---|
Shiro简介 | 掌握 |
Shiro架构原理 | 掌握 |
INI文件介绍 | 掌握 |
Shiro环境搭建及认证实现 | 掌握 |
第一个Shiro演示 | 掌握 |
授权 | 掌握 |
加密及凭证匹配器 | 掌握 |
自定义Realm | 掌握 |
凭证匹配器 | 掌握 |
Spring Boot整合Shiro实现登录认证 | 掌握 |
凭证匹配器 | 掌握 |
Remember Me实现 | 掌握 |
退出实现 | 掌握 |
授权 | 掌握 |
Thymeleaf整合shiro | 掌握 |
使用注解判断方法是否具有权限执行 | 掌握 |
EHCache | 掌握 |
Shiro和EHCache整合 | 掌握 |
Shiro中SessionManager管理 | 掌握 |
一、Shiro 简介
1 概述
权限体系在现代软件应用中有着非常重要的地位。一个应用如果没有权限体系都会显着这个系统“特别不安全”,无论是传统的MIS系统还是互联网项目出于对业务数据和应用自身的安全,都会设置自己的安全策略。
目前市场上专门的Java权限框架有Apache Shiro 和 Spring Security。相较于Spring Security 来说 Shiro更加老牌,所以就先讲解Shiro,在后面的阶段中讲解Spring Security。学习好Shiro对于以后市场上在出现新型权限框架的学习能带来很大便利。因为权限的概念是不变的,变得是框架的实现方式。当然了,对于第一次学习权限框架的人来说,相较于权限框架的应用,更难的就是权限方面的概念。
2 Shiro官方解释
中文:Apache Shiro是一个强大的并且简单使用的java权限框架.主要应用认证(Authentication),授权(Authorization),cryptography(加密),和Session Manager.Shiro具有简单易懂的API,使用Shiro可以快速并且简单的应用到任何应用中,无论是从最小的移动app到最大的企业级web应用都可以使用.
3 核心功能
在Shiro官网首页上占用了很大的篇幅说明了Shiro的核心功能。
Authentication 认证。如用户的登录。
Authorization 授权。用户是否有权限访问指定URL等。
Cryptography 密码学。如密码的加密。
Session Management Session 管理。
Web Integration Web集成。Shiro不依赖于容器。
二、Shiro 架构原理
必须会。Shiro学习的重中之重。
1 第一行
第一行中示例出了一些语言。无论是什么语言都需要包含Subject
2 Subject
主体。每个用户登录成功后都会对应一个Subject对象,所有用户信息都存放在Subject中。可以理解:Subject就是Shiro提供的用户实体类。
3 Security Manager
Shiro最大的容器,此容器中包含了Shiro的绝大多数功能。在非Spring Boot项目中,获取Security Manager 是编写代码的第一步。而在Spring Boot中已经帮助我们自动化配置了。
4 Authenticator
认证器。执行认证过程调用的组件。里面包含了认证策略。
5 Authorizer
授权器。执行授权时调用的组件。
6 Session Manager
Shiro被Web集成后,HttpSession对象会由Shiro的Session Manager进行管理。
7 Cache Manager
缓存管理。Shiro执行很多第三方缓存技术。例如:EHCache等。
8 Session DAO
操作Session内容的组件。
9 Realms
Shiro框架实现权限控制不依赖于数据库,通过内置数据也可以实现权限控制。但是目前绝大多数应用的数据都存储在数据库中,所以Shiro提供了Realms组件,此组件的作用就是访问数据库。Shiro内置的访问数据库的代码,通过简单配置就可以访问数据库,也可以自定义Realms实现访问数据库逻辑(绝大多数都这么做)
三、INI文件介绍
INI英文名称(InitializationFile)
INI文件是Window系统配置文件的扩展名.
Shiro的全局配置文件就是.ini文件,ini中数据都是固定数据,后面会用数据库中数据替代下面users和roles(固定数据部分)
.ini文件内容的语法和.properties类似都是key=value,value格式.
INI文件中包含了四个部分:
1 [main] 主体部分.
这部分配置类对象,或设置属性等操作.
内置了根对象:securityManager,注意对象名大小写。
[main]
securityManager.属性=值
key=value
securityManager.对象属性=com.bjsxt.pojo.People #后面值是字符串 peo=com.bjsxt.pojo.People
securityManager.对象属性=$peo #出现$时才表示是引用对象
2 [users]
定义用户,密码及用户可以具有的角色.
[users]
用户名=密码,角色1,角色2 #角色部分可以省略.
zhangsan=123 Sxt=123,role1,role2
3 [roles]
定于角色具有的权限
[roles]
角色名=权限名,权限名
role1=user:insert,user:update
role2=insert,update,delete,select
role3=user:*
4 [urls]
定义哪个控制器被哪个过滤器过滤.Shiro内置很多过滤器。此部分主要在WEB应用中使用。
下图是官网上提供的Shiro内置Filter及名称。
anon:不认证也可以访问。例如:/admin/**=anon /login=anon
authc:必须认证。 /**=authc —所有的资源都认证
authcBasic:没有参数时表示httpBasic认证(客户端认证方式)。
logout:退出。
noSessionCreation:新增Filter,表示没有Session创建。
perms:判断是有具有指定权限。例如:/admin/user/**=perms[“per1”,”per2”]。必须同时具有给定权限才可以访问。如果只有一个权限可以省略双引号。
port:限制端口。例如:/admin/**=port[8081]。只要请求不是8081端口就重新发送URL到8081端口。
rest:请求方式和权限的简便写法。例如:/admin/=rest[user],相当于/admin/ = perms[user:方式],方式是http请求的方式:post、get等。
roles:判断是否具有指定角色。/admin/**=roles[role1]
ssl:表示是安全的请求。协议为https
user:表示必须存在用户。
[urls] 控制器名称=过滤器名称 /login=authc /**=anon
四、Shiro环境搭建及认证实现
1 认证流程
获取主体,通过主体Subject对象的login方法进行登录
把Subject中内容传递给Security Manager
Security Manager内部组件Authenticator进行认证,
认证数据使用InI Realm,调用Ini文件中数据.
2 名词解释
Principal: 身份。用户名,邮箱,手机等能够唯一确认身份的信息.
Credential: 凭证,代表密码等。
AuthenticationInfo:认证时存储认证信息。
五、 第一个Shiro演示
Shiro是不依赖于容器的,所以建立一个普通的Maven项目就可以。
1 添加依赖
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
2 新建配置文件
在resources下新建shiro.ini,文件名称任意,扩展名必须是ini。
[users] zhangsan=zs lisi=ls
3 新建测试类
新建com.bjsxt.ShiroRun。
public class ShiroRun {
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
//web项目时,用户名和密码是客户端表单传递过来的用户名和密码。
AuthenticationToken token = new UsernamePasswordToken("admin", "pwd");
try {
//login()方法没有返回值,只能通过是否有异常判断是否登录成功。
subject.login(token);
System.out.println("登录成功");
} catch (UnknownAccountException e) {
System.out.println("账号不存在");
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
} catch (AuthenticationException e) {
e.printStackTrace();
}
}
}
六、授权
授权:代表的是判断认证成功的用户是否具有某一个权限或者角色
1 判断角色
1.1 修改配置文件
直接在密码后面添加用户包含的角色名即可。
[users]
zhangsan=zs,role1,role2
lisi=ls
1.2 添加代码
hasRole()通过返回值判断用户是否具有指定角色。
boolean hasRoleResult = SecurityUtils.getSubject().hasRole("role1");
System.out.println("result:"+hasRoleResult);
2 判断权限
2.1 修改配置文件
[users]
zhangsan=zs,role1,role2
lisi=ls
[roles]
role1=permission1,permission2
2.2 添加测试代码
通过是否出现AuthenticationException异常控制是否有指定权限。
SecurityUtils.getSubject().checkPermission(“permission”);
boolean ii = subject.isPermitted(“update”);
七、加密及凭证匹配器
在实际开发中数据库中一些敏感信息经常会被加密存储。如:用户密码等。
Shiro框架内嵌了很多加密算法。如MD5等。使用Shiro框架时可以很方便的实现加密功能。
String password = "smallming";
//md5加密
Md5Hash md5Hash = new Md5Hash(password);
System.out.println(md5Hash.toHex());
//带盐的MD5加密。盐就是在原有字符串后面拼接盐形成新的字符串,然后加密。
Md5Hash md5Hash2 = new Md5Hash(password, "bjsxt");
System.out.println(md5Hash2);
//无论是否加盐都可以很容易的被破解,可以多次迭代加密保证数据安全性。
//第三个参数表示迭代加密次数
Md5Hash md5Hash3 = new Md5Hash(password, "bjsxt", 2);
System.out.println(md5Hash3);
//使用Md5的父类也也实现
SimpleHash simpleHash = new SimpleHash("md5",password,"bjsxt",2);
System.out.println(simpleHash);
八、自定义Realm
ShiroRun 代码不需要做任何变化。
1 自定义Realm类
在项目中新建com.bjsxt.realm.MyRealm
public class MyRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行认证");
//用户名
String username = token.getPrincipal().toString();
//密码
String pwd = new String((char[])token.getCredentials());
System.out.println(username+" "+pwd);
//先从数据库查询select * from user where username=? 查看用户名是否存在
if(username.equals("admin")){//假设用户名为admin时能从数据库中查询出来
//根据之前查询出来的用户信息获取密码。假设查询出来的密码是pwd
String password= "pwd";
//此处需要注意,第二个参数是从数据库查询出来的密码,而不是传递过来的密码。
//第三个参数自定义。但是尽量不重复了。常直接使用用户名当做realname名字。
AuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(),password,"realmname");
//shiro会判断从数据库查询出来的密码和客户端传递过来的密码是否一致。
return info;
}
//返回null说明用户名不存在。
return null;
}
}
2 修改配置文件
在配置文件中添加下面内容
[main]
myrealm=com.bjsxt.realm.MyRealm
securityManager.realms=$myrealm
3 测试结果
在ShiroRun中修改用户名和密码,当用户名和密码为admin、pwd时可以正常登录。
此时无论shiro.ini是否配置了[users],都按照Realm中配置的逻辑进行比较用户名和密码。
九、凭证匹配器
在实际应用中数据库密码都是加密的。Shiro内置了凭证匹配器,通过简单配置就可以实现明文数据和数据库中加密数据匹配的效果。
1 修改自定义Realm
AuthenticationInfo 的构造方法由三个参数变成四个参数的。新增第三个参数表示加盐。
一般都是拿用户数据的id作为盐。
//密码必须是MD5加密后的密码
String password= "7614fd642608ca0755b78d2b2c352e19";
//假设id是从数据库中取出的id
Long id = 123L;
//第三个参数是加密的盐
AuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(), password, ByteSource.Util.bytes(id+"") ,token.getPrincipal().toString());
2 修改配置文件
在配置文件中配置凭证匹配器配置。
[main] md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=2 myrealm=com.bjsxt.shiro.MyRealm myrealm.credentialsMatcher=$md5CredentialsMatcher securityManager.realms=$myrealm
十、 Spring Boot整合Shiro实现登录认证
整体项目结构。省略数据访问层相关代码,使用固定数据进行认证。
1 添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.13.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.2</version>
</dependency>
<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.1</version>
</dependency>
<!--数据库的驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
</dependencies>
2 编写配置文件
新建application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/rbac1?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
shiro:
loginUrl: /login
3 新建实体类
新建com.bjsxt.pojo.User
public class User {
private Long id;
private String username;
private String password;
// 省略getter和setter
// 省略构造方法
}
3 编写业务层代码
新建com.bjsxt.service.UserService及实现类
public interface AdminService {
Admin findOne(String uname);
}
@Service
public class AdminServiceImpl implements AdminService {
@Autowired
private AdminMapper adminMapper;
@Override
public Admin findOne(String uname) {
return adminMapper.selectOne(uname);
}
}
4 自定义Realm
新建com.bjsxt.realm.MyRealm编写认证逻辑
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private AdminService adminService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String name = authenticationToken.getPrincipal().toString();
Admin admin = adminService.findOne(name);
if(admin!=null){
AuthenticationInfo info=new SimpleAuthenticationInfo(name,admin.getPwd(), ByteSource.Util.bytes(admin.getSalt()),"sxt");
return info;
}
return null;
}}
5 编写配置
新建com.bjsxt.config.ShiroConfig,编写配置
@Configuration
public class SApplicationConfig {
@Autowired
private MyRealm myRealm;
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(3);
myRealm.setCredentialsMatcher(matcher);
defaultWebSecurityManager.setRealm(myRealm);
return defaultWebSecurityManager;
}
@Bean
public shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
definition.addPathDefinition("/doLogin", "anon");
definition.addPathDefinition("/login", "anon");
definition.addPathDefinition("/**", "authc");
return definition;
}
}
6 编写控制器
新建com.bjsxt.controller.UserController
@Controller
public class AdminController {
@RequestMapping("/{path}")
public String getUrl(@PathVariable String path){
System.out.println("iii");
return path;
}
@RequestMapping("/doLogin")
public String login(String uname,String pwd){
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(uname,pwd));
return "main" ;
} catch (AuthenticationException e) {
System.out.println("失败");
return "login";
}
}
}
7 编写启动类
新建com.bjsxt.ShiroApplication
@SpringBootApplication
@MapperScan("com.bjsxt.mapper")
public class ShiroApplication {
public static void main(String[] args) {
SpringApplication.run(ShiroApplication.class,args);
}
}
8 编写页面
8.1 编写登录页面
新建templates/login.html
<form action="/doLogin" method="post">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password" /><br/>
<input type="submit" value="登录"/>
</form>
8.2 编写主页面
新建templates/login.html。页面中虽然编写内容
<body>
index.html
</body>
十一、凭证匹配器
1 修改ShiroConfig
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(2);
myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
manager.setRealm(myRealm);
return manager;
}
2 修改UserServiceImpl
模拟从数据库查询出来的密码是加密的密码。
此加密数据是”admin”加盐”123”迭代两次加密的结果。
return new User(1L,"admin","6bdae6366c1e46d541eb0ca9547d974c");
3 修改MyRealm
修改MyRealm中doGetAuthenticationInfo方法。把盐添加进去。
AuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(),user.getPassword(), ByteSource.Util.bytes("123"),user.getUsername());
十二、多个Realm认证策略
1 简介
当一个应用程序配置了两个或两个以上的Realm 时,ModularRealmAuthenticator 依靠内部的AuthenticationStrategy 组件来判定认证的成功或失败。
AuthenticationStrategy是一个无状态的组件,它在身份验证尝试中被询问4 次(这4 次交互所需的任何必要的状态将被作为方法参数):
-
在任何Realm 被调用之前被询问;
-
在一个单独的Realm 的getAuthenticationInfo 方法被调用之前立即被询问;
-
在所有的Realm 被调用后询问。
认证策略的另外一项工作就是聚合所有Realm的结果信息封装至一个AuthenticationInfo实例中,并将此信息返回,以此作为Subject的身份信息。
Shiro中定义了3种认证策略的实现:
ModularRealmAuthenticator内置的认证策略默认实现是AtLeastOneSuccessfulStrategy 方式,因为这种方式也是被广泛使用的一种认证策略。当然,你也可以通过配置文件定义你需要的策略
2 代码实现
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
ModularRealmAuthenticator modularRealmAuthenticator=new ModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
securityManager.setAuthenticator(modularRealmAuthenticator);
List<Realm> list=new ArrayList<>();
list.add(myRealm);
list.add(myRealm2);
securityManager.setRealms(list);
return securityManager;
}
十三、remember me实现
1 修改ShiroConfig
秘钥长度为16位,使用的时AES加密。
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(2);
myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
manager.setRealm(myRealm);
manager.setRememberMeManager(rememberMeManager());
return manager;
}
/**
* cookie 属性设置
*/
public SimpleCookie rememberMeCookie()
{
SimpleCookie cookie = new SimpleCookie("rememberMe");
// cookie.setDomain(domain);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(30 * 24 * 60 * 60);
return cookie;
}
/**
* 记住我
*/
public CookieRememberMeManager rememberMeManager()
{
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(“1234567890987654”);
return cookieRememberMeManager;
}
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
definition.addPathDefinition("/doLogin", "anon");
definition.addPathDefinition("/logout", "logout");
definition.addPathDefinition("/**", "user");
return definition;
}
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
2 修改控制器方法
修改控制doLogin方法,在UsernamePasswordToken中添加第三个参数。表示是否启用remember me功能。
此功能应该在页面中添加一个复选框,代码中直接假设用户勾选了复选框
@RequestMapping("/doLogin")
public String showLogin(User user){
Subject subject = SecurityUtils.getSubject();
try {
//添加第三个参数,表示是否启用rememberme功能
subject.login(new UsernamePasswordToken(user.getUsername(),user.getPassword(),true));
return "redirect:/showIndex";
} catch (Exception e) {
e.printStackTrace();
}
return "redirect:/showLogin";
}
十四、退出实现
1 修改配置类
修改ShiroConfig类,添加logout filter 对应的url。
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
definition.addPathDefinition("/doLogin", "anon");
definition.addPathDefinition("/logout", "logout");
definition.addPathDefinition("/**", "authc");
return definition;
}
2 修改主页面
在index.html页面中添加超链接。跳转到/logout后会由shiro内置filter进行拦截。
<body>
index.html
<a href="/logout">退出</a>
</body>
十五、授权
授权就是判断认证用户是否具有指定角色或指定权限。
Shiro可以和JSP整合也可以和Thymeleaf整合,我们讲解SpringBoot的视图技术Thymeleaf整合Shiro。
只要是授权就执行Realm的doGetAuthorizationInfo进行判断,而触发doGetAuthorizationInfo的方式,常用的就两种:
(1)在页面中通过shiro:xxxx 属性进行判断
(2)在java代码中通过注解@RequiresXXX/config文件中配置
1、使用注解判断方法是否具有权限执行
方法:可以用控制器方法,也可以是业务方法。常在控制器方法上添加注解进行判断。
常用注解:
@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated() 结果为true时。
@RequiresUser
验证用户是否被记忆,user有两种含义:
一种是成功登录的(subject.isAuthenticated() 结果为true);
另外一种是被记忆的(subject.isRemembered()结果为true)。
@RequiresGuest
验证是否是一个guest的请求,与@RequiresUser完全相反。
换言之,RequiresUser == !RequiresGuest。
此时subject.getPrincipal() 结果为null.也就是所谓的游客
@RequiresRoles
例如:@RequiresRoles(“aRoleName”);
void someMethod();
如果subject中有aRoleName角色才可以访问方法someMethod。如果没有这个权限则会抛出异常AuthorizationException。
@RequiresPermissions
例如: @RequiresPermissions({“file:read”, “write:aFile.txt”} )
void someMethod();
要求subject中必须同时含有file:read和write:aFile.txt的权限才能执行方法someMethod()。否则抛出异常AuthorizationException
页面显示问题处理
当用户登录成后,点击超链接“跳转到判断权限的”超链接会出现500错误页面信息。
新建依赖处理类
新建com.bjsxt.controller.NoPermissionException
@ControllerAdvice
public class NoPermissionException {
@ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public String handleShiroException(Exception ex) {
return "无权限";
}
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public String AuthorizationException(Exception ex) {
return "权限认证失败";
}
}
2、Thymeleaf整合shiro
2.1 添加依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2.2 修改index.html
访问页面后会发现“有角色”不显示,“有权限”显示。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
index.html
<a href="/logout">退出</a>
<span shiro:hasRole="role3">有角色</span>
<span shiro:hasPermission="permission1">有权限</span>
</body>
2.3 修改配置类
在配置类中com.bjsxt.config.ShiroConfig中添加。
负责解析thymeleaf中shiro:相关属性。
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
2.4 thymeleaf中常用属性
shiro:user=””认证通过或已记住的用户。
shiro:authenticated认证通过的用户。不包含记住的用户。
shiro:principal输出认证用户信息。shiro:principal/
shiro:hasRole="admin"判断是否具有指定角色。
shiro:hasAllRoles="role1,role2"判断指定角色用户是否都具有。
shiro:hasAnyRoles="role1,role2"只要用户具有其中一个角色就表示判断通过。
shiro:hasPermission="userInfo:add"是否具有指定权限。
shiro:lacksPermission="userInfo:del"是否不具有指定权限
shiro:hasAllPermissions="userInfo:view, userInfo:add"是否全具有指定权限。
shiro:hasAnyPermissions="userInfo:view, userInfo:del"只要有其中任何一个权限即可。
十六、EHCache
1 ehcache简介
EHCache是sourceforge的开源缓存项目,现已经具有独立官网,网址:(http://www.ehcache.org)。其本身是纯JAVA实现的,所以可以和绝大多数Java项目无缝整合,例如:Hibernate的缓存就是基于EHCache实现的。
EHCache支持内存和磁盘缓存,默认是存储在内存中的,当内存不够时允许把缓存数据同步到磁盘中,所以不需要担心内存不够问题。
EHCache支持基于Filter的Cache实现,同时也支持Gzip压缩算法提高响应速度。
ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。
2 EHCache API演示
2.1 添加依赖
从3.0版本开始groupid为org.ehcache
<dependencies>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.6.11</version>
</dependency>
</dependencies>
2.2 新建配置文件
在src/main/resources中新建ehcache.xml。
属性含义:
maxElementsInMemory:缓存中允许创建的最大对象数。
eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。
timeToIdleSeconds:缓存数据的钝化时间,取值0表示无限长。
timeToLiveSeconds:缓存数据的生存时间,取值0表示无限长。
overflowToDisk:内存不足时,是否启用磁盘缓存。
memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir/ehcache"/>
<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<!-- helloworld缓存 -->
<cache name="HelloWorldCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="5"
timeToLiveSeconds="5"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
public static void main(String[] args) {
/* String property = System.getProperty("java.io.tmpdir");
System.out.println(property);*/
CacheManager cacheManager=CacheManager.create("D:\\SXTCourse\\Testshiro\\uu\\src\\main\\resources\\each.xml");
Cache cache = cacheManager.getCache("HelloWorldCache");
Element element=new Element("key","sxt");
cache.put(element);
Element element1 = cache.get("key");
System.out.println(element1);
}
十七、Shiro和EhCache整合
Shiro支持很多第三方缓存工具。官方提供了shiro-ehcache,实现了把EHCache当做Shiro的缓存工具的解决方案。其中最好用的一个功能是就是缓存认证执行的Realm方法,减少对数据库的访问。
1 添加依赖
添加shiro-ehcache依赖。
commons-io主要是为了使用里面的工具类。本质和当前整合功能没有关系。
<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>
2 编写ehcache缓存配置
在resources下新建ehcache/ehcache-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="ehcache" updateCheck="false">
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir"/>
<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="1000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="false">
</defaultCache>
<!-- 登录记录缓存 锁定10分钟 -->
<cache name="loginRecordCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
</ehcache>
3 修改配置文件
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(2);
myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
manager.setRealm(myRealm);
manager.setRememberMeManager(rememberMeManager());
manager.setCacheManager(getEhCacheManager());
return manager;
}
public EhCacheManager ehCacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
InputStream is = null;
try {
is = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml");
} catch (IOException e) {
e.printStackTrace();
}
net.sf.ehcache.CacheManager cacheManager = new net.sf.ehcache.CacheManager(is);
ehCacheManager.setCacheManager(cacheManager);
return ehCacheManager;
}
十八、Shiro中sessionManager
1、sessionManager的简介
在shiro里所有的用户的会话信息都会由Shiro来进行控制,shiro提供的会话可以用于JavaSE/JavaEE环境,不依赖 于任何底层容器,可以独立使用,是完整的会话模块。通过Shiro的会话管理器(SessionManager)进行统一的 会话管理
SessionManager(会话管理器):管理所有Subject的session包括创建、维护、删除、失效、验证等工作。
SessionManager是顶层组件,由SecurityManager管理
shiro提供了三个默认实现:
-
DefaultSessionManager:用于JavaSE环境
-
ServletContainerSessionManager:用于Web环境,直接使用servlet容器的会话。
-
DefaultWebSessionManager:用于web环境,自己维护会话(自己维护着会话,直接废弃了Servlet容器的会话管理)
//登录成功后,打印所有session内容
@RequestMapping(value="/show")
public String show(HttpSession session) {
// 获取session中所有的键值
Enumeration<?> enumeration = session.getAttributeNames();
// 遍历enumeration中的
while (enumeration.hasMoreElements()) {
// 获取session键值
String name = enumeration.nextElement().toString();
// 根据键值取session中的值
Object value = session.getAttribute(name);
// 打印结果
System.out.println("---" + name + "---" + value + "---");
}
return "session";
2理解示意图
3 获得session 方式
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("key","value");
结论:
在shiro使用过程中,我们是可以调到httpSession对象的,调用方式即为shiro获取session的方法。
(1)controller中的request,在shiro过滤器中的doFilterInternal方法,将被包装为ShiroHttpServletRequest 。
(2)在controller中,通过 request.getSession(_) 获取会话 session ,该session到底来源servletRequest 还是由shiro管理并管理创建的会话,主要由 安全管理器 SecurityManager 和 SessionManager 会话管理器决定。
(3)不管是通过 request.getSession或者subject.getSession获取到session,操作session,两者都是等价的。在使用默认session管理器的情况下,操作session都是等价于操作HttpSession。