Shiro安全框架简介

news/2024/12/23 0:45:36/

 一、权限管理

1.1 什么是权限管理

  • 基本上只要涉及到用户参数的系统都要进行权限管理,使用权限管理实现了对用户访问系统的控制,不同的用户访问不同的资源。按照安全规则或者安全策略控制用户访问资源,而且只能访问被授权的资源
  • 权限管理包括认证和授权两部分,当用户访问资源时先对其进行身份的认证,认证通过后即可访问已经授权的资源。

1.2 身份认证

  • 用来判断一个用户是否合法的处理过程。用过用户输入的用户名或者口令来和系统中存储进行比较,从而认证用户的身份是否正确

对于身份认证,也就是之前做的的登录

1.3 授权

  • 用来控制认证过后的用户可以访问哪些资源。用户身份认证后需要给该用户分配可访问的资源,如果没有某个资源的权限,那么将无法访问

二、Shiro架构

2.1 Shiro的理解

是一个功能强大且易实现的Java安全框架,使用Shiro可以执行认证、授权、加密和会话管理。使用Shiro中提供的API可以快速轻松的保护任何程序

Shiro不依赖于WEB,即使是一个测试程序也能够使用Shiro中的功能

2.2 Shiro的体系

官方图示:

原理:

Subject

  • 表示主体,外部应用和Subject进行交互。Subject中记录了当前操作用户,这个用户可以是一个发送请求的用户,也可以是一个运行的程序
  • Subject在Shiro是一个接口,定义了很多认证授权的相关方法,外部程序通过Subject进行认证授权,Subject又是通过SecurityManager安全管理器就行认证管理

SecurityManager

  • 表示安全管理器,是Shiro中的核心,用来协调其托管的组件,以保证它们能够顺利协同工作。
  • 可以进行会话管理等

Authenticator

  • 表示身份认证器,负责执行和响应用户的身份认证尝试的组件。当用户进行登录操作时,此组件进行处理

Authorizer

  • 表示授权器,负责控制用户在系统中可以访问哪些资源。在访问资源时都需要该组件进行判断当前用户是否拥有这个资源的权限

Realm

  • 表示领域,充当Shiro与应用程序的安全数据之间的桥梁或者连接器,和DataSource数据源差不多,当需要和安全相关的数据(如用户账号)进行实际交互从而执行认证和授权时,Shiro会从应用配置的一个或者多个Realm中查询其中的内容
  • SecurityManager进行安全认证时需要通过Realm获取到用户权限数据
  • Realm不只是从数据库取数据,还有认证和授权的相关逻辑代码

SessionManager

  • 表示会话管理,知道如果创建和管理用户生命周期,以便为所欲环境中的用户提供强大的会话体验
  • 不依赖WEB容器,所以Shiro可以使用在非WEB应用中也可以将分布式应用的会话集中在一点管理,次特征可以使它实现单点登录

SessionDao

  • 表示会话Dao,是对session会话操作的一套接口

CacheManager

  • 表示缓存管理器,将用户权限存储到缓存中,从而提高性能

Cryptography

  • 表示密码管理,Shiro中提供了一套加密/解密的组件,方便开发

三、Shiro中的认证

3.1 认证中的关键对象

  • Subject:主体

访问系统的每一个用户或者应用程序,经过认证的都成为主体

  • Principal:身份信息

是主体(Subject)进行身份证认证的表示,表示必须具有唯一性。比如用户名/手机号/邮箱,一个主体(Subject)中可以有多个身份信息,但必须有一个主身份

  • Credential:凭证信息

3.2 认证流程

图示:

文字:

  1. 收集使用者的Principal和Credential
  1. 提交进行身份验证
  1. 如果验证成功就允许访问,否则重试身份证验证或者阻止访问

3.3 环境搭建

  1. 引入Shiro依赖
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.9.1</version>
</dependency>
  1. 在resources下创建.ini的配置文件,来临时模拟数据库存储用户的身份信息和凭证信息

[users]
admin=1234
tom=222
jack=456

  1. 编写认证代码
