Shiro

Shiro概述

1 简介

Apache Shiro是一个强大且易用的Java安全框架!可以完成身份验证、授权、密码和会话管理!

Shiro不仅可以用在 JavaSE 环境中,也可以用在 JavaEE 环境中!

官网: http://shiro.apache.org/

2 功能

img

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

3 从外部看

应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个Shiro应用:

应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;

我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入!

4 外部架构

img

Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;

SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;

SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);

SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能!

Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的!

5 认证流程

在这里插入图片描述

用户 提交 身份信息、凭证信息 封装成 令牌 交由 安全管理器 认证!

SpringBoot 集成 Shiro

1 编写配置文件

1、IDEA新建一个 springboot 模块;

2、导入 SpringBoot 和 Shiro 整合包的依赖;

Apache Shiro 在 1.8.0 之前,当将 Apache Shiro 与 Spring Boot 配合使用时,特制的 HTTP 请求可能会导致身份验证绕过。用户应该更新到Apache Shiro 1.8.0。

<!--SpringBoot 和 Shiro 整合包-->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.6.0</version>
</dependency>

下面是编写配置文件Shiro 三大要素

  • subject -> ShiroFilterFactoryBean
  • securityManager -> DefaultWebSecurityManager
  • realm

实际操作中对象创建的顺序 : realm -> securityManager -> subject

3、编写自定义的 realm ,需要继承 AuthorizingRealm

//自定义的 Realm
public class UserRealm extends AuthorizingRealm {
    //授权

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //打印一个提示
        System.out.println("执行了授权方法");
        return null;
    }

    //认证

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //打印一个提示
        System.out.println("执行了认证方法");
        return null;
    }
}

4、新建一个 ShiroConfig配置文件(按照上面说的创建过程来写);

创建 Realm

// 1、创建一个Realm类,需要自定义
@Bean(name = "userRealm")
public UserRealm userRealm() {
    return new UserRealm();
}

创建 securityManager:可以看到这里需要的参数为一个Realm类,可以与上面所创建的Realm类对应起来!

// 2、DefaultWebSecurityManager
@Bean(name = "defaultWebSecurityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    // 关联Realm
    securityManager.setRealm(userRealm);
    return securityManager;
}

创建 subject

//1、 subject -> ShiroFilterFactoryBean
// @Qualifier("securityManager") 指定 Bean 的名字为 securityManager

@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager securityManager){
    ShiroFilterFactoryBean subject = new ShiroFilterFactoryBean();
    //设置安全管理器
    //需要关联 securityManager ,通过参数把 securityManager 对象传递过来
    subject.setSecurityManager(securityManager);
    return subject;
}

2 搭建简单测试环境

  1. 新建一个登录页面;
  2. 新建一个首页;
    • 首页上有三个链接,一个登录,一个增加用户,一个删除用户;
  3. 新建一个增加用户页面;
  4. 新建一个删除用户页面;
  5. 编写对应的 Controller;

3 使用Shiro

1、登录拦截

在上面的 getShiroFilterFactoryBean 方法中加上需要拦截的登录请求;

public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    // 设置安全管理器
    shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
    //添加 Shiro 的内置过滤器=======================
    /*
            anon : 无需认证,就可以访问
            authc : 必须认证,才能访问
            user : 必须拥有 “记住我”功能才能用
            perms : 拥有对某个资源的权限才能访问
            role : 拥有某个角色权限才能访问
         */
    Map<String, String> filterMap = new LinkedMap();
    //        filterMap.put("/add", "authc");
    //        filterMap.put("/update", "authc");
    // 支持通配符
    filterMap.put("/user/**", "authc");

    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
    // 设置登录的请求
    shiroFilterFactoryBean.setLoginUrl("/toLogin");
    // 设置未授权的请求
    shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
    return shiroFilterFactoryBean;
}

测试:点击 addUser 链接,不会跳到 addUser 页面,而是跳到登录页,拦截成功!

2.、用户认证

1、在 Controller 中写一个登录的控制器;

@RequestMapping("login")
public String login(String username, String password, Model model) {
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    try {
        subject.login(token); // 执行登录方法
        return "redirect:/index";
    } catch (UnknownAccountException e) {
        model.addAttribute("error", "用户名不存在");
        return "login";
    } catch (IncorrectCredentialsException e) {
        model.addAttribute("error", "密码不存在");
        return "login";
    }
}

2、重启,测试,可以发现当用户名错误之后正确弹出“用户名不存在”的信息,并且在弹出信息之前,还弹出了“执行了认证方法”的信息,可以看出,是先执行了 自定义的 UserRealm 中的 AuthenticationInfo 方法,也就是认证方法,再执行了登录的相关操作

