前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >深入理解Shiro

深入理解Shiro

作者头像
用户11097514
发布2024-05-30 18:05:29
发布2024-05-30 18:05:29
21400
代码可运行
举报
文章被收录于专栏:技术分享技术分享
运行总次数:0
代码可运行

Shiro

Shiro介绍

​ Apache Shiro(发音为“shee-roh”,日语中“城堡”的意思)是一个功能强大且易于使用的 Java 安全框架,可执行身份验证、授权、加密和会话管理,可用于保护任何应用程序 -从命令行应用程序、移动应用程序到最大的 Web 和企业应用程序。

Shiro 提供应用程序安全 API 来执行以下方面(我喜欢将它们称为应用程序安全的 4 个基石):

  • 身份验证 - 证明用户身份,通常称为用户“登录”。
  • 授权-访问控制
  • 密码学 - 保护或隐藏数据免遭窥探
  • 会话管理 - 每个用户的时间敏感状态

Shiro 还支持一些辅助功能,例如 Web 应用程序安全性、单元测试和多线程支持,但这些功能的存在是为了加强上述四个主要问题。

官网QuickStart

  1. Tutorial.java
代码语言:javascript
代码运行次数:0
复制
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.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Tutorial {

    private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);

    public static void main(String[] args) {
        log.info("My First Apache Shiro Application");

        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        //1. 获取当前的用户对象Subject
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)

        //2. 通过当前用户拿到session
        Session session = currentUser.getSession();
            session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        //3. 判断当前的用户是被认证
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);  //设置记住我

            try {
                currentUser.login(token);   //执行了登录操作(暂时看不到 )
            } catch (UnknownAccountException uae) {
                //未知的账户
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                //
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        //4. 测试当前用户是否有角色
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        //粗粒度       (暂时为止)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }
        //细粒度
        /**
         * 这些权限都是在shiro.ini中的
         * admin = *
         * schwartz = lightsaber:*
         * goodguy = winnebago:drive:eagle5
         */
        //是否拥有更高的权限
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!注销
        currentUser.logout();

        //结束
        System.exit(0);
    }
}

我们当前使用的shiro很多的用法都可以从QuickStart中了解到

  1. shiro.ini
代码语言:javascript
代码运行次数:0
复制
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5

实现

导入依赖

代码语言:javascript
代码运行次数:0
复制
<!--shiro重点依赖-->
 <dependency>
           <groupId>org.apache.shiro</groupId>
           <artifactId>shiro-spring</artifactId>
           <version>1.4.1</version>
       </dependency>

       <dependency>
           <groupId>org.apache.shiro</groupId>
           <artifactId>shiro-core</artifactId>
           <version>1.4.1</version>
       </dependency>
       <!--shiro 和前端的交互-->
       <dependency>
           <groupId>com.github.theborakompanioni</groupId>
           <artifactId>thymeleaf-extras-shiro</artifactId>
           <version>2.1.0</version>
       </dependency>



    <!--基础开发依赖-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-thymeleaf</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>

       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <optional>true</optional>
       </dependency>



       <!--sql驱动-->
       <dependency>
           <groupId>com.mysql</groupId>
           <artifactId>mysql-connector-j</artifactId>
           <version>8.0.32</version>
       </dependency>
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>2.1.2</version>
       </dependency>
       <dependency>
           <groupId>log4j</groupId>
           <artifactId>log4j</artifactId>
           <version>1.2.17</version>
       </dependency>

项目实现背景介绍

代码语言:javascript
代码运行次数:0
复制
 我们日常使用的所有应用程序都存在着很多不安全的问题,为了解决这些问题,我们学习SpringSecurity、shiro等技术,为实现密码安全问题,我们会使用md5,md5盐值加密....一系列操作

​ 在应用程序中,为了更好的盈利,我们会将普通用户和会员用户进行区分,同时,对于两者之间展现的页面,功能也会有所不同。那么如何实现这种不同

​ 本次练习项目就会通过shiro来实现这些操作

功能实现介绍

  • vip1身份的用户会展示有关vip1的界面,以及普通用户界面
  • vip2身份的用户会展示有关vip1的界面,以及普通用户界面
  • 普通用户只会展示普通用户界面
  • 登录、退出
  • 提示用户名或者密码错误
  • ….

数据库信息展示

image
image

未登录界面展示

登录及其错误提示

vip1用户所在页面展示

普通用户页面展示

关键代码详解

首先,我们导入以来完成后,需要进行配置自定义配置shiro类,同时用@Configuration注解标注

代码语言:javascript
代码运行次数:0
复制
@Configuration
//自定义配置类,实现shiro的配置
public class shiroConfig{
		//1. shiroFilterFactoryBean
        //2. DefaultWebSecurityManager 安全管理器(关联Realm)
        //3. 创建realm对象
}

在shiro配置类中有三个内置类是十分重要的,他们分别是

  • ShiroFilterFactoryBean
  • DefaultWebSecurityManager
  • realm对象

他们决定了shiro的工作机制及流程

如图所示

Realm :

Realm : 作为接收需要接受的安全数据的对象,起着与我们底层的数据交互的作用,它通过封装安全数据,然后放入在Spring的容器中

