1.基本使用
1.1环境准备
- shiro不依赖容器,直接创建maven工程即可
- 添加依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
1.2INI文件
- 创建ini文件
1.3登录认证
- 登录认证概念
- 身份认证:一般需要提供如身份ID等一些标识信息来表明登录者身份,如提供email,用户名密码来证明
- 在shiro中,用户需要提供principals和credentials给shiro,从而应用能验证用户身份
- principals:身份,即主体的标识属性,如用户名邮箱,唯一即可
- credentials:证明、凭证,即只有主体知道的安全值,如密码、数字证书等
- 最常见的principals和credentials组合就是用户名+密码
- 登录认证基本流程
- 收集用户信息,用户名密码
- 调用subject.login进行登录,如果失败将得到相应的AuthenticationException异常,根据异常提示用户,错误信息,否则登录成功
- 创建自定义的Realm类,继承org.apache.shiro.realm.AuthenticationRealm类实现doGetAuthenticationInfo()方法
- 登录认证实例
public class ShiroRun {
public static void main(String[] args) {
//初始化SecurityManager
IniSecurityManagerFactory iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
//从工厂中拿到管理器
SecurityManager instance = iniSecurityManagerFactory.getInstance();
SecurityUtils.setSecurityManager(instance);
//获取Subject对象
Subject subject = SecurityUtils.getSubject();
//创建token对象,web应用用户名密码从页面传递
AuthenticationToken usernamePasswordToken = new UsernamePasswordToken("zhangsan","z3");
//完成登录
try {
subject.login(usernamePasswordToken);
System.out.println("登录成功");
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}catch (AuthenticationException e){
e.printStackTrace();
System.out.println("登录失败");
}
}
}
1.4角色、授权
subject.login(usernamePasswordToken);
System.out.println("登录成功");
//判断角色
boolean hasRole = subject.hasRole("role1");
System.out.println("是否拥有此角色:"+hasRole);
//判断权限
boolean permitted = subject.isPermitted("user:insert");
System.out.println("是否拥有此权限:"+permitted);
//检查权限,没有返回值,如果有权限就执行下去,如果没有就抛异常
subject.checkPermission("user:insert111");
没有权限就抛出UnauthorizedException异常
1.5shiro加密
在实际系统开发中,一些敏感信息需要进行加密,比如说用户的密码,shiro内嵌了很多常用的加密算法,比如MD5加密,shiro可以很简单的使用信息加密
- 使用shiro进行密码加密
public class ShiroMD5 {
public static void main(String[] args) {
//密码明文
String password = "z3";
Md5Hash md5Hash = new Md5Hash(password);
System.out.println("MD5加密后的密文"+md5Hash.toHex());
//md5加盐,就是在明文后拼接新字符串,然后再加密
Md5Hash md5Hash2 = new Md5Hash(password,"salt");
System.out.println(md5Hash2);
//为了保证更安全,可以多次迭代加盐
Md5Hash md5Hash3 = new Md5Hash(password,"salt",3);
System.out.println(md5Hash3);
//使用父类进行加密
SimpleHash simpleHash = new SimpleHash("MD5",password,"salt",3);
System.out.println(simpleHash);
}
}
1.6shiro自定义登录认证
shiro默认的登录认证是不带加密的,如果要实现加密认证需要自定义登录认证,自定义realm
public class MyRealm extends AuthenticatingRealm {
//自定义登入认证方法,shiro的login方法底层会调用该类的认证方法进行认证
//需要配置自定义的Realm生效,在ini文件中配置。或者在springboot中进行配置
//该方法只是获取进行对比信息,认证逻辑还是按照shiro底层认证逻辑来完成
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取身份信息
String n = authenticationToken.getPrincipal().toString();
//获取凭证信息
String p = authenticationToken.getCredentials().toString();
System.out.println("认证用户信息:"+n+"----"+p);
//获取数据库中存储的用户信息
if (n.equals("zhangsan")){
//数据库中加盐迭代三次的密码
String pwdInfo = "7174f64b13022acd3c56e2781e098a5f";
//创建封装校验逻辑对象,封装返回即可
AuthenticationInfo info = new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),
pwdInfo,
ByteSource.Util.bytes("salt"),
authenticationToken.getPrincipal().toString()
);
}
return null;
}
}
2.与springboot整合
2.1导入坐标
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
2.2建表编写实体类
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(30) DEFAULT NULL COMMENT '用户名',
`pwd` varchar(50) DEFAULT NULL COMMENT '密码',
`rid` bigint DEFAULT NULL COMMENT '角色编号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3 COMMENT='用户表';
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private String pwd;
private Integer rid;
}
2.3MybatisPlus继承basemapper接口
@Repository
public interface UserMapper extends BaseMapper<User> {
}
2.4编写登录接口方法
public interface UserService {
//用户登录方法
User getUserInfoByName(String name);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserInfoByName(String name) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name",name);
User user = userMapper.selectOne(wrapper);
return user;
}
}
2.5自定义Realm自定义登录认证方法
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//自定义登录认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.获取用户身份信息
String name = authenticationToken.getPrincipal().toString();
//2.调用业务层获取用户信息(数据库中存储的信息)
User user = userService.getUserInfoByName(name);
//3.非空判断,将数据完成封装返回
if (user != null) {
AuthenticationInfo info = new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),
user.getPwd(),
ByteSource.Util.bytes("salt"),
authenticationToken.getPrincipal().toString()
);
return info;
}
return null;
}
//设置shiro内置过滤器的拦截范围
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();
//设置不认证可以访问的资源
defaultShiroFilterChainDefinition.addPathDefinition("/myController/userLogin","anon");
defaultShiroFilterChainDefinition.addPathDefinition("/login","anon");
//设置需要进行登录认证的拦截范围
defaultShiroFilterChainDefinition.addPathDefinition("/**","authc");
return defaultShiroFilterChainDefinition;
}
}
在这里AuthorizingRealm类为AuthenticatingRealm的子类,是对AuthenticatingRealm类的增强,realm其实就是自定义登录规则的一个过程,我们可以写很多个Realm,通过重写doGetAuthenticationInfo()方法实现不同形式的自定义登录方式。
- 第一个参数,有的人传的是userInfo对象对用的用户名。
- 第二个参数,传的是从数据库中获取的password,然后再与token中的password进行对比,匹配上了就通过,匹配不上就报异常。org.apache.shiro.authc.IncorrectCredentialsException
- 第三个参数,盐–用于加密密码对比,–获取的经验:为了防止两用户的初始密码是一样的:四个参数,防止两用户可能初始密码相同时候用,token 用simplehash(四个参数的构造) 加密默认用了MD5 迭代一次加密,info中在密码比对调用new SimpleHash(String algorithmName, Object source)这个实例化对象默认迭代一次了,所以当你用三个参数加密时候可能两个初始密码相同人的就没能区别开 (因此realm中密码要从数据库的查的原因),通过设置reaml 中credentialsMatcher 属性的各项属性可实现(后面会提及)
- 第四个参数:当前realm的名字。
2.6配置shiroConfig
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
//配置SecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
//创建对象
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//创建加密对象,设置相关属性
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//采用md5加密
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//迭代次数加密
hashedCredentialsMatcher.setHashIterations(3);
//加密对象存储到Myrealm当中
myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//存储Myrealm到manager对象当中
defaultWebSecurityManager.setRealm(myRealm);
//返回
return defaultWebSecurityManager;
}
}
在DefaultWebSecurityManager这个类当中,主要是定义了登录,创建subject,登出等操作,我们又知道subject是shiro安全框架的一个重要对象,登录操作需要他来进行调用。在这个类当中,我们需要把之前自定义的Realm对象传进去,并且把我们创建的加密对象放进Realm里面,登录等操作的时候所有的操作逻辑都会根据我们刚刚编写的逻辑走。
2.7编写Controller
@Controller
@RequestMapping("myController")
public class MyController {
@GetMapping("login")
public String login(){
return "login";
}
@GetMapping("/userLogin")
public String userLogin(String name, String pwd, HttpSession httpSession){
//获取subject对象
Subject subject = SecurityUtils.getSubject();
//封装请求数据到token
AuthenticationToken token = new UsernamePasswordToken(name,pwd);
//调用login方法进行登录认证
try{
subject.login(token);
httpSession.setAttribute("user",token.getPrincipal().toString());
return "main";
}catch (AuthenticationException exception){
exception.printStackTrace();
System.out.println("登录失败");
return "登录失败";
}
}
}
2.8shiro整合thymeleaf
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>登录后的主页面</title>
</head>
<body>
<h1>Shiro登录后的主页面</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>shiro登录认证</title>
</head>
<body>
<h1>Shiro登录认证</h1>
<br>
<form action="/myController/userLogin">
<div>用户名:<input type="text" name="name" value=""></div>
<div>密码:<input type="password" name="pwd" value=""></div>
<div><input type="submit" value="登录"></div>
</form>
</body>
</html>
2.9编写多个Realm的认证策略设置
认证策略的另一项工作就是聚合所有的Realm的结果信息封装到一个AuthenticationInfo实例中,并将此信息返回,以此作为Subject的身份信息
shiro中定义了三种认证策略的实现
- AtLeastOneSuccessfulStrategy:只要有一个(或更多)的Realm认证成功,那么则认证将视为成功
- FirstSuccessfulStrategy:第一个Realm成功,则视为整体成功,且后续Realm将忽略
- AllSuccessfullStrategy:所有Realm成功,认证才视为成功
2.10RememberMe功能实现
defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
public SimpleCookie rememberMeCookie(){
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//设置跨域
//simplecookie.setDomain(domain);
simpleCookie.setPath("/");
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(30*24*60*60);
return simpleCookie;
}
//创建Shiro的cookie对象
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());
return cookieRememberMeManager;
}
首先需要在defaultWebSecurityManager中添加cookie信息,然后在controller中添加访问携带cookie参数
2.11用户登录后登出
defaultShiroFilterChainDefinition.addPathDefinition("/logout","logout");
直接添加一个登出的过滤器即可
2.12授权、角色认证
- @RequireAuthentication:验证用户是否登录,等同于subject.isAuthenticated()
- @RequireUser:验证用户是否被记忆
- 登录认证成功subject.isAuthenticated()为true
- 登录后被记忆subject.isRemembered()为true
- @RequireGuest:验证是否是一个Guest请求,是否是游客的请求,此时subject.getPrincipal()为null
- @RequireRoles:验证是否有相应角色,有角色访问方法, 没有则会抛出异常AuthorizationException
- @RequirePermissions:验证subject是否有相应权限,有权限访问方法,没有则会抛出异常
3.EhCache缓存
- EhCache是一种开源的Java分布式缓存,主要面向通用缓存,javaEE和轻量级容器,可以和大部分java项目无缝整合,例如:hibernate的缓存就是基于EhCache实现的。
- EhCache支持内存和磁盘存储,默认存储在内存中,如果内存不够,会把缓存数据同步到磁盘中,EhCache支持Filter的Cache实现,也支持Gzip算法。
- EhCache直接在JVM虚拟机中缓存,速度快,效率高
- EhCache的缺点是缓存共享麻烦,集群分布式应用使用不方便