public class ShiroAuthentication {public static void main(String[] args) {// 1.创建SecurityManager安全管理器的实现类DefaultSecurityManager securityManager = new DefaultSecurityManager();// 2.将Realm中的数据设置到安全管理器中securityManager.setRealm(new IniRealm("classpath:shiro.ini")); // Realm去读取ini中的主体与凭证信息// 3.将安全管理器设置到全局安全工具类中SecurityUtils.setSecurityManager(securityManager);// 4.获取主体Subject subject = SecurityUtils.getSubject();// 5.认证if (!subject.isAuthenticated()) { // 是否已经认证// 如果没有认证过,那么证明该用户第一次登录.收集使用者的身份信息和凭证信息UsernamePasswordToken token = new UsernamePasswordToken("admin","1234"); // 模拟前台输入// 提交身份信息和凭证信息subject.login(token);}}
}

3.4 处理结果

如果提交成功,执行后续的逻辑代码;提交过程中出现错误,那么Shiro将以抛异常的形式声明错误

异常列表:

  • UnknowAccountException  --> 未知账号异常【用户名错误】
  • IncorrectCredentialsException  --> 凭证信息异常【密码错误】
  • LockedAccountException  --> 锁定账号异常
  • ExcessiveAttemptsException  --> 过度尝试异常
  • AuthenticationException  --> 身份认证异常

修改以上程序:

try {// 开始认证subject.login(token);
}catch (UnknownAccountException e){System.out.println("账号错误");
}catch (IncorrectCredentialsException e){System.out.println("密码错误");
}

3.5 获取主体身份信息与注销

此操作必须保证认证通过

  • 获取主体身份信息
if (subject.isAuthenticated()){ // 认证通过Object principal = subject.getPrincipal(); // 6. 获取身份信息System.out.println(principal); // admin
}

  • 注销
subject.logout();

3.6 底层实现

身份信息校验

  • 在SimpleAccountRealm类中的doGetAuthenticationInfo方法判断身份信息是否一致
  • 如果用户名错误,那么返回的info==null,系统抛出UnknowAccountException异常

密码校验

  • 在AuthenticatingRealm类中的assertCredentialsMatch方法进行密码(凭证信息)的校验,Shiro中凭证信息默认的校验规则是equals
  • 如果密码错误,抛出IncorrectCredentialsException异常

3.7 自定义Realm

以后校验用户名肯定不能使用Shiro中定义的,需要连接数据库,通过用户名查询。所以Shiro中也可以让我们自定义Realm

Realm继承图

全部

主要部分

认证方法和授权方法都在AuthorizingRealm定义为抽象方法,等待子类继承并重写这两个方法。

SimpleAccountRealm继承了AuthorizingRealm,所以这个类里面有认证和授权功能,其两个功能对应的方法为:

  • doGetAuthenticationInfo:认证
  • doGetAuthorizationInfo:授权

以后自定义的Realm只需要继承AuthorizingRealm,然后重写这两个方法

/**
* 自定义Realm,继承AuthorizingRealm类
*/
public class LoginRealm extends AuthorizingRealm {/*授权*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}/*认证*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {return null;}
}

在doGetAuthenticationInfo方法中获取用户的身份信息,然后校验是否和数据库中的一致

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// getPrincipal()获取身份信息String username = (String) token.getPrincipal();if ("admin".equals(username)){// 正确 返回AuthenticationInfo的实现类// 参数:1.当前用户的身份信息 2.验证主体的凭证信息[如果和前台传入的不一致,抛出IncorrectCredentialsException异常] 3.当前Realmreturn new SimpleAuthenticationInfo(username,"1234",super.getName());}// 不正确返回一个null, info == null 抛出UnknownAccountException异常return null;
}

3.8 加密

测试程序:

public class MD5Test {public static void main(String[] args) {// 加密Md5Hash md5Hash = new Md5Hash("1234");// 加密+salt(盐)Md5Hash md5Hash1 = new Md5Hash("1234", "f5gy");// 加密+salt(盐)+散列次数Md5Hash md5Hash2 = new Md5Hash("1234","f5gy",1024);}
}

整合认证:

  1. 修改认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// getPrincipal()获取身份信息String username = (String) token.getPrincipal();if ("admin".equals(username)){// 正确 返回AuthenticationInfo的实现类/*参数:1.当前用户的身份信息2.验证主体的凭证信息[如果和前台传入的不一致,抛出IncorrectCredentialsException异常]3.盐4.当前Realm*/String password = "75b323294effa42ed07f895f37f9a192";String salt = "f5gy";return new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes(salt),super.getName());}// 不正确返回一个null, info == null 抛出UnknownAccountException异常return null;
}

  1. 修改密码比较器

