shiro入门

近一周看了关于shiro的知识.这篇文章仅供完全没有了解过安全框架的人查阅.因为我也是第一次接触安全框架.本文通过三种类型的项目(javase,springmvc,springboot)来介绍shiro,关于细节的配置在这里不会过多描述,甚至不全面.这类的东西网上很多.首先我们考虑下,在一个生态环境良好的系统中,避免不了出现这样的场景,为了分别不同的用户,我们有账号密码,当然我们不能让用户每次发出请求都传入账号密码,所以我们会有登录,登录后通过session或者其他token的技术,在服务端保存用户的登录状态.我们假设这个系统是一个商城平台,平台订单的统计只能是管理员才可以查看.而查看商品则是普通用户或者管理员都可以查看,这就会产生角色.如果系统足够健壮,我们往往还需要更细粒度的控制,例如对订单管理员进行拆分,A类型的管理员可以对订单进行查看,B类型的管理员可以对订单进行修改和删除,这就产生的权限

用户 一对多 角色 一对多 权限

接下来,我们来分析下对于一个安全框架他必须要有的功能然后我们对应shiro中的组件

登录:Authentication 通过账户和密码

授权:Authorization 这个授权包括了 角色和权限

会话:Session Manager 用户登录后,我们要保持当前用户到内存中,不然我们是不是每次都要登录

缓存:Caching 当我们已经获取过用户的身份,角色,权限后.下次在需要不必再次查询

....以上为主要内容

然后我们在了解关于以上功能产生或者需要的类

安全管理器:SecurityManager  shiro的核心,你要登陆验证等等都需要这个

用户/主体:Subject 当用户登录后,产生一个Subject到内存也就是Session Manager中,他代表当前用户,其中包含账户密码,角色,权限

数据源:Realm 我们从哪里拿到用户的信息.shiro给了我们几个默认的实现,其中包括从文件读取,从数据库读取,但是这些都需要我们按照他的规则,当然我们可以自定义realm

....以上内容我们只要关注的重点在于Realm 因为我们大多数会自定义Realm

