Shiro安全框架简介

news2025/1/21 2:56:30

 一、权限管理

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 {
    /*
     授权
    */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /*
     认证
    */
    @Override
    protected 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.当前Realm
        return 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 {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
    }
}
  1. 编写shiro和springboot整合的配置
@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 将SecurityManager设置到Filter中
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        return shiroFilterFactoryBean;
    }
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 给安全管理器设置Realm
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }
    @Bean
    public 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 {
    
    @Autowired
    private 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";
        else
            return "redirect:/doRegister";
    }
}
  1. 在ShiroFilter放过这些URL路径
map.put("/user/register","anon");
map.put("/user/doRegister","anon");
  1. service中完成加密和散列盐,调用Mapper完成注册
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public 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 {
    
    @Autowired
    private 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());
    }
    // 查询不到直接返回null
    return 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")
    @ResponseBody
    public String save(){
        Subject subject = SecurityUtils.getSubject();
        if (!subject.hasRole("admin")) { // 校验当前角色是否有admin这个角色
            return "权限不足";
        }else{
          return "访问成功";   
        }
    }
}

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

@Controller
@RequestMapping("/user")
public class UserController {
    
    @GetMapping("/save")
    @ResponseBody
    public 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")
    @ResponseBody
    public 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.name
    from t_user u
    left join role_user ru on ru.userid = u.id
    left join t_role r on r.id = ru.roleid
    where 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.perm
    from t_role r
    left join role_perms rp on r.id = rp.roleid
    left join t_perms p on rp.permid = p.id
    where 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.coloradmin.cn/o/434534.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学成在线笔记+踩坑(1)——项目思路、架构、父工程和基础工程,Gogs使用

【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线牛客面试题 目录 简历-技术架构 项目预览 后台页面 课程管理 媒资管理页面上传视频 新增课程 课程管理页提交审核 审核员审核后发布课程 前台页面 主页 验证码登录…

uniapp系列-超详细教你在uni-app+vue3里通过web-view组件传递信息打开H5页面写入localstorage并解决兼容性

web-view是什么 web-view 是一个 web 浏览器组件&#xff0c;可以用来承载网页的容器&#xff0c;会自动铺满整个页面&#xff08;nvue 使用需要手动指定宽高&#xff09;。点击这里直达官网文档点击这里下载我的代码demo本文最下面还有一些常见或者奇怪问题解决方案哦~ 为什…

GMRES算法及Matlab程序

关于GMRES的总结 一些理论以及代码 首先是我们要解决的问题&#xff0c;也就是最常见的问题&#xff0c;即求解 A x b Axb Axb Galerkin条件 krylov子空间 我们所做的GMRES实际上就是在Galerkin条件的扩张下&#xff0c;n o r m ( r ) normnorm不断减小的一个过程。这也是…

家用洗地机哪种好?性价比高的洗地机推荐

选择清洁家电时&#xff0c;首先需要考虑你需要清洁的区域和清洁的方式。如果你需要清洁的是地面&#xff0c;那么洗地机可能是一个不错的选择。洗地机的优势在于它可以自动清洁地面&#xff0c;并且可以在较短的时间内完成清洁工作。相比于其他的清洁家电&#xff0c;洗地机的…

设计链表(链表篇)

你可以选择使用单链表或者双链表&#xff0c;设计并实现自己的链表。 单链表中的节点应该具备两个属性&#xff1a;val 和 next 。val 是当前节点的值&#xff0c;next 是指向下一个节点的指针/引用。 如果是双向链表&#xff0c;则还需要属性 prev 以指示链表中的上一个节点…

Linux_红帽8学习笔记分享_5

Linux_红帽8学习笔记分享_5 文章目录 Linux_红帽8学习笔记分享_51. UMASK反掩码1.1如何查看反掩码umask1.2 UMASK反掩码的作用1.2.1对于目录来说1.2.2对于文件来说 1.3如何修改UMASK反掩码1.4普通用户反掩码的测试 2.whereis的使用3. SUID权限弥补(主要针对文件,所有者执行位变…

第14章_数据结构与集合源码

第14章_数据结构与集合源码 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 本章专题与脉络 1. 数据结构剖析 我们举一个形象的例子来理解数据结构的作用&#xff1a; 战场&#xff1a;程序运行…

认识Object类和深浅拷贝

本文介绍了Object类以及Object类部分方法,toString方法,equals和hashCode方法(重写前和重写后的对比),getClass方法,clone方法,以及拷贝新对象时会出现的深浅拷贝, 内容较长,耗时一天,建议收藏后观看~ Object类和深浅拷贝 一.初识Object类1.Object类接收所有子类实例2.Object类…

【C 字符串】02 常用字符串函数(命令行参数)

Navigator 一、strlen()函数—统计长度二、strcat()函数—拼接三、strncat()函数—strcat()的升级四、strcmp()和strncmp()—比较五、strcpy()和strncpy()—拷贝六、sprintf()函数—合并多个字符串七、其他可能用到的字符串函数八、ctype.h中的字符函数九、把字符串转换为数字十…

(03)基础强化:静态类静态成员,静态构造函数,抽象类抽象成员,值类型和引用类型,Ref

一、静态成员 1、方法重写注意事项 1&#xff09;子类重写父类方法时&#xff0c;必须与父类保持一致的方法签名与返回值类型。即: 方 法名、返回值类型、参数列表都必须保持一致。[访问修饰符也得一致] 2&#xff09;“方法签名”:一般是指…

VScode---visual stdio code快速安装教程(Windows系统)

1.下载VSCode安装包&#xff0c;官网传送门https://code.visualstudio.com/ 选择Windows下的User Installer 64 bit 直接下载速度如果很慢&#xff0c;在浏览器或者下载软件中就可以看到这么一个下载地址了&#xff0c;复制链接地址&#xff08;如下图箭头所指&#xff09;。 …

本节作业之5秒后自动关闭广告、倒计时、发送短信、5秒之后自动跳转页面、获取URL参数数据

本节作业之5秒后自动关闭广告、倒计时、发送短信、5秒之后自动跳转页面、获取URL参数数据 1 5秒后自动关闭广告2 倒计时3 发送短信4 5秒之后自动跳转页面5 获取URL参数数据 1 5秒后自动关闭广告 <!DOCTYPE html> <html lang"en"> <head><meta …

管理系统的前端模板(vue2+Element UI)

目录 前言 一、模板展示图 二、获取的方式及操作运行步骤 &#xff08;一&#xff09;获取方式 &#xff08;二&#xff09;操作步骤 1.下载安装node.js 2.下载完成解压缩后在idea的里面打开终端。 3.输入下载相关依赖的命令 4.运行项目的命令 5.然后把给到的地址…

腾讯云渲染实战

UE使用流渲染技术的主要原因是为了提高渲染效率和降低成本。流渲染技术可以将渲染任务分配到多个计算节点上进行并行处理&#xff0c;从而加快渲染速度。同时&#xff0c;流渲染技术还可以将渲染任务分配到云端进行处理&#xff0c;减少本地计算机的负担&#xff0c;降低成本。…

又一科研利器诞生!能对话的论文阅读器,hammerScholar

文&#xff5c;智商掉了一地 hammerScholar 新升级&#xff0c;用对话式读论文工具提升科研生产力~ 不得不说&#xff0c;自从 AIGC 这个概念出现以来&#xff0c;它极强的内容理解与生成能力也推动着各种生产力工具层出不穷&#xff0c;除了一些浏览器和代码插件以外&#xff…

SpringBoot的Interceptor拦截器的简介和实际使用

拦截器&#xff08;Interceptor&#xff09; 概念&#xff1a;是一种动态拦截方法调用的机制&#xff0c;类似于过滤器。Spring框架中提供的&#xff0c;用来动态拦截控制器方法的执行。 作用&#xff1a;拦截请求&#xff0c;在指定的方法调用前后&#xff0c;根据业务需要执行…

干货 | 关于等效电路图

等效电路图是电路原理中非常重要的一个概念&#xff0c;在电子工程、通信工程和电力工程等领域中经常被使用。等效电路图是指将一个复杂的电路简化成一个简单的电路&#xff0c;同时保持电路的等效性质&#xff0c;以便于分析和设计电路。在本文中&#xff0c;我们将详细介绍等…

回溯法 思想

回溯法&#xff08;back tracking&#xff09;&#xff08;探索与回溯法&#xff09;是一种选优搜索法&#xff0c;又称为试探法&#xff0c;按选优条件向前搜索&#xff0c;以达到目标。但当探索到某一步时&#xff0c;发现原先选择并不优或达不到目标&#xff0c;就退回到上一…

ChatGPT安全性受质疑 国家网信办发布《生成式人工智能服务管理办法(征求意见稿)》

你是否曾经和一款人工智能对话&#xff1f;它们似乎能够理解你的问题&#xff0c;并给出令人满意的答案。ChatGPT是目前最流行的人工智能。它是由OpenAI开发的一款基于GPT技术的自然语言处理模型。 通过训练大量的文本数据&#xff0c;ChatGPT可以理解并回答用户的问题&#xf…

Excel vba直接调用斑马打印机进行打印代码

1.难点 1.1 vba 对zebra打印机调用方法open 1.2 zebra打印机默认支持UTF8 编码方式&#xff0c;对应编码命令为CI28; 支持GB2312 GB18030 ASCII码编码方式&#xff0c;对应编码命令为CI26 1.3 VBA对中文只支持GB2312, 而excel 是默认支持UTF8的&#xff0c; excel 与VBA编…