Shiro默认实用的是simpleCredentialsMatcher中的doCredentialsMatcher方法,这个方法使用的是equals的方式进行比较密码。

CredentialsMatcher继承图:

使用HashedCredentialsMatcher这个类

LoginRealm realm = new LoginRealm();
HashedCredentialsMatcher hash = new HashedCredentialsMatcher();
// 设置算法
hash.setHashAlgorithmName("MD5");
// 设置散列次数
hash.setHashIterations(1024);
// 设置到Realm中
realm.setCredentialsMatcher(hash);

四、Shiro中的授权

4.1 授权中的关键对象

  • Who

表示主体,主题需要系统中的资源

  • What

表示资源,这个资源可以是一个按钮、菜单等。资源又分为资源实例和资源类型

  • How

表示权限/许可,控制主体对资源的访问

4.2 授权方式

  • 基于角色的访问控制(Role-Based Access Control):以角色为中心进行权限控制
  • 基于资源的访问控制(Resource-Based Access Control):以资源为中心进行权限控制

4.3 权限字符串

权限字符串的规则:资源标识符:操作,意思是对哪个资源进行哪些操作。":"是分割符,权限字符串可以使用"*"来表示通配符

4.4 校验角色

  1. 在doGetAuthorizationInfo方法中设置当前主体的角色
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 获取主题中的身份信息[用户名]String principal = (String) principals.getPrimaryPrincipal();// 返回AuthorizationInfo的实现类SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 给当前主体添加角色info.addRole("admin");info.addRole("user");return info;
}

  1. 模拟前台测试
if (subject.isAuthenticated()){// 校验单个角色System.out.println(subject.hasRole("admin")); // 是否有admin角色// 校验多个角色System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user"))); // 是否同时有admin user角色// 校验 多次 角色boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super"));for (boolean b : booleans) {System.out.println(b);}
}

4.5 校验权限字符串

  1. 在doGetAuthorizationInfo方法中设置当前主体的权限字符串
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// 获取主题中的身份信息[用户名]String principal = (String) principals.getPrimaryPrincipal();// 返回AuthorizationInfo的实现类SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 给当前主体添加权限字符串info.addStringPermission("user:update");info.addStringPermission("product:select");return info;
}

  1. 模拟前台测试

五、整合SpringBoot

5.1 整合思路

5.2 环境搭建

  1. 导入springboot和shiro整合的依赖包
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId><version>1.4.0</version>
</dependency>
  1. 创建一个类,继承AuthorizingRealm类,重写授权和认证方法
public class MyRealm extends AuthorizingRealm {@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {return null;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {return null;}
}
  1. 编写shiro和springboot整合的配置
@Configuration
public class ShiroConfig {@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 将SecurityManager设置到Filter中shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);return shiroFilterFactoryBean;}@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager(Realm realm){DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();// 给安全管理器设置RealmdefaultWebSecurityManager.setRealm(realm);return defaultWebSecurityManager;}@Beanpublic Realm Realm(){LoginRealm loginRealm = new LoginRealm();return loginRealm;}
}
  1. 在ShiroFilter过滤器中配置需要拦截的资源URL
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 将SecurityManager设置到Filter中shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);//设置受限资源Map<String,String> map = new HashMap<>();/*** authc:该路径资源需要认证和授权*/map.put("/**","authc"); // "/**"代表所有的资源路径都拦截shiroFilterFactoryBean.setFilterChainDefinitionMap(map);return shiroFilterFactoryBean;
}
  1. 访问资源跳转到login.jsp,修改默认跳转路径