javase使用shiro

 //登录用户,拿到角色判断角色的权限
    @Test
    public void testIsPermitted() {
        //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
        Factory<SecurityManager> factory =
                new IniSecurityManagerFactory("classpath:shiro-permission.ini");
        //2、得到SecurityManager实例 并绑定给SecurityUtils
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = null;
     //判断是否登录
if (!subject.isAuthenticated()) { token = new UsernamePasswordToken("zhang", "123"); } subject.login(token); /*is不抛出异常*/ //判断拥有权限:user:create Assert.assertTrue(subject.isPermitted("user:create")); //判断拥有权限:user:update and user:delete Assert.assertTrue(subject.isPermittedAll("user:update", "user:delete")); //判断没有权限:user:view Assert.assertFalse(subject.isPermitted("user:view")); /*check抛出异常*/ //断言拥有权限:user:create subject.checkPermission("user:create"); //断言拥有权限:user:delete and user:update subject.checkPermissions("user:delete", "user:update"); }
[users]
zhang=123,common,admin
wang=123,common
[roles]
admin=user:create,user:delete,user:update,user:read
common=user:read

在配置文件中按照指定的规则配置用户名,密码,角色,然后在配置roles角色的权限.应该是比较好理解.但是实际的系统大多数需要从数据库取出数据

    @Test
    public void readIniRealmJdbc() throws Exception {
        //1、获取SecurityManager工厂,此处使用Ini配置文件初始化
        IniSecurityManagerFactory iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");

        //2、得到SecurityManager实例 并绑定给SecurityUtils
        SecurityManager securityManager = iniSecurityManagerFactory.getInstance();

        //获取 SecurityManager 并绑定到 SecurityUtils,这是一个全局设置,设置一次即可;
        SecurityUtils.setSecurityManager(securityManager);

        //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
        Subject subject = SecurityUtils.getSubject();


        //账号密码验证器
        UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
        try {
            //4、登录,即身份验证
            subject.login(token);
            //判断拥有角色:role1
            Assert.assertTrue(subject.hasRole("admin"));

            Assert.assertTrue(subject.isPermitted("user:update"));
            Assert.assertTrue(subject.isPermittedAll("user:update","user:create"));

        } catch (Exception e) {
            //5、身份验证失败
            System.out.println("登录认证失败:" + e.getMessage());
        }
        //6、退出
        subject.logout();
    }
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm

dataSource=com.alibaba.druid.pool.DruidDataSource

dataSource.driverClassName=com.mysql.cj.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro?serverTimezone=GMT%2B8&characterEncoding=utf8&useUnicode=true&useSSL=false
dataSource.username=root
dataSource.password=root

jdbcRealm.dataSource=$dataSource
# 要加上,否则不会查询角色的权限
jdbcRealm.permissionsLookupEnabled=true
securityManager.realms=$jdbcRealm

这里我们使用jdbcRealm从数据库中查询用户角色和权限.在配置文件中我们可以看出,这里我们指定了数据源.其余的什么都没指定.这也就表达,这个jdbcRealm会自动查询数据库.并且使用固定的SQL语句

 所以使用JDBCRealm必须按照shiro的表结构创建表和添加数据.

那么万一我们的数据表结构不是shiro指定的,这样我们就需要自定义Realm

 除了MyRealm1 和 MyRealm2其他都是shiro定义的Realm我们这里需要登录+授权所以选择AuthorizingRealm

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

//这里有个小技巧,如果你选择自定义XXX首先你需要选择一个正确的父类,然后实现他的方法
//实现方法时,首先考虑这个方法要做什么,然后在看返回值,返回值要什么,你创建一个什么
//剩下的细节,创建的返回值需要设置什么这就要我们通过读源码,或者看框架实现的其他类是怎么做的
public class MyRealm3 extends AuthorizingRealm {

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //创建返回值
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //假装从数据库取出了用户角色
        List<String> roles = new ArrayList<String>();
        roles.add("admin");
        roles.add("common");
        //添加
        simpleAuthorizationInfo.addRoles(roles);
        //假装从数据库取出了权限
        Set<String> permissions = new HashSet<String>();
        permissions.add("user:update");
        permissions.add("user:create");
        //添加
        simpleAuthorizationInfo.setStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //强制转换下
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String username = usernamePasswordToken.getUsername();
        char[] password = usernamePasswordToken.getPassword();

        //此处假装我们从数据库查询账号密码
        //select username,password user_info where username = 'zhangsan'
        String dbUserName = "zhangsan";
        String dbPassword = "123";

        //这里是shiro对比密码的地方,给他指定的账号密码,然后在把当前的realm名字给他,他自己做对比
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(dbUserName,dbPassword,this.getName());

        return simpleAuthenticationInfo;
    }
}
  @Test
    public void customerRealm(){
        //1、获取SecurityManager工厂,此处使用Ini配置文件初始化
        IniSecurityManagerFactory iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro-custom-realm.ini");

        //2、得到SecurityManager实例 并绑定给SecurityUtils
        SecurityManager securityManager = iniSecurityManagerFactory.getInstance();

        //获取 SecurityManager 并绑定到 SecurityUtils,这是一个全局设置,设置一次即可;
        SecurityUtils.setSecurityManager(securityManager);

        //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
        Subject subject = SecurityUtils.getSubject();


        //账号密码验证器
        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
        try {
            //4、登录,即身份验证
            subject.login(token);
//            //判断拥有角色:role1
            Assert.assertTrue(subject.hasRole("admin"));

            Assert.assertTrue(subject.isPermitted("user:update"));
            Assert.assertTrue(subject.isPermittedAll("user:update","user:create"));

        } catch (Exception e) {
            //5、身份验证失败
            System.out.println("登录认证失败:" + e.getMessage());
        }
        //6、退出
        subject.logout();
    }
#声明realm
myRealm3=ShiroDemo.MyRealm3

#指定securityManager的realms实现
securityManager.realms=$myRealm3

除了以上内容,我们还可以对realm进行加密和加盐.这种场景一般是,在用户注册账号后,用户的密码不是明文存在数据库的.这是当用户再次登录,那么我们需要对用户输入的账号密码进行加密加盐.此处我就不举例了.还有我们可以定义多个realm这种形式说白了,就像我们的拦截器一样.一个不够,搞多个.需要注意的,如果你有多个realm那么默认的校验规则是,只要有一个通过就会通过.这是因为securityManager.authenticator有一个authenticationStrategy.这个玩意有几种取值,默认是多个realm是或者的关系.你也可以设置authenticationStrategy为AllSuccessfulStrategy.这样就是全部需要通过.

这里推荐一篇文章写的是subject是如何保证唯一的 https://www.cnblogs.com/zhaosq/p/9921040.html

springmvc使用shiro

在web项目中,使用到shiro场景应该是最多的.我们来分析,当我们需要对系统的一个url做登录校验,角色校验或者细粒度的权限校验时.我们肯定是要做拦截器的.看一下配置

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>Archetype Created Web Application</display-name>

    <!-- dispatcher servlet-->
    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>WEB-INF/spring-config.xml</param-value>
    </context-param>
    <!-- Spring监听器 管理bean-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