3、修改 UserRealm 中的 AuthenticationInfo以获取用户信息;

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    //打印一个提示
    System.out.println("执行了认证方法");

    // 用户名密码(暂时先自定义一个做测试)
    String name = "root";
    String password = "1234";

    //通过参数获取登录的控制器中生成的 令牌
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    //用户名认证
    if (!token.getUsername().equals(name)){
        // return null 就表示控制器中抛出的相关异常
        return null;
    }
    //密码认证, Shiro 自己做,为了避免和密码的接触
    //最后返回一个 AuthenticationInfo 接口的实现类,这里选择 SimpleAuthenticationInfo
    // 三个参数:获取当前用户的认证 ; 密码 ; 认证名
    return new SimpleAuthenticationInfo(principal, user.getPwd(), this.getName());
}

测试:

  • 输入错误的密码后,可以发现登录被拦截了,而且抛出了对应的异常(虽然乍一眼看上去,Controller类中编写的登录验证和自定义Realm类中认证方法并无多大关联,但是它们俩确确实实联系在一起了!)

  • 输入正确的密码登录成功,且可以访问add和update两个页面!

3、退出登录

在控制器中添加一个退出登录的方法;

//退出登录
@RequestMapping("/logout")
public String logout(){
    Subject subject = SecurityUtils.getSubject();
    subject.logout();
    return "login";
}

4、授权实现

1、授权的拦截主要在ShiroConfig配置类中进行配置,也就是需要在内置过滤器中进行配置:

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    // 设置安全管理器
    shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
    //添加 Shiro 的内置过滤器=======================
    /*
            anon : 无需认证,就可以访问
            authc : 必须认证,才能访问
            user : 必须拥有 “记住我”功能才能用
            perms : 拥有对某个资源的权限才能访问
            role : 拥有某个角色权限才能访问
         */
    Map<String, String> filterMap = new LinkedMap();
    //        filterMap.put("/add", "authc");
    //        filterMap.put("/update", "authc");

    // 授权(正常的情况之下,没有授权会跳转到未授权页面)
    // 当用户访问/user/add页面时,需要为user用户且拥有add权限时才能访问
    filterMap.put("/user/add", "perms[user:add]");
    filterMap.put("/user/update", "perms[user:update]");
    
    filterMap.put("/user/**", "authc");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
    shiroFilterFactoryBean.setLoginUrl("/toLogin");

    // 设置未授权的请求(当用户没有权限访问时跳转到未授权页面)
    shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
    return shiroFilterFactoryBean;
}

2、编写未授权页面的跳转Controller(主要用于测试):

@RequestMapping("/unauthorized")
@ResponseBody
public String unauthorized() {
    return "未经过授权的用户暂时无法访问该页面!";
}

3、测试:当用户没有权限时,访问页面将会返回信息“未经过授权的用户暂时无法访问该页面”!而通过该测试也可以发现,**当用户进行页面跳转时,会进入到自定义Realm中AuthorizationInfo方法,也就是授权方法!**我们可以通过在授权方法中对所有用户进行授权:

info.addStringPermission("user:add");

以上代码给所有访问页面的用户都基于了访问权限(add页面的访问权限)!当然,真实情况下是不可能给所有用户都一视同仁的,这个权限信息按照逻辑,应该在数据库里拿,所以这边对实体类进行修改!

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String pwd;
    // 保存用户授权信息
    private String perms;
}

在AuthenticationInfo的认证方法中,将用户信息返回:

// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    System.out.println("doGetAuthenticationInfo==>认证");
    // 这里需要认证用户名以及密码(链接真实数据库)

    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    // 用户的信息
    User user = userService.queryUserByName(token.getUsername());
    if (user == null) {
        return null; // UnknownAccountException
    }
    // 返回用户信息
    return new SimpleAuthenticationInfo(user, user.getPwd(), "");
}

在授权方法中,用SecurityUtils获得Subject对象,拿到用户信息:

// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("doGetAuthorizationInfo==>授权");
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    // 在认证方法中返回的用户信息对象通过subject拿到
    Subject subject = SecurityUtils.getSubject();
    User currentUser = (User) subject.getPrincipal();

    info.addStringPermission(currentUser.getPerms());
    //        info.addStringPermission("user:add");
    return info;
}

数据库中的数据如下:

image-20220407170653293

测试:当用户ayin访问add页面时,可以顺利访问,但是因为未拥有update的访问权限,所以访问update页面时会被拦截!