shiroFilterFactoryBean.setLoginUrl("/doLogin");@Controller
public class IndexController {@GetMapping("/doLogin")public String doLogin(){return "login";}
}
  1. 拦截后访问/doLogin路径

5.3 ShiroFilter过滤列表

Shiro中提供了多个默认的过滤器,用这些过滤器来控制指定URL路径下的资源

配置缩写

对应过滤器

描述

anon

AnonymousFilter

指定URL路径下的资源可以匿名访问

authc

FormAuthenticationFilter

指定URL路径下的资源需要认证过后才能访问

authcBasic

BasicHttpAuthenticationFilter

指定URL路径下的资源需要basic登录

logout

LogoutFilter

注销过滤器,只需配置对应的URL路径即可实现

noSessionCreation

NoSessionCreationFilter

禁止创建Session会话

perms

PermissionsAuthorizationFilter

需要有该URL资源对应的权限字符串才能访问

port

PortFilter

指定某个端口可以访问

rest

HttpMethodPermissionFilter

将HTTP请求转换成相对应的动词来构建权限字符串

roles

RolesAuthorizationFilter

需要有指定角色才能访问

ssl

SslFilter

需要https请求才能访问

user

UserFilter

需要已登录或者"记住我"的用户才能访问

六、连接数据库完成认证

6.1 注册

  1. 表设计

  1. 注册页面
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><h1>注册页面</h1><hr><form action="/user/register" method="post">用户名 : <input type="text" name="username"><br>密码 : <input type="password" name="password"> <br><input type="submit" value="注册"></form></body>
</html>
  1. 实体类POJO
@Data
public class User {private Long id;private String username;private String password;private String salt;
}
  1. 编写Controller,调用service处理业务逻辑
@Controller
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/doRegister")public String doRegister(){return "register";}@PostMapping("/register")public String register(User user){int count = userService.register(user);if (count > 0)return "redirect:/doLogin";elsereturn "redirect:/doRegister";}
}
  1. 在ShiroFilter放过这些URL路径
map.put("/user/register","anon");
map.put("/user/doRegister","anon");
  1. service中完成加密和散列盐,调用Mapper完成注册
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic int register(User user) {String salt = "ga*n";Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);user.setPassword(md5Hash.toHex());user.setSalt(salt);return userMapper.insert(user);}
}
  1. Mapper接口和SQL语句