springmvc配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd ">

    <context:component-scan base-package="com.demo"/>

    <mvc:annotation-driven/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

spring管理bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <!-- 启用shrio授权注解拦截方式 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 装配 securityManager -->
        <property name="securityManager" ref="securityManager"/>

        <!--未登录转发-->
         <property name="loginUrl" value="/hello"/>
        <!-- 具体配置需要拦截哪些 URL, 以及访问对应的 URL 时使用 Shiro 的什么 Filter 进行拦截.  -->
        <property name="filterChainDefinitions">
            <value>
                /pub=anon
                /get=authc
                /admin=roles[admin]
                /userdelete=perms[user:update]
            </value>
        </property>
    </bean>

    <!-- 配置进行授权和认证的 Realm -->
    <bean id="myRealm" class="com.demo.ShiroRealm"></bean>

    <!-- 配置 Shiro 的 SecurityManager Bean. -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--<property name="cacheManager" ref="cacheManager"/>-->
        <property name="realm" ref="myRealm"/>
    </bean>
</beans>

抛开springmvc的配置,但看web.xml和spring-config.xml 在web.xml配置了一个代理拦截器.而这个代理拦截器指向的是spring-config.xml中的shiroFilter这其实是spring使用的策略模式,对于spring来说,我们可以使用拦截器来进行权限校验,但是具体使用哪个拦截器需要我们指定.配置的shirofilter就用shiro配置springSecurity就用spring自己的权限工具.spring-config中的配置其实跟javase项目中差不多了,通过工厂类装配一个securityManager.在securityManager中配置Realm.自定义Realm我们稍后再看.先看shiroFilter中的filterChainDefinitions这里有很多属性,就是让我们确定,哪些具体的url需要拦截.具体需要的权限是什么.但是需要注意的是,所有对于角色和权限的过滤必须要先登录才可以.

Realm就比较简单,跟javase一样.

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ShiroRealm extends AuthorizingRealm {
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;

        String username = usernamePasswordToken.getUsername();
        char[] password = usernamePasswordToken.getPassword();

        //此处取数据库查询账号密码
        String dbUserName = "zhangsan";
        String dbPassword = "123";

        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(dbUserName,dbPassword,this.getName());
        return simpleAuthenticationInfo;
    }
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //创建返回值
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //假装从数据库取出了用户角色
        List<String> roles = new ArrayList<String>();
        roles.add("admin");
        roles.add("user");
        //添加
        simpleAuthorizationInfo.addRoles(roles);
        //假装从数据库取出了权限
        Set<String> permissions = new HashSet<String>();
      //  permissions.add("user:update");
        permissions.add("user:create");
        //添加
        simpleAuthorizationInfo.setStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }
}
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class Hello {


    @ResponseBody
    @GetMapping("hello")
    public String hello() {
        return "hello please login";
    }

    @ResponseBody
    @GetMapping("success")
    public String sucess() {
        return "login-success";
    }

    @ResponseBody
    @GetMapping("err")
    public String err() {
        return "login-err";
    }

    @ResponseBody
    @GetMapping("pub")
    public String pub() {
        return "public-resource";
    }

    @ResponseBody
    @GetMapping("get")
    public String get() {
        return "login of get";
    }


    @GetMapping("admin")
    @ResponseBody
    public String adminread() {
        return "admin look!";
    }

    @GetMapping("userdelete")
    @ResponseBody
    public String userdelete() {
        return "delete perms look!";
    }

    @ResponseBody
    @GetMapping("login")
    public String login(String name, String password) {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            return "success";
        } else {
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(name, password);
            try {
                subject.login(usernamePasswordToken);
                return "get";
            } catch (Exception e) {
                return "err";
            }
        }
    }

}

以上内容都是对shiro最低的配置.我们可以配置缓存,加密.也可以对subject进行配置.思考一个问题,在web情况下每个用户的线程都不一样.shiro不会依赖线程来确定subject.那么依赖什么呢?session中的jsession那么如果浏览器禁止cookie呢?再或者说,我们的系统提供的都是RESTful风格的API呢?

以下是截取源码中的一部分,有兴趣的可以自行查看源码

 springboot使用shiro

springboot号称零配置文件,也是名不虚传的,一下内容是我在shrio官方文档下载的源码

导包

 配置filter和realm

 使用注解的方式实现权限控制

到此为止我们这次对shiro入门的了解就结束了.

原文地址:https://www.cnblogs.com/zumengjie/p/11770234.html