4、Shiro整合Thymeleaf

在首页中,会显示一个登录按钮,点进按钮之后会跳转至登录页,输入用户名和密码之后,跳转至已登录状态的首页,但这里存在一个问题:登录按钮理应在未登录状态下显示,而此时登录按钮无论是在登录状态或者是未登录状态下都会显示!

使用Shiro整合Thymeleaf可以解决这类问题,当然,在进行操作之前,也是需要导入所需的依赖:

<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.1.0</version>
</dependency>

导入依赖之后,在所需要修改的主页导入命名空间,导入命名空间的原因是:

xmlns:shiro="http://www.pollix.at/thymeleaf/shiro

关于shiro标签的使用方法:

<shiro:authenticated>        登录之后
<shiro:notAuthenticated>        不在登录状态时
<shiro:guest>            用户在没有RememberMe时
<shiro:user>            用户在RememberMe时
<shiro:hasAnyRoles name="abc,123" >    在有abc或者123角色时
<shiro:hasRole name="abc">        拥有角色abc
<shiro:lacksRole name="abc">        没有角色abc
<shiro:hasPermission name="abc">    拥有权限资源abc
<shiro:lacksPermission name="abc">    没有abc权限资源
<shiro:principal>        显示用户身份名称
<shiro:principal property="username"/>         显示用户身份中的属性值

如上所示,我们这里所使用的标签为shiro:hasPermission以及shiro:notAuthenticated权限,不过这里有一些问题:在shiro:hasPermission中直接写上user:add,这里的冒号会报错,不过并不意向测试结果,而在双引号再加上单引号,主要的目的是为了避免报错,因为即使这样,测试结果也并不会出错,单纯为了讨好强迫症而已!

<h1 th:text="${msg}"></h1>
<div shiro:notAuthenticated>
    <p>
        <a th:href="@{/toLogin}">登录</a>
    </p>
</div>

<div shiro:hasPermission="'user:add'">
    <h2><a th:href="@{/user/add}">add</a></h2>
</div>
||
<div shiro:hasPermission="'user:update'">
    <h2><a th:href="@{/user/update}">update</a></h2>
</div>

<h1><a th:href="@{/logout}">注销</a></h1>

号会报错,不过并不意向测试结果,而在双引号再加上单引号,主要的目的是为了避免报错,因为即使这样,测试结果也并不会出错,单纯为了讨好强迫症而已!

<h1 th:text="${msg}"></h1>
<div shiro:notAuthenticated>
    <p>
        <a th:href="@{/toLogin}">登录</a>
    </p>
</div>

<div shiro:hasPermission="'user:add'">
    <h2><a th:href="@{/user/add}">add</a></h2>
</div>
||
<div shiro:hasPermission="'user:update'">
    <h2><a th:href="@{/user/update}">update</a></h2>
</div>

<h1><a th:href="@{/logout}">注销</a></h1>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Dubbo Shiro是一种基于Dubbo和Shiro框架的权限管理解决方案。它可以帮助开发人员快速实现基于角色的访问控制和权限管理。下面是一个简单的演示: 1.在Dubbo服务提供者中配置Shiro过滤器链 ```java @Bean public FilterRegistrationBean<DelegatingFilterProxy> shiroFilter() { FilterRegistrationBean<DelegatingFilterProxy> filterRegistration = new FilterRegistrationBean<>(); filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); filterRegistration.addUrlPatterns("/*"); filterRegistration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ERROR); return filterRegistration; } @Bean("shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/dubbo/**", "authc, roles[dubbo]"); filterChainDefinitionMap.put("/**", "anon"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } ``` 2.在Dubbo服务消费者中配置Shiro ```java @Bean public ReferenceBean<DemoService> demoService() { ReferenceBean<DemoService> referenceBean = new ReferenceBean<>(); referenceBean.setInterface(DemoService.class); referenceBean.setUrl("dubbo://localhost:20880"); referenceBean.setVersion("1.0.0"); referenceBean.setTimeout(5000); referenceBean.setRetries(3); referenceBean.setCluster("failover"); referenceBean.setLoadbalance("roundrobin"); referenceBean.setApplication(applicationConfig()); referenceBean.setRegistry(registryConfig()); referenceBean.setInterface(DemoService.class); referenceBean.setCheck(false); referenceBean.setFilter("shiro"); return referenceBean; } ``` 3.在Shiro配置文件中配置权限 ```ini [users] admin=admin,admin guest=guest,guest [roles] dubbo=guest ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值