一、初识shiro框架
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
一、主要功能
三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果系统默认的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
基本功能点
- Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
- Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
- Web Support:Web 支持,可以非常容易的集成到 Web 环境;
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
- Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
- Testing:提供测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
特点
- 易于理解的 Java Security API
- 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等)
- 对角色的简单的签权(访问控制),支持细粒度的签权
- 支持一级缓存,以提升应用程序的性能
- 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境
- 异构客户端会话访问
- 非常简单的加密 API
- 不跟任何的框架或者容器捆绑,可以独立运行
二、基本使用
1、环境准备
1、Shiro不依赖容器,创建maven工程 / springboot项目都可
2、添加shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
2、INI 文件
Shiro 获取权限相关信息可以通过数据库获取,也可以通过 ini 配置文件获取
[users]
zhangsan=z3
lisi=l4
3、登录认证
- 身份验证:一般需要提供如身份ID等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明。
- 在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,从 而应用能验证用户身份。
- principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一 即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/ 邮箱/手机号。
- credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
- 最常见的principals和credentials组合就是用户名/密码
登录认证基本流程
- 收集用户身份/凭证,即如用户名/密码
- 调用 Subject.login 进行登录,如果失败将得到相应 的 AuthenticationException 异常,根据异常提示用户 错误信息;否则登录成功
- 创建自定义的 Realm 类,继承 org.apache.shiro.realm.AuthenticatingRealm类, 实现 doGetAuthenticationInfo() 方法
登录认证实例
创建测试类,获取认证对象,进行登录认证,如下:
package com.syh.shiro.text;
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.subject.Subject;
public class Role{
public static void main(String[] args) {
//1 初始化获取 SecurityManager
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:static/shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//2 获取 Subject 对象
Subject subject = SecurityUtils.getSubject();
//3 创建 token 对象,web 应用用户名密码从页面传递
AuthenticationToken token = new UsernamePasswordToken("zhangsan","z3");
//4 完成登录
try {
subject.login(token);
System.out.println("登录成功");
}
catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户不存在");
}
catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
}
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
}
身份认证流程
- 首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager
- SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份 验证;
- Authenticator 才是真正的身份验证者,Shiro API 中核心的身份 认证入口点,此 处可以自定义插入自己的实现;
- Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份 验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
- Authenticator 会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息,如 果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序 及策略进行访问。
4、角色、授权
授权概念
- 授权:也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面 操作 等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权 限 (Permission)、角色(Role)。
- 主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只 有授权 后才允许访问相应的资源。
- 资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑 某些 数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
- 权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中 用户 有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访 问用 户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权 限控 制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允 不允 许。
- Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权 限, 即实例级别的)
- 角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可 以拥有 一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工 程师等 都是角色,不同的角色拥有一组不同的权限
授权方式
(1)编程式:通过写if/else 授权代码块完成
(2)注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相 应的异 常
授权流程
- 首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而 SecurityManager接着会委托给 Authorizer;
- Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通 过PermissionResolver把字符串转换成相应的Permission实例;
- 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入 的角色/权限;
- Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托 给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole* 会返回 true,否则返回false表示授权失败
授权实例
1、获取角色信息
(1)给shiro.ini增加角色配置
[users]
zhangsan=z3,role1,role2
lisi=l4
(2)上述示例代码中添加代码,沟通过hasRole()判断用户是否有指定角色
try {
subject.login(token);
System.out.println("登录成功");
// 沟通过hasRole()判断用户是否有指定角色
boolean role1 = subject.hasRole("role1");
System.out.println("是否拥有此角色:" + role1);
}
2、判断权限信息信息
(1)给shiro.ini增加权限配置
[roles]
role1=user:insert,user:select
(2)给例子添加代码,判断用户是否有指定权限
//判断权限信息信息
boolean isPermitted = subject.isPermitted("user:insert");
System.out.println("是否拥有此权限:"+isPermitted);
//也可以用 checkPermission 方法,但没有返回值,没权限抛 AuthenticationException
subject.checkPermission("user:select");
5、Shiro 加密
实际系统开发中,一些敏感信息需要进行加密,比如说用户的密码。Shiro 内嵌很多 常用的加密算法,比如 MD5 加密。Shiro 可以很简单的使用信息加密。
package com.syh.shiro.text;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
public class ShiroJM {
public static void main(String[] args) {
//密码明文
String password = "z3";
//使用 md5 加密
Md5Hash md5Hash = new Md5Hash(password);
System.out.println("md5 加密:"+md5Hash.toHex());
//带盐的 md5 加密,盐就是在密码明文后拼接新字符串,然后再进行加密
Md5Hash md5Hash2 = new Md5Hash(password,"salt");
System.out.println("md5 带盐加密:"+md5Hash2.toHex());
//为了保证安全,避免被破解还可以多次迭代加密,保证数据安全
Md5Hash md5Hash3 = new Md5Hash(password,"salt",3);
System.out.println("md5 带盐三次加密:"+md5Hash3.toHex());
//使用父类实现加密
SimpleHash simpleHash = new SimpleHash("MD5",password,"salt",3);
System.out.println("父类带盐三次加密:"+simpleHash.toHex());
}
}
6、Shiro 自定义登录认证
Shiro 默认的登录认证是不带加密的,如果想要实现加密认证需要自定义登录认证, 自定义 Realm。
(1)自定义登录认证
package com.syh.shiro.text;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
import org.springframework.stereotype.Component;
@Component
public class MyRealm extends AuthenticatingRealm {
//自定义的登录认证方法,Shiro 的 login 方法底层会调用该类的认证方法完成登录认证
//需要配置自定义的 realm 生效,在 ini 文件中配置,或 Springboot 中配置
//该方法只是获取进行对比的信息,认证逻辑还是按照 Shiro 的底层认证逻辑完成认证
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authenticationToken) throws
AuthenticationException {
//1 获取身份信息
String principal = authenticationToken.getPrincipal().toString();
//2 获取凭证信息
String password = new String((char[])
authenticationToken.getCredentials());
System.out.println("认证用户信息:"+principal+"---"+password);
//3 获取数据库中存储的用户信息
if(principal.equals("zhangsan")){
//3.1 数据库存储的加盐迭代 3 次密码
String pwdInfo = "7174f64b13022acd3c56e2781e098a5f";
//3.2 创建封装了校验逻辑的对象,将要比较的数据给该对象
AuthenticationInfo info = new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),
pwdInfo,
ByteSource.Util.bytes("salt"),
authenticationToken.getPrincipal().toString());
return info;
}
return null;
}
}
(2)在shiro.ini中添加配置信息
配置信息中有些为自定义登录认证配置类的路径信息
[main]
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3
myrealm=com.syh.shiro.text.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm
[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi=l4
[roles]
role1=user:insert,user:select
(3)启动登录认证示例
三、Spring Boot 整合 shiro
1、创建springboot项目
(1)添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mp-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
</dependencies>
(2)目录结构
2、登录认证实现
访问数据库获取用户信息,实现登录认证.
(1)数据库
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
`pwd` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`r_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('11', 'zhangsan', '7174f64b13022acd3c56e2781e098a5f', '1111');
SET FOREIGN_KEY_CHECKS = 1;
(2)实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {
@TableId("id")
private Integer id;
@TableField("name")
private String name;
@TableField("pwd")
private String pwd;
@TableField("rid")
private Integer rid;
}
(3)service业务
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
/* 根据用户名称获取用户信息 */
@Override
public User getUserInfoByName(String name) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, name);
return userMapper.selectOne(wrapper);
}
}
(4)自定义realm
/*
* 自定义认证授权方法
* */
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//自定义登录认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1 获取用户身份信息
String name = token.getPrincipal().toString();
//2 调用业务层获取用户信息(数据库中)
User user = userService.getUserInfoByName(name);
//3 判断并将数据完成封装
if(user!=null){
AuthenticationInfo info = new SimpleAuthenticationInfo(
token.getPrincipal(),
user.getPwd(),
ByteSource.Util.bytes("salt"),
token.getPrincipal().toString()
);
return info;
}
return null;
}
}
(5)编写配置类
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
//配置 SecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
//1 创建 defaultWebSecurityManager 对象
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2 创建加密对象,并设置相关属性
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//2.1 采用 md5 加密
matcher.setHashAlgorithmName("md5");
//2.2 迭代加密次数
matcher.setHashIterations(3);
//3 将加密对象存储到 myRealm 中
myRealm.setCredentialsMatcher(matcher);
//4 将 myRealm 存入 defaultWebSecurityManager 对象
defaultWebSecurityManager.setRealm(myRealm);
//5 返回
return defaultWebSecurityManager;
}
// 过滤器
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 拦截配置
Map<String, String> filter = new LinkedHashMap<>();
// 登录接口放行
filter.put("/api/UserController/userLogin", "anon");
// 需要拦截的资源
filter.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filter);
return shiroFilter;
}
/*
* 以后会用到的几个:
* anon 无需认证即可访问
* logout 退出清除HTTPSession数据
* authc 需要认证才可访问
* user user是介于,anon和authc直之间的。
* 换句话来说:而“/authenticated= user”
* 表示访问该地址的用户是身份验证通过或RememberMe 登录的都可以。
* */
}
(6)实现登录接口
@RestController
@RequestMapping("/api/UserController")
@Slf4j
public class UserController {
// http://localhost:8080/api/UserController/userLogin?name=zhangsan&pwd=z3
@GetMapping("/userLogin")
public String userLogin(String name, String pwd){
//1 获取 Subject 对象
Subject subject = SecurityUtils.getSubject();
//2 封装请求数据到 token 对象中
AuthenticationToken token = new UsernamePasswordToken(name, pwd);
//3 调用 login 方法进行登录认证
try {
subject.login(token);
return "登录成功";
} catch (AuthenticationException e) {
log.error("登录失败");
return "登录失败";
}
}
}
3、多个 realm 的认证策略设置
多个realm实现原理
多Realm验证,有时候会存在多Realm,不同的角色会有不同的验证逻辑,这个时候会需要多Realm。Shiro 的 ModularRealmAuthenticator 会使用内部的 AuthenticationStrategy 组件判断认 证是成功还是失败。
AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这 4 次交互所需的任何必要的状态将被作为方法参数):
- 在所有 Realm 被调用之前
- 在调用 Realm 的 getAuthenticationInfo 方法之前
- 在调用 Realm 的 getAuthenticationInfo 方法之后
- 在所有 Realm 被调用之后
认证策略的另外一项工作就是聚合所有 Realm 的结果信息封装至一个 AuthenticationInfo 实例中,并将此信息返回,以此作为 Subject 的身份信息。
Shiro 中定义了 3 种认证odularRealmAuthenticat策略的实现,Mor 内置的认证策略默认实现是 AtLeastOneSuccessfulStrategy 方式。可以通过配置修改策略。
AuthenticationStrategy class
|
描述
|
AtLeastOneSuccessfulStrategy
|
只要有一个(或更多)的
Realm
验证成功,那么认证将视为成功
|
FirstSuccessfulStrategy
|
第一个
Realm
验证成功,整体认证将视为成功,且后续
Realm
将被忽略
|
AllSuccessfulStrategy
|
所有
Realm
成功,认证才视为成功
|
多个realm代码实现
标题2已经配置一个realm名字为MyRealm,下面继续创建Customer2Realm
@Component
public class Customer2Realm extends AuthorizingRealm {
@Autowired
private UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// userName
String principal = (String) authenticationToken.getPrincipal();
// password
// String credentials = (String) authenticationToken.getCredentials();
// 根据用户名获取用户信息
User user = userService.getUserInfoByName("zhangsan1");
if(user != null){
System.out.println("app认证成功");
return new SimpleAuthenticationInfo(user, user.getPwd(), ByteSource.Util.bytes("salt"), principal);
}else{
return null;
}
}
}
配置 SecurityManager,添加Customer2Realm(新添加3、5、5.1步)
// 认证授权方法一
@Autowired
private MyRealm myRealm;
// 认证授权方法二
@Autowired
private Customer2Realm customer2Realm;
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {
//1 创建 defaultWebSecurityManager 对象
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2 创建认证对象,并设置认证策略
ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
//3 只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功 默认值
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);
//3 创建加密对象,并设置相关属性
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//3.1 采用 md5 加密
matcher.setHashAlgorithmName("md5");
//3.2 迭代加密次数
matcher.setHashIterations(3);
//4 将加密对象存储到 myRealm 中
myRealm.setCredentialsMatcher(matcher);
//5 封装 myRealm 集合
List<Realm> list = new ArrayList<>();
list.add(myRealm);
//5.1 封装customer2Realm
list.add(customer2Realm);
//6 将 myRealm 存入 defaultWebSecurityManager 对象
defaultWebSecurityManager.setRealms(list);
//6.5 设置 rememberMe(Shiro 提供了记住我(RememberMe)的功能)
defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
//7 返回
return defaultWebSecurityManager;
}
4、remember me(记住我功能)
Shiro 提供了记住我(RememberMe)的功能,比如访问一些网站时,关闭了浏览器, 下次再打开时还是能记住你是谁, 下次访问时无需再登录即可访问。
基本流程
(1) 首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会 把 RememberMe 的 Cookie 写到客户端并保存下来;
(2) 关闭浏览器再重新打开;会发现浏览器还是记住你的;
(3) 访问一般的网页服务器端,仍然知道你是谁,且能正常访问;
(4) 但是,如果我们访问电商平台时,如果要查看我的订单或进行支付时,此时还 是需要再进行身份认证的,以确保当前用户还是你。
代码实现
(1)修改配置类 ShiroConfig,在defaultWebSecurityManager方法放的6步后面添加代码,并且添加两个方法;在shiroFilterFactoryBean方法中添加过滤条件
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
//配置 SecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {
//1 创建 defaultWebSecurityManager 对象
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2 创建认证对象,并设置认证策略
ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);
//3 创建加密对象,并设置相关属性
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//3.1 采用 md5 加密
matcher.setHashAlgorithmName("md5");
//3.2 迭代加密次数
matcher.setHashIterations(3);
//4 将加密对象存储到 myRealm 中
myRealm.setCredentialsMatcher(matcher);
//5 封装 myRealm 集合
List<Realm> list = new ArrayList<>();
list.add(myRealm);
//6 将 myRealm 存入 defaultWebSecurityManager 对象
defaultWebSecurityManager.setRealms(list);
//6.5 设置 rememberMe(Shiro 提供了记住我(RememberMe)的功能)
defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
//7 返回
return defaultWebSecurityManager;
}
//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;
}
//创建 Shiro 的 cookie 管理对象
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new
CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());
return cookieRememberMeManager;
}
//配置 Shiro 内置过滤器拦截范围
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 拦截配置
Map<String, String> filter = new LinkedHashMap<>();
// 登录接口放行
//设置需要进行登录认证的拦截范围
filter.put("/**", "authc");
//添加存在用户的过滤器(rememberMe)
filter.put("/**", "user");
shiroFilter.setFilterChainDefinitionMap(filter);
return shiroFilter;
}
}
(2)修改 controller,新增一个访问参数
package com.syh.shiro.controller;
import com.syh.shiro.Utils.JWT.JwtUtil;
import com.syh.shiro.Utils.relevant.RespStaticEnum;
import com.syh.shiro.Utils.relevant.ResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@RestController
@RequestMapping("/api/UserController")
@Slf4j
public class UserController {
// http://localhost:8080/api/UserController/userLogin/true?name=zhangsan&pwd=z3
@GetMapping("/userLogin/{rememberMe}")
public ResultUtils userLogin(String name, String pwd, @PathVariable boolean rememberMe, HttpServletRequest request){
//1 获取 Subject 对象
Subject subject = SecurityUtils.getSubject();
//2 封装请求数据到 token 对象中
AuthenticationToken token = new UsernamePasswordToken(name, pwd, rememberMe);
//3 调用 login 方法进行登录认证
try {
subject.login(token);
String jwtToken = JwtUtil.getJwtToken(name, pwd);
return ResultUtils.success(RespStaticEnum.LOGIN_SUCCESS, jwtToken);
} catch (AuthenticationException e) {
log.error("登录失败");
return ResultUtils.fail(RespStaticEnum.LOGIN_ERROR);
}
}
// http://localhost:8080/api/UserController/get
/*
* 测试接口
* 清理好浏览器cookie,直接访问此方法,会被拦截;
* 如果先访问登录接口,然后再访问此接口,就可以访问;
* 此刻关闭浏览器,再打开,然后访问此接口,发现依旧可以访问,这表明浏览器记住了登陆者
* */
@GetMapping("/get")
public String getStr(HttpSession session, HttpServletRequest request) {
session.setAttribute("user", "rememberMe");
return "ss";
}
}
5、登出功能
过滤器中添加如下配置
// 配置登出,请求该地址,shiro清除HTTPSession数据,实现退出登录。
filter.put("/api/UserController/logout", "logout");
6、授权验证-->角色认证
用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判断。这个工具就是Realm的doGetAuthorizationInfo方法进行判断。触发权限判断的有两种方式,这里使用在接口服务中通过注解@Requires****进行判断
通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加 在业务方法上,一般加在控制器方法上。常用注解如下:
- @RequiresAuthentication 验证用户是否登录,等同于方法subject.isAuthenticated()
- @RequiresUser 验证用户是否被记忆: 登录认证成功subject.isAuthenticated()为true 登录后被记忆subject.isRemembered()为true
- @RequiresGuest 验证是否是一个guest的请求,是否是游客的请求 此时subject.getPrincipal()为null
- @RequiresRoles 验证subject是否有相应角色,有角色访问方法,没有则会抛出异常 AuthorizationException。 例如:@RequiresRoles(“aRoleName”) void someMethod(); 只有subject有aRoleName角色才能访问方法someMethod()
- @RequiresPermissions 验证subject是否有相应权限,有权限访问方法,没有则会抛出异常 AuthorizationException。 例如:@RequiresPermissions (“file:read”,”wite:aFile.txt”) void someMethod(); subject必须同时含有file:read和wite:aFile.txt权限才能访问方法someMethod()
(1)数据库
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`r_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名',
`desc` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`r_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1111', '管理员', '全部权限');
SET FOREIGN_KEY_CHECKS = 1;
(2)实体类
@Getter
@Setter
@TableName("role")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 角色id
*/
@TableId("r_id")
private String rId;
/**
* 角色名
*/
@TableField("name")
private String name;
/**
* 描述
*/
@TableField("desc")
private String desc;
}
(3)授权校验代码实现
注解生效需要一些配置,在ShiroConfig配置文件中添加如下代码
/*
* 如下两个配置可以使shiro的权限校验注解生效
* */
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
//AuthorizationAttributeSourceAdvisor 的作用是匹配所有类 匹配所有加认证注解的方法
//具体看这篇博客:https://blog.csdn.net/wangjun5159/article/details/51889628
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Autowired DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
(4)MyRealm自定义授权方法
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private IRoleService roleService;
//自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1 创建对象,存储当前登录的用户的权限和角色
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//2 获取用户身份信息
String principal = principalCollection.getPrimaryPrincipal().toString();
//3 获取用户对应的角色信息
List<String> roleList = roleService.getRoleName(principal);
//4 存储角色
info.addRoles(roleList);
//5 返回
return info;
}
//自定义登录认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1 获取用户身份信息
String name = token.getPrincipal().toString();
//2 调用业务层获取用户信息(数据库中)
User user = userService.getUserInfoByName(name);
//3 判断并将数据完成封装
if(user!=null){
AuthenticationInfo info = new SimpleAuthenticationInfo(
token.getPrincipal(),
user.getPwd(),
ByteSource.Util.bytes("salt"),
token.getPrincipal().toString()
);
return info;
}
return null;
}
}
(5)controller角色授权验证接口
/*
* 角色授权验证接口
* */
// 1111:管理员角色
@RequiresRoles("1111")
@GetMapping("/userLoginRoles")
public String userLoginRoles() {
System.out.println("登录认证验证角色");
return "验证角色成功";
}
(6)角色验证失败的返回信息
数据库中只有1111角色,没有1111222角色
6、授权验证-->角色的权限认证
(1)数据库
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for permissions
-- ----------------------------
DROP TABLE IF EXISTS `permissions`;
CREATE TABLE `permissions` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名',
`info` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限信息',
`desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of permissions
-- ----------------------------
INSERT INTO `permissions` VALUES ('111111111', 'user:delete', '删除用户', '删除用户信息');
INSERT INTO `permissions` VALUES ('1111111222', 'user:select', '查询用户信息', '查询用户信息');
SET FOREIGN_KEY_CHECKS = 1;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for role_ps
-- ----------------------------
DROP TABLE IF EXISTS `role_ps`;
CREATE TABLE `role_ps` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
`rid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色 id',
`pid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限 id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色权限映射表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role_ps
-- ----------------------------
INSERT INTO `role_ps` VALUES ('sahksah', '1111', '111111111');
SET FOREIGN_KEY_CHECKS = 1;
(2)实体类
@Getter
@Setter
@TableName("permissions")
public class Permissions implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 编号
*/
@TableId("id")
private String id;
/**
* 权限名
*/
@TableField("name")
private String name;
/**
* 权限信息
*/
@TableField("info")
private String info;
/**
* 描述
*/
@TableField("desc")
private String desc;
}
@Getter
@Setter
@TableName("role_ps")
public class RolePs implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 编号
*/
@TableId("id")
private String id;
/**
* 角色 id
*/
@TableField("rid")
private String rid;
/**
* 权限 id
*/
@TableField("pid")
private String pid;
}
(4)MyRealm授权方法中添加权限信息
/*
* 自定义认证授权方法
* */
@Component
public class MyRealm extends AuthorizingRealm {
// 用户
@Autowired
private UserService userService;
// 角色
@Autowired
private IRoleService roleService;
// 角色对应的权限
@Autowired
private IRolePsService rolePsService;
//自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1 创建对象,存储当前登录的用户的权限和角色
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//2 获取用户身份信息
String principal = principalCollection.getPrimaryPrincipal().toString();
//3 获取用户对应的角色信息
List<String> roleList = roleService.getRoleName(principal);
//4 存储角色
info.addRoles(roleList);
//5 获取用户权限信息
List<String> jurisdictionList = rolePsService.getRolejurisdiction(roleList);
//6 储存权限信息
info.addStringPermissions(jurisdictionList);
//7 返回
return info;
}
//自定义登录认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1 获取用户身份信息
String name = token.getPrincipal().toString();
//2 调用业务层获取用户信息(数据库中)
User user = userService.getUserInfoByName(name);
//3 判断并将数据完成封装
if(user!=null){
AuthenticationInfo info = new SimpleAuthenticationInfo(
token.getPrincipal(),
user.getPwd(),
ByteSource.Util.bytes("salt"),
token.getPrincipal().toString()
);
return info;
}
return null;
}
}
(5)controller角色的权限认证接口
/*
* 角色的权限验证接口
* */
@RequiresPermissions("user:delete")
@GetMapping("/userJurisdiction")
public String userJurisdiction() {
System.out.println("角色权限验证");
return "角色权限验证成功";
}
7、授权验证-异常处理
集成到springboot全局异常处理中
/*
* 全局异常处理类
* */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/*
* 全局异常捕获
* */
@ResponseBody
@ExceptionHandler(value = Exception.class)
public ResultUtils javaExceptionHandler(Exception ex){
return ResultUtils.fail(RespStaticEnum.FAIL, ex.getMessage());
}
/*
* shiro框架权限校验失败异常捕获
* */
@ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public ResultUtils unauthorizedException(Exception ex){
log.error("无权限:" + ex.getLocalizedMessage());
return ResultUtils.fail(RespStaticEnum.FAIL);
}
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public ResultUtils authorizationException(Exception ex){
log.error("权限认证失败:" + ex.getLocalizedMessage());
return ResultUtils.fail(RespStaticEnum.FAIL);
}
}
8、Shiro整合EhCache,实现缓存
springboot整合EhCache学习请移步(1条消息) EhCache缓存框架_SUN Y H的博客-CSDN博客
添加依赖
Shiro官方提供了shiro-ehcache,实现了整合EhCache作为Shiro的缓存工具。可以缓 存认证执行的Realm方法,减少对数据库的访问,提高认证效率
<!--Shiro 整合 EhCache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
</dependency>
添加shiro配置文件
在 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>
<!--登录认证信息缓存:缓存用户角色权限-->
<cache name="loginRolePsCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true"/>
</ehcache>
修改配置类 ShiroConfig
6.6步为新添加,其他为之前代码
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {
//1 创建 defaultWebSecurityManager 对象
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2 创建认证对象,并设置认证策略
ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
//3 只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功 默认值
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);
//3 创建加密对象,并设置相关属性
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//3.1 采用 md5 加密
matcher.setHashAlgorithmName("md5");
//3.2 迭代加密次数
matcher.setHashIterations(3);
//4 将加密对象存储到 myRealm 中
myRealm.setCredentialsMatcher(matcher);
//5 封装 myRealm 集合
List<Realm> list = new ArrayList<>();
list.add(myRealm);
//5.1 封装customer2Realm
list.add(customer2Realm);
//6 将 myRealm 存入 defaultWebSecurityManager 对象
defaultWebSecurityManager.setRealms(list);
//6.5 设置 rememberMe(Shiro 提供了记住我(RememberMe)的功能)
defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
//6.6 设置缓存管理器
defaultWebSecurityManager.setCacheManager(getEhCacheManager());
//7 返回
return defaultWebSecurityManager;
}
//缓存管理器
public EhCacheManager getEhCacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
InputStream is = null;
try {
is = ResourceUtils.getInputStreamForPath(
"classpath:ehcache/ehcache-shiro.xml");
} catch (IOException e) {
e.printStackTrace();
}
CacheManager cacheManager = new CacheManager(is);
ehCacheManager.setCacheManager(cacheManager);
return ehCacheManager;
}
代码测试
此处不贴运行结果图了,打开sql日志打印。登录之后,访问权限验证接口,第一次会查询数据库打印sql语句,第二次访问会走缓存,看不到sql语句打印。
缓存更新问题
当某个用户的权限被更新,应该同步更新EhCache中缓存
9、会话管理
SessionManager
会话管理器,负责创建和管理用户的会话(Session)生命周期,它能够在任何环境中 在本地管理用户会话,即使没有Web/Servlet/EJB容器,也一样可以保存会话。默认情况 下,Shiro会检测当前环境中现有的会话机制(比如Servlet容器)进行适配,如果没有(比如独立应用程序或者非Web环境),它将会使用内置的企业会话管理器来提供相应的会 话管理服务,其中还涉及一个名为SessionDAO的对象。SessionDAO负责Session的持久化操 作(CRUD),允许Session数据写入到后端持久化数据库。
会话管理实现
SessionManager由SecurityManager管理。Shiro提供了三种实现
- DefaultSessionManager:用于JavaSE环境
- ServletContainerSessionManager:用于web环境,直接使用Servlet容器的会话
-
DefaultWebSessionManager:用于web环境,自己维护会话(不使用Servlet容器的会话管理)
获得session方式
- 实现 :Session session = SecurityUtils.getSubject().getSession();session.setAttribute(“key”,”value”)
- 说明 :Controller 中的 request,在 shiro 过滤器中的 doFilerInternal 方法,被包装成 ShiroHttpServletRequest; SecurityManager 和 SessionManager 会话管理器决定 session 来源于 ServletRequest 还是由 Shiro 管理的会话; 无论是通过 request.getSession 或 subject.getSession 获取到 session,操作 session,两者都是等价的。
四、shiro拦截器
1、shiro默认拦截器
Shiro内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器:
注意:anon, authc, authcBasic, user 是第一组认证过滤器,perms, port, rest, roles, ssl 是第二组授权过滤器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权) 才能走第二组授权器(例如访问需要 roles 权限的 url,如果还没有登陆的话,会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的 url
默认拦截器名 | 拦截器类 | 说明(括号里的表示默认值) |
身份验证相关的 | ||
authc | org.apache.shiro.web.filter.authc .FormAuthenticationFilter | 基于表单的拦截器;如“/**=authc”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username); passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe); loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure); |
authcBasic | org.apache.shiro.web.filter.authc .BasicHttpAuthenticationFilter | Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application); |
logout | org.apache.shiro.web.filter.authc .LogoutFilter | 退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/);示例“/logout=logout” |
user | org.apache.shiro.web.filter.authc .UserFilter | 用户拦截器,用户已经身份验证/记住我登录的都可;示例“/**=user” |
anon | org.apache.shiro.web.filter.authc .AnonymousFilter | 匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例“/static/**=anon” |
授权相关的 | ||
roles | org.apache.shiro.web.filter.authz .RolesAuthorizationFilter | 角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例“/admin/**=roles[admin]” |
perms | org.apache.shiro.web.filter.authz .PermissionsAuthorizationFilter | 权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例“/user/**=perms["user:create"]” |
port | org.apache.shiro.web.filter.authz .PortFilter | 端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样 |
rest | org.apache.shiro.web.filter.authz .HttpMethodPermissionFilter | rest风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll); |
ssl | org.apache.shiro.web.filter.authz .SslFilter | SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样; |
其他 | ||
noSessionCreation | org.apache.shiro.web.filter.session .NoSessionCreationFilter | 不创建会话拦截器,调用 subject.getSession(false)不会有什么问题,但是如果 subject.getSession(true)将抛出 DisabledSessionException异常; |
2、自定义jwt拦截器
学习该模块时,看的其他博主的帖子。试验了一下无一成功。应该是理解有误,然后放弃网上的方法,按照自己的理解写了这篇自定义jwt拦截器。
自定义iwt拦截器
(1)用到的jwt工具类
(3条消息) jjwt工具类_SUN Y H的博客-CSDN博客
(2)创建自定义拦截器,实现BasicHttpAuthenticationFilter
import com.syh.shiro.Utils.JWT.JwtUtil;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author ojj
* @title: JwtFilter
* @projectName test
* @description: 拦截器
* @date 2022/1/6 14:57
*/
public class JwtFilter extends BasicHttpAuthenticationFilter {
/**
* 拦截器的前置 最先执行的 这里只做了一个跨域设置
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
res.setHeader("Access-control-Allow-Origin", req.getHeader("Origin"));
res.setHeader("Access-control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
res.setHeader("Access-control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"));
// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
res.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
/**
* preHandle 执行完之后会执行这个方法
* 再这个方法中 我们根据条件判断去去执行isLoginAttempt和executeLogin方法
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
/**
* 先去调用 isLoginAttempt方法 字面意思就是是否尝试登陆 如果为true
* 执行executeLogin方法
*/
if (isLoginAttempt(request, response)) {
executeLogin(request, response);
return true;
}
return false;
}
/**
* 这里我们只是简单去做一个判断请求头中的token信息是否为空
* 如果没有我们想要的请求头信息则直接返回false
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("X-Token");
return token != null;
}
/**
* 执行登陆
* 因为已经判断token不为空了,所以直接执行登陆逻辑
* 将token放入JwtToken类中去
* 然后getSubject方法是调用到了MyRealm的 执行方法 因为上面我是抛错的所有最后做个异常捕获就好了
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
String jwtToken = req.getHeader("X-Token");
// 校验token是否有效
if (JwtUtil.checkToken(jwtToken)) {
return true;
} else {
return false;
}
}
}
(3)ShiroConfig类中引入自定义jwt拦截器,并配置拦截路径
//配置 Shiro 内置过滤器拦截范围
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截配置
Map<String, Filter> filterMap = new LinkedHashMap<>();
//引入自定义拦截器,命名为jwt
filterMap.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
Map<String,String> filterRuleMap=new LinkedHashMap<>();
// 登录接口放行
filterRuleMap.put("/api/UserController/userLogin/**", "anon");
// 配置登出,请求该地址,shiro去清除session,实现退出登录。
filterRuleMap.put("/api/UserController/logout", "logout");
// 配置自定义jwt认证
filterRuleMap.put("/api/UserController/jwtLan", "jwt");
// 设置需要进行登录认证的拦截范围
filterRuleMap.put("/**", "authc");
// 添加存在用户的过滤器(rememberMe)
filterRuleMap.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
return shiroFilterFactoryBean;
}
(4)登录接口将生成的token返回给前端
//配置 Shiro 内置过滤器拦截范围
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截配置
Map<String, Filter> filterMap = new LinkedHashMap<>();
//引入自定义拦截器,命名为jwt
filterMap.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
Map<String,String> filterRuleMap=new LinkedHashMap<>();
// 登录接口放行
filterRuleMap.put("/api/UserController/userLogin/**", "anon");
// 配置登出,请求该地址,shiro去清除session,实现退出登录。
filterRuleMap.put("/api/UserController/logout", "logout");
// 配置自定义jwt认证
filterRuleMap.put("/api/UserController/jwtLan", "jwt");
// 设置需要进行登录认证的拦截范围
filterRuleMap.put("/**", "authc");
// 添加存在用户的过滤器(rememberMe)
filterRuleMap.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
return shiroFilterFactoryBean;
}
(5)测试,该访问路径会经过自定义jwt拦截器
/*
* 自定义jwt认证
* */
@GetMapping("/jwtLan")
public String jwtLan() {
System.out.println("自定义jwt认证");
return "自定义jwt认证";
}