public interface UserMapper {int insert(User user);
}
<mapper namespace="com.jiuxiao.mapper.UserMapper"><insert id="insert">insert into t_user(id,username,password,salt) values(null,#{username},#{password},#{salt})</insert>
</mapper>
  1. 启动类上添加@MapperScan注解,扫描Mapper包
@SpringBootApplication
@MapperScan("com.jiuxiao.mapper")
public class ShiroApp {public static void main(String[] args) {SpringApplication.run(ShiroApp.class,args);}
}
  1. 注册页面输入用户信息后,完成注册

6.2 认证

  1. 在Shiro配置中修改密码比较器
@Bean
public Realm realm(){LoginRealm loginRealm = new LoginRealm();HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();matcher.setHashAlgorithmName("MD5");matcher.setHashIterations(1024);loginRealm.setCredentialsMatcher(matcher);return loginRealm;
}
  1. 在自定义的Realm的doGetAuthenticationInfo方法中编写认证逻辑
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {@Autowiredprivate UserService userService;// 获取主体中的身份信息String principal = (String) authenticationToken.getPrincipal();// 调用service查询数据库User user = userService.selectByUsername(principal);if (!ObjectUtils.isEmpty(user)){// 如果可以查询到,校验密码return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),super.getName());}// 查询不到直接返回nullreturn null;
}
  1. service调用Mapper
@Override
public User selectByUsername(String principal) {return userMapper.selectByUsername(principal);
}
  1. Mapper接口与SQL语句
User selectByUsername(String principal);
<select id="selectByUsername" resultType="com.jiuxiao.pojo.User">select * from t_user where username = #{username}
</select>
  1. 登录页面代码
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>$Title$</title></head><body><h1>登录页面</h1><hr><form action="/user/login" method="post">用户名: <input type="text" name="username"> <br>密码: <input type="password" name="password"> <br><input type="submit" value="登录"></form></body>
</html>
  1. 在Controller中编写对应URL,封装token,并处理异常结果
@Controller
@RequestMapping("/user")
public class UserController {@PostMapping("/login")public String login(String username,String password){Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken(username, password);try {subject.login(token);return "index"; // 登陆成功跳转到首页} catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用户名错误");}catch (IncorrectCredentialsException e){e.printStackTrace();System.out.println("密码错误");}return "login"; // 登录失败跳转到登录页面}
}
  1. 首页代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>$Title$</title>
</head>
<body><h1>首页</h1><hr><ul><li><a href="">用户管理</a></li><li><a href="">商品管理</a></li><li><a href="">菜单管理</a></li><li><a href="">物流管理</a></li></ul>
</body>
</html>
  1. 在ShiroFilter过滤器中放过登录URL
map.put("/user/login","anon");
  1. 页面登录成功后,进入到index.html

6.3 注销

① 配置方式

  1. 在ShiroFilter过滤器中添加注销的URL
map.put("/user/logout","logout");
  1. 在页面直接输入这个URL即可注销

② 代码方式

调用subject的logout方法完成注销,页面访问该Controller的URL

七、授权的基本使用

7.1 校验角色

在自定义的Realm的doGetAuthorizationInfo方法中给当前主体赋予角色

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {// 获取主体中的身份信息String principal = (String) principalCollection.getPrimaryPrincipal();if ("jiuxiao".equals(principal)){ // 给jiuxiao用户赋予admin和user角色SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();// 增加admin和user角色simpleAuthorizationInfo.addRole("admin");simpleAuthorizationInfo.addRole("user");return simpleAuthorizationInfo;}return null;
}

① 编码方式

单个角色:使用subject中的hasRole方法

@Controller
@RequestMapping("/user")
public class UserController {@GetMapping("/save")@ResponseBodypublic String save(){Subject subject = SecurityUtils.getSubject();if (!subject.hasRole("admin")) { // 校验当前角色是否有admin这个角色return "权限不足";}else{return "访问成功";   }}
}

多个角色:使用subject中的hasAllRoles方法【这些角色都有才能访问】

@Controller
@RequestMapping("/user")
public class UserController {@GetMapping("/save")@ResponseBodypublic String save(){List<String> roles = Arrays.asList("admin", "user");if (SecurityUtils.getSubject().hasAllRoles(roles)) {return "访问成功";}else{return "权限不足";}}
}

② 注解方式

单个角色:直接在注解参数中写入对应的角色即可

@Controller
@RequestMapping("/user")
public class UserController {@GetMapping("/save")@ResponseBody@RequiresRoles("admin")public String save(){return "访问成功";}
}

多个角色:在注解中以数组的形式写入多个角色【这些角色都有才能访问】

@GetMapping("/save")
@ResponseBody
@RequiresRoles(value = {"admin","user"})
public String save(){return "访问成功";
}

7.2 校验权限字符串

在自定义的Realm的doGetAuthorizationInfo方法中给当前主体赋予权限字符串

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {String principal = (String) principalCollection.getPrimaryPrincipal();System.out.println("执行授权:"+principal);if ("jiuxiao".equals(principal)){SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();// 添加权限字符串simpleAuthorizationInfo.addStringPermission("user:update");simpleAuthorizationInfo.addStringPermission("product:create");return simpleAuthorizationInfo;}return null;
}

① 编码方式

调用subject中的isPermittedAll方法,参数为可变长参数(可以传一个或者多个)【如果是多个,那么这个主体需要拥有参数里面所有的权限字符串才能访问】

@Controller
@RequestMapping("/user")
public class UserController {@GetMapping("/save")@ResponseBodypublic String save(){Subject subject = SecurityUtils.getSubject();// if (subject.isPermittedAll("user:update")){ // 判断当前主体使用拥有对user资源的001实例的更新操作if (subject.isPermittedAll("user:update","product:update")){return "访问成功";}else{return "权限不足";}}
}

② 注解方式

单个权限字符串:直接在注解参数中写入需要校验的权限字符串即可

@GetMapping("/save")
@ResponseBody
@RequiresPermissions("user:update")
public String save(){return "访问成功";
}

多个权限字符串:在注解中以数组的形式写入多个权限字符串【当前主体主要拥有这些权限字符串才能访问】

@GetMapping("/save")
@ResponseBody
@RequiresPermissions(value = {"user:update","product:create"})
public String save(){return "访问成功";
}

③ 配置方式

在ShiroFilter过滤器使用perms进行权限的校验

map.put("/user/save","perms[user:update,product:delete]"); // 数组中添加权限字符串

如果权限不足,页面抛出401错误

在ShiroFilter中定义权限不足后跳转的URL

注意:定义权限不足跳转URL的方式只限制配置方式,别的方式都不能使用,权限不足时会抛出AuthorizationException异常

八、连接数据库完成授权

8.1 表设计:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for role_perms
-- ----------------------------
DROP TABLE IF EXISTS `role_perms`;
CREATE TABLE `role_perms`  (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`roleid` int(11) NULL DEFAULT NULL COMMENT '角色id',`permid` int(11) NULL DEFAULT NULL COMMENT '权限id',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Table structure for role_user
-- ----------------------------
DROP TABLE IF EXISTS `role_user`;
CREATE TABLE `role_user`  (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`roleid` int(11) NULL DEFAULT NULL COMMENT '角色id',`userid` int(11) NULL DEFAULT NULL COMMENT '用户id',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Table structure for t_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_perms`;
CREATE TABLE `t_perms`  (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`perm` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '权限字符串',`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '资源URL',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role`  (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '角色',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '用户名(身份信息)',`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '密码(凭证信息)',`salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '盐',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;

8.2 POJO

User

@Data
public class User {private Long id;private String username;private String password;private String salt;private List<Role> roles;
}

Role

@Data
public class Role {private Integer id;private String name;private List<Perms> perms;
}

Perms

@Data
public class Perms {private Integer id;private String perm;private String url;
}

8.3 授权角色

  1. Mapper接口与SQL语句
List<Role> selectRoleNameByUserId(String username);
<select id="selectRoleNameByUserId" resultType="com.jiuxiao.pojo.Role">select r.id,r.namefrom t_user uleft join role_user ru on ru.userid = u.idleft join t_role r on r.id = ru.roleidwhere u.username = #{username}
</select>
  1. Service
@Override
public List<Role> getRoleNameByUsername(String username) {return userMapper.selectRoleNameByUsername(username);
}
  1. 自定义Realm中进行授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {String principal = (String) principalCollection.getPrimaryPrincipal();List<Role> roles = userService.getRoleNameByUsername(principal);if(!CollectionUtils.isEmpty(roles)){SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();roles.forEach(role-> simpleAuthorizationInfo.addRole(role.getName()));return simpleAuthorizationInfo;}return null;
}

8.4 授权字符串

  1. Mapper接口与SQL语句
 List<String> selectPermByRoleId(Integer id);
<select id="selectPermByRoleId" resultType="string">select p.permfrom t_role rleft join role_perms rp on r.id = rp.roleidleft join t_perms p on rp.permid = p.idwhere r.id = #{id}
</select>
  1. Service
@Override
public List<String> getPermByRoleId(Integer id) {return userMapper.selectPermByRoleId(id);
}
  1. 自定义Realm进行授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {String principal = (String) principalCollection.getPrimaryPrincipal();List<Role> roles = userService.getRoleNameByUsername(principal);if(!CollectionUtils.isEmpty(roles)){SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();roles.forEach(role-> simpleAuthorizationInfo.addRole(role.getName()));roles.forEach(role->{List<String> perms = userService.getPermByRoleId(role.getId());simpleAuthorizationInfo.addStringPermissions(perms);});return simpleAuthorizationInfo;}return null;
}

九、Shiro与thymeleaf整合

9.1 导入依赖

<dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version>
</dependency>

9.2 配置方言

@Bean
public ShiroDialect shiroDialect(){return new ShiroDialect();
}

9.3 引入工作空间

<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

9.4 常用标签使用

<!-- 验证当前用户是否为"访客",即未认证的用户 -->
<p shiro:guest="">未认证</p><!-- 认证通过或者已经"记住我"的用户 -->
<p shiro:user="">hello</p><!-- 认证通过的用户 -->
<p shiro:authenticated="">hello</p><!-- 输出当前用户信息,通常为账号登录信息 -->
<p shiro:principal></p><!-- 判断当前用户是否拥有该角色 -->
<p shiro:hasRole="admin">拥有该角色</p><!-- 当前用户没有该角色认证通过 -->
<p shiro:lacksRole="user">没有改角色</p><!-- 判断当前用户是否拥有以下所有角色 -->
<p shiro:hasAllRoles="admin,user"></p><!-- 判断当前用户是否拥有以下任意一个角色 -->
<p shiro:hasAnyRoles="admin,user"></p><!-- 判断当前用户是否拥有以下权限字符串 -->
<p shiro:hasPermission="user:add"></p><!-- 当前用户没有该权限字符串认证通过 -->
<p shiro:lacksPermission="user:add"></p>


http://www.ppmy.cn/news/48412.html

相关文章

13.基于双层优化的电动汽车日前-实时两阶段市场竞标

MATLAB代码&#xff1a;基于双层优化的电动汽车日前-实时两阶段市场竞标 关键词&#xff1a;日前-实时市场竞标 电动汽车 双层优化 编程语言&#xff1a;MATLAB平台 内容简介&#xff1a;代码主要做的是电动汽车充电站市场竞标策略&#xff0c;采用双层优化模型对电动汽车…

内网穿透搭建

搭建内网穿透服务器搭建 1.frp frp官网 https://gofrp.org/ 简介 frp 是一个专注于内网穿透的高性能的反向代理应用&#xff0c;支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 条件 公网服务器&…

Linux网桥简介、入门与配置

开始之前先得介绍一下什么是网桥&#xff0c;这里我们假设大家已经知道了物理的交换机是工作在链路层的。交换机的主要任务是在链路层查找转发表&#xff08;mac地址与端口对应关系表&#xff09;&#xff0c;按照数据帧的目标mac地址&#xff0c;转发数据帧到相应的端口。那么…

4.24、半关闭、端口复用

UNIX网络编程卷1&#xff1a;套接字联网API&#xff08;第3版&#xff09; 等文件(提取码&#xff1a;q99x) 4.24、半关闭、端口复用 1.半关闭2.端口复用 1.半关闭 当 TCP 链接中 A 向 B 发送 FIN 请求关闭&#xff0c;另一端 B 回应 ACK 之后&#xff08;A 端进入 FIN_WAIT_…

Java最新面试题100道,包含答案示例(1-10题)

1. Java 中什么是 JVM&#xff1f; JVM&#xff08;Java Virtual Machine&#xff09;即 Java 虚拟机&#xff0c;是一种能够在不同平台上运行 Java 程序的虚拟计算机。JVM 是 Java 的核心组成部分&#xff0c;它负责解释 Java 代码并将其转换成可执行的二进制字节码指令&…

13.弹出层.下

学习要点&#xff1a; 1. 基础参数 本节课我们来开始了解 Layui 的内置模块&#xff1a;弹出层的方法演示。 一&#xff0e;基础参数 1. 参数&#xff0c;我们主要通过 open 方法来演示&#xff0c;其它方法类似&#xff1b; layer . open ({ // 标题 title : 标…

004+limou+C语言链表之“有头双向有循环链表”的实现

0、前要 如果您是初步认识链表&#xff0c;或是不能完全“手撕”一个简单的单链表&#xff0c;可以看看我的上一篇有关“无头单向非循环链表”的实现&#xff0c;再来看这一篇文章 1、“有头双向有循环链表”的实现 &#xff08;0&#xff09;首先阐述两个节点的区别 头节点…

PostMan笔记(三)自动化测试

1. 简介 Postman是一款功能强大的API开发工具&#xff0c;也是一款流行的自动化测试工具。它提供了多种测试功能&#xff0c;包括测试脚本、预请求脚本和测试集合等。 1.1 测试脚本 测试脚本是Postman中用于自动化测试的核心部分。它可以使用JavaScript语言编写&#xff0c;…