一篇文章快速学会shiro框架!(建议收藏)
1. 认证与授权
在学习 Shiro 框架之前,我们先来了解一下什么是认证与授权。
认证
认证就是校验身份的正确性,看一下你是不是这个平台的用户。系统一般会将用户输入的账号密码和数据库的信息做比对,从而判断身份是否合法。
授权
授权就是确定你是否有权访问系统的某些资源,比如查看某个页面、点击某个按钮、操作某一行数据等。
2. 邂逅 Shiro
Shiro 是 Apache 基金会下面一个开源的 java 权限管理框架,它可以进行身份验证、授权、密码和会话管理。
官网:
https://shiro.apache.org/
Shiro 核心架构:
对于上面复杂的架构,我们只需要记住以下几个核心模块:
1.Subject
主体,你可以理解为访问系统的用户。
2.SecurityManager
安全管理器。用户进行认证和授权都是通过 securityManager 进行,你可以理解为 shiro 的老大。
3.authenticator
认证器,用户通过 authenticator 进行认证。
4.authorizer
授权器,用户通过 authorizer 进行授权。
5.realm
领域,相当于数据源。
在 realm 中,我们通过查询数据库的信息,然后对用户进行认证和授权。所以 authenticator 和 authorizer 其实是调用了 realm 中 认证和授权的方法。
6.cryptography
密码管理。shiro 提供了一套加密和解密的组件。
3. Springboot 整合 Shiro
1.新建 springboot 项目
2.引入依赖
这里我们主要引入了 web、mybatis-plus、shiro、mysql 的依赖,后面有完整的代码。
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!--引入 shiro 整合 Springboot 依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
3.修改配置文件
4.创建用户表
CREATE TABLE `t_user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '姓名',
`username` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '账号',
`password` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密码',
`salt` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '盐',
`created_date` datetime DEFAULT NULL COMMENT '创建时间',
`updated_date` datetime DEFAULT NULL,
`is_deleted` int DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1500052815584731139 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
5.使用 mybatis-plus 生成实体类、service、dao、mapper文件
4. Shiro 认证
1.文件数据源
因为 shiro 的默认数据源是配置文件数据源,所以我们先用配置文件的方式模拟数据库信息。
配置文件数据源:
/*认证*/
@Test
void authenticator() {
// 1.创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2.设置 realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
// 3.设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4.获取当前主体对象
Subject subject = SecurityUtils.getSubject();
// 5.将用户登录时的账号和密码封账成 UsernamePasswordToken 对象
UsernamePasswordToken token = new UsernamePasswordToken("zhifou", "123456");
try {
log.info("认证前状态:" + subject.isAuthenticated());
subject.login(token);
log.info("认证后状态:" + subject.isAuthenticated());
} catch (UnknownAccountException e) {
log.error("用户不存在");
} catch (IncorrectCredentialsException e) {
log.error("账号或密码不正确");
}
}
认证结果:
认证流程讲解:
首先,你需要创建安全管理器 SecurityManager,然后为安全管理器设置 Realm,Realm 的数据源是以配置文件的方式进行配置。
用户在前端输入账号和密码,shiro 框架获取当前要登录的主体(用户)对象,然后将账号和密码封装成 UsernamePasswordToken 对象。
接着安全管理器通知 Realm 认证一下当前用户,Realm 将用户输入的账号密码信息和配置文件中的信息做比对,比对通过则认证成功,比对不通过则返回错误信息。
2.数据库数据源
使用数据库作为数据源需要我们自定义 Realm,其中自定义的 Realm 需要继承 AuthorizingRealm 类并实现两个方法。
其中一个是认证,一个是授权,这里我们实现认证方法。
新建自定义 Realm,并实现认证方法:
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
public MyRealm( UserService userService) {
this.userService = userService;
}
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 用户登录名称
String principal = (String) authenticationToken.getPrincipal();
User user = userService.getOne(new QueryWrapper<User>().eq("username", principal));
//用户不为空
if (null != user) {
// 1.账号 2.密码 3.当前 realm 名字
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
修改安全管理器的 Realm 为自定义的 Realm:
数据库中用户信息:
启动测试类,查看认证结果:
讲解:
用户登录的账号和密码被封装成了 token 对象,调用 subject.login() 方法之后,自定义 Realm 的认证方法会接收传递的 token 信息。
其中 UsernamePasswordToken 是 AuthenticationToken 接口的实现类,在 AuthenticationToken 接口中,getPrincipal() 方法返回的是用户的账号信息,getCredentials() 返回的是密码信息。
将用户填写的账号和密码与数据库中的信息做比对,比对通过则返回 SimpleAuthenticationInfo 对象,其中需要传递三个参数:
- 数据库中的账号
- 数据库中的密码
- 当前的 realm 对象
3.Shiro 认证完整案例
这里我们采用 shiro 配置类和登录接口的方式来实现 shiro 的认证。
1.在 UserController 中新建登录接口:
@Controller
@RequestMapping("/user")
@Slf4j
public class UserController {
@RequestMapping("/login")
public String login(String username, String password) {
try {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken(username, password));
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
log.error("用户不存在");
} catch (IncorrectCredentialsException e) {
log.error("账号或者密码错误");
} catch (Exception e) {
log.error("服务出错");
}
return "redirect:/login.jsp";
}
}
2.新建自定义 Realm,实现认证方法。
3.新建 shiro 的配置类
shiro 的配置类主要包含三个 bean:
- shiroFilter:用来拦截所有请求
- SecurityManager:安全管理器
- Realm:自定义 realm
完整配置类:
@Configuration
public class ShiroConfig {
// 1.shiroFilter:负责拦截所有请求
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
// 默认跳转页面---认证不通过时跳转
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
return shiroFilterFactoryBean;
}
// 2.创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
// 3.创建自定义 realm
@Bean("realm")
public Realm getRealm() {
CustomerRealm customerRealm = new CustomerRealm();
return customerRealm;
}
}
4.发起登录请求
4.密码加密
我们经常使用 MD5 进行加密,MD5 加密是一种不可逆的加密算法。使用 MD5 加密后的结果是一个 32 位的字符串。
使用 MD5 加密需要三个参数:
- 密码
- 随机盐:其实就是一串复杂的字符串,可以使加密后的密码更复杂
- 散列次数:一般是 1024
在 Shiro 中,需要修改自定义 Realm 来完成 MD5 的加密解密:
在认证方法中,需要返回随机盐。
案例:
1.随机盐工具类
public class SaltUtils {
public static String getSalt(int n) {
String str = "01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz(#$^@%&*!)";
char[] chars = str.toCharArray();
StringBuilder randomSalt = new StringBuilder();
Random random = new Random();
for (int i = 0; i < n; i++) {
int number = random.nextInt(chars.length);
char aChar = chars[number];
randomSalt.append(aChar);
}
return randomSalt.toString();
}
}
2.新增用户信息
@Test
void registerUser() {
String salt = SaltUtils.getSalt(6);
User user = new User();
user.setName("张无忌").setUsername("zhangwuji").setPassword("123456");
// 设置盐值
user.setSalt(salt);
// 明文密码进行md5 + salt + hash散列
Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
user.setPassword(md5Hash.toHex());
userService.save(user);
}
3.登录认证
5. Shiro 授权
我们一般会基于 RBAC 模型设计权限管理系统,在 RBAC 模型里面,有 3 个基础组成部分,分别是:用户、角色和权限。
我们先根据 RBAC 设计数据库表:
修改用户实体类:
修改角色实体类:
在 Shiro 中有三种授权方式:
权限注解
@RequiresRoles(value={"admin","user"})
@RequestMapping("save")
public String save(){
// 有权限
}
@RequiresPermissions("user:update:01")
@RequestMapping("save")
public String save(){
// 有权限
}
编程判断
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
// 有权限
} else {
// 无权限
}
权限标签
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
完整授权案例:
1.初始化数据库
2.修改 ShiroFilter
因为 ShiroFilter 负责拦截所有请求,所以需要设置哪些资源要拦截,哪些资源可以放开。
在 ShiroFilter 中,需要授权和不需要授权的资源存放在 map 集合里面。其中 anon 表示不需要授权,authc 表示需要授权。
我们还需要配置认证不通过默认跳转页面以及授权不通过跳转页面。
3.修改自定义 Realm
我们需要在自定义的 Realm 中通过查询数据库的角色和权限,然后为主体
授权。
4.演示授权
1)注解式授权
新建 login.jsp
新建保存订单接口,拥有 admin 和 user 角色的用户可以访问:
浏览器直接访问保存订单接口,由于没有登录所以默认会跳转到登录页面
使用 zhouzhiruo 这个账户登录,然后再访问保存订单接口
因为 zhouzhiruo 没有添加订单的权限,所以会跳转到 403 页面。
这里使用了全局异常处理器拦截无权限的异常。
2)权限标签
上面系统主页菜单的显示使用了 shiro 的权限标签。
如果用户拥有此角色或者权限资源,就展示相应的菜单。
注意:jsp 页面需要引入 shiro 相关的 tag 资源。
5.退出登录
/**
* 退出登录
*/
@RequestMapping("/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login.jsp";
}
6. 完整代码
链接: https://pan.baidu.com/s/1Fd0KVFiuRW9a-OY01uTBKw?pwd=1234
提取码: 1234
-END-