代码语言:javascript
代码运行次数:0
复制
//3. 创建realm对象,需要自定义类,然后交给spring托管(放到bean中)
@Bean(name = "userRealm")
public UserRealm userRealm(){
    return new UserRealm();
}
DefaultWebSecurityManager :

DefaultWebSecurityManager : 作为安全管理员,他的作用就是来关联我们放置在容器中Realm对象,同时,将自身再封装成为Bean交给spring容器来托管,然后等待ShiroFilterFactoryBean的调用 。可以看出他是shiro的核心,相当于我们springMVC中的DispatcherServlet

代码语言:javascript
代码运行次数:0
复制
//2. DefaultWebSecurityManager 安全管理器(关联Realm)
//@Qualifier("userRealm")中的内容就是下面第三步中的@Bean中name属性
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    //用securityManager来关联realm
    securityManager.setRealm(userRealm);
    //因为我们第三步已经将userRealm交给了spring接管,所以需要传参数来得到,而不是直接new
    return securityManager;
}
ShiroFilterFactoryBean :

​ **ShiroFilterFactoryBean : 作用就是继续关联安全管理器DefaultWebSecurityManager,然后同时将自己也封装成bean **

代码语言:javascript
代码运行次数:0
复制
//1. shiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    //关联DefaultWebSecurityManager安全管理器
    bean.setSecurityManager(defaultWebSecurityManager);
    /**
     * 添加内置过滤器
     * ● anon : 无需认证即可访问
     * ● authc :必须认证才能访问
     * ● user :必须拥有记住我才能访问
     * ● perms  : 拥有对某个资源的权限才能访问
     * ● role  : 拥有某个角色权限才能访问
     */
    //拦截的请求-----------------------------------
    Map<String, String> filterMap = new LinkedHashMap<>();
    filterMap.put("/vipFirst","perms[vip1]");
    filterMap.put("/vipSecond","perms[vip2]");

    bean.setFilterChainDefinitionMap(filterMap);//他的参数是从map集合中拿的,所以需要提前设置一个集合
    //如果没有权限,设置跳转页面(登录的请求)
    bean.setLoginUrl("/login");
    //未授权的请求
    //bean.setUnauthorizedUrl("");

    //-------------------------------------------
    return bean;
}

他的拦截请求会通过一个map集合来操作,但是shiro也存在很多的内置过滤器 , 通过这些过滤器,我们就可以实现请求过滤操作

代码语言:javascript
代码运行次数:0
复制
 * ● anon : 无需认证即可访问
 * ● authc :必须认证才能访问
 * ● user :必须拥有记住我才能访问
 * ● perms  : 拥有对某个资源的权限才能访问
 * ● role  : 拥有某个角色权限才能访问

​ 在ShiroFilterFactoryBean中我们可以做很多需要的操作比如:

  • 请求的拦截
  • 未授权用户页面的跳转
  • ….

上述三者的执行顺序虽然是从:S - > D - > R (简写)

但是我们在按逻辑写的时候确实 从 : R- > D -> S

对外核心Subject

与应用程序的代码直接进行交互的对象就是Subject, 它代表的是当前用户,这个用户不仅仅值得是一个具体的人,而是与当前应用交互的任何事物。与Subject进行交互,他就会将所有的东西全都委托给我们的安全管理员(DefaultWebSecurityManager ),他才是真正的执行者。

对内核心Realm

为什么这里我将他作为对内核心,因为我们所有需要进行安全操作的事情都在他的实现类中完成

代码语言:javascript
代码运行次数:0
复制
//3. 创建realm对象
    //实现的两个方法就是 springsecurity中授权和认证

public class UserRealm extends AuthorizingRealm {
	//认证
   // ....
    //授权
}

比如:

  • 对用户进行授权
  • 认证
  • 密码保护

认证

代码语言:javascript
代码运行次数:0
复制
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("===> 执行了认证方法");
        //获取当前的令牌,及其其中的信息
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;

        // 获取当前的用户 , 封装用户的登录数据(在controller的登录方法中实现)
        User user = userService.selectUser(userToken.getUsername());

        //判断是否与数据库中的相同,如果不相同
        if (!user.getUsername().equals(userToken.getUsername())){
            return null; //就会抛出异常
        }
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        session.setAttribute("loginUser",user);
        //密码认证, shrio来帮助我们实现
        AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),user.getDept());
        return info;
    }

授权

代码语言:javascript
代码运行次数:0
复制
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("===> 执行了授权方法");
        //执行授权的功能
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //将用户所具有的权限存放在数据库中,然后登录时获取用户的全部信息
        Subject subject = SecurityUtils.getSubject();
        //在用户进行认证时我们进行这个操作【AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),user.getDept());】
        //通过他我们就可以拿到数据
        User user = (User) subject.getPrincipal();
        //设置当前用户的权限
        authorizationInfo.addStringPermission(user.getDept());
        //authorizationInfo.addRole("vip1");

        return authorizationInfo;
    }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-02-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Shiro
    • Shiro介绍
    • 官网QuickStart
    • 实现
      • 导入依赖
      • 项目实现背景介绍
      • 功能实现介绍
    • 关键代码详解
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档