Shrio安全框架的使用

Shiro

Apache Shiro 是一个强大易用的JAVA安全框架,提供了认证、授权、加密、会话管理等功能

  • 认证-用户身份识别,等同于用户登录,判断用户是否登录,如果当前用户未登录则会拦截当前请求。
  • 授权-访问控制,当用户登录后,判断其角色角色身份是否有相应的权限访问相应的资源,如果没有当前权限则则拦截请求。
  • 密码加密-保护或隐藏数据被窥视;例如密码的明文加密后只能看到密文,保护账户信息,shrio提供的密码加密对MD5进行了二次封装,不可逆让密码保护更加安全。
  • 会话管理-Shiro 根据传统的Session抽象了一个自己的 Session来管理主体与应用之间交互的数据,控制Session的生命周期。

从工作流程来看shrio(Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入


  • 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 能得到合法的用户及其权限进行判断。


Shrio通过一些列过滤器来对认证和授权进行控制

Shrio的过滤器:


以上的过滤器分为两类:

一、认证的过滤器:anno(不认证也可以访问)、authcBasic,authc(必须认证才能访问),user

二、授权的过滤器:perms(指定资源需要哪些权限才能被访问),roles,ssl,rest,port

例子:在Spring配置文件中通过配置ShrioFilter的filterChainDefinitions过滤链对不同的Url分配不同的过滤方式,详细看配置文件部分

-------------------------------------------------------------------------------------------------------------------------------------------

通过实例来使用Shrio安全验证框架(web应用spring与shrio的集成:登录访问页面实例)

maven依赖:见项目源码 其他配置文件参考源码(源码地址在文章底部)

tomcat的配置文件web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID" version="2.5">
  <display-name>WebShiro</display-name>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/applicationContext-*.xml</param-value>
  </context-param>

  <!-- Spring监听器 -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- 处理POST提交乱码问题 -->
  <filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>*.do</url-pattern>
  </filter-mapping>


  <!-- 前端控制器 -->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 默认找 /WEB-INF/[servlet的名称]-servlet.xml -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/springmvc.xml</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <!-- 1. /* 拦截所有 jsp js png .css 真的全拦截 建议不使用 2. *.action *.do 拦截以do action
        结尾的请求 肯定能使用 ERP 3. / 拦截所有 (不包括jsp) (包含.js .png.css) 强烈建议使用 前台 面向消费者 www.jd.com/search
        /对静态资源放行 -->
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>


  <!-- shiro过滤器,DelegatingFilterProx会从spring容器中找shiroFilter -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
      <param-name>targetFilterLifecycle</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

shrio配置文件:applicationContext-shrio.xml

<?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.xsd">

    <!--Shrio过滤器工厂Bean 通过属性注入的方式注入-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" depends-on="myAuthorizationFilter">
        <!--安全管理器-->
        <property name="securityManager" ref="securityManager"/>
        <!--认证相关配置:当用户没有登陆访问资源就会跳转到此页面-->
        <property name="loginUrl" value="/login.html"/>
        <!--授权相关配置:当用户没有访问权限的资源就跳转到次页面-->
        <property name="unauthorizedUrl" value="/error.html"/>
        <property name="filters">
            <map>
                <entry key="perms" value-ref="myAuthorizationFilter"></entry>
            </map>
        </property>
        <!--过滤链:针对不同url指定不同的过滤器-->
        <!--配置login.doerror.html为不需要认证的URL,配置哪些资源需要哪些权限才能访问
            anon为不需要认证和授权都可以访问
            perms为需要什么权限才能访问资源
            authc为需要认证后才能访问资源
            注意:一定要按照以上的顺序写配置,不然权限拦截会失败
        -->
        <property name="filterChainDefinitions">
            <value>
                /error.html = anon
                /login.html = anon
                /login.do = anon
                /custom.html = perms["用户"]
                /manager.html = perms["管理员","超级管理员"]
                /*.do = authc
                /*.html = authc
            </value>
        </property>

    </bean>
    <!--用户自定义的Realm-->
    <bean id="userRealm" class="work.yanghao.realm.UserRealm">
        <property name="userService" ref="userService"></property>
    </bean>
    <!--自定义授权过滤器-->
    <bean id="myAuthorizationFilter" class="work.yanghao.filter.MyAuthorizationFilter"></bean>
    <!--安全管理器 Shrio核心组件:所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm"></property>
    </bean>
</beans>

实现Shrio的认证

我们知道Shrio通过Subject----->SecurityManager----->Realm来执行认证操作的,我们要自定义Realm域,提供安全的数据域对象。来看看用户认证(即登录)是如何认证的。

-----当用户没有被认证,请求都会被拦截(除了/error.html和/login.do)转到页面login.html,定义配置login.do不需认证是因为我们发起登录请求login.do的时候不想被Shrio拦截。

<!--配置login.doerror.html为不需要认证的URL,配置哪些资源需要哪些权限才能访问
    anon为不需要认证和授权都可以访问
    perms为需要什么权限才能访问资源
    authc为需要认证后才能访问资源
    注意:一定要按照以上的顺序写配置,不然权限拦截会失败
-->
<property name="filterChainDefinitions">
    <value>
        /error.html = anon
        /login.html = anon
        /login.do = anon
        /html/custom.html = perms["用户"]
        /html/manager.html = perms["管理员","超级管理员"]
        /*.do = authc
        /*.html = authc
        /* = authc
    </value>
</property>

-----Controller层代码:

@Controller
public class UserController {
    @RequestMapping("login")
    public String login(String username,String password){
        //创建令牌
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
        //得到subject
        Subject subject = SecurityUtils.getSubject();
        //执行验证

        try {
            subject.login(usernamePasswordToken);
            System.out.println("登录成功");
            User user = (User)subject.getPrincipal();
            System.out.println("登录成功后可以从subject中获取principal信息,principal代表了当前用户对象:"+user.getUserName());
            return "index";
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("登录失败");
        return "redirect:/error.html";
    }


    @RequestMapping("loginOut")
    public String loginOut(){
        //得到subject
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:/login.html";
    }
}
请求参数需要组装成一个token,这里是一个用户名和密码串,当我们使用subject调起login时,Shrio会从我们定义的所有Realm中搜寻匹配当前token的Realm来进行认证,当前找到UserRealm做认证。为了简便,userService层login方法直接返回一个User对象,代表成功在数据库中查到用户信息。

public class UserRealm extends AuthorizingRealm{
    
    private UserService userService;
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    public UserService getUserService() {
        return userService;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
       return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("Shrio执行认证方法");
        //等到要认证串的用户名和密码
        String username = ((UsernamePasswordToken)token).getUsername();
        String password = new String(((UsernamePasswordToken) token).getPassword());
        //调用业务层查询数据库
        User user = userService.login(username, password);
        if(user!=null)
        {
            //principle 主体/用户名
            //credentials 密码
            //realmName realm名称
            return new SimpleAuthenticationInfo(username,password,"UserRealm");
        }
        System.out.println("用户名或者密码错误");
        return null;
    }
}

到此,认证部分已经完成,通过测试,如果当前用户没有被认证(登录),就会拦截当前请求到登录页面,直到用户成功登录后,Shrio不会再拦截已经登录过的用户的请求。

实现Shrio的授权:

授权就是通过设定规则,指定哪些URL需要哪些权限可以访问资源,修改shrio配置文件

/custom.html 需要用户权限,/manager.html需要管理员和超级管理员两个权限,是And的关系。

<!--配置login.doerror.html为不需要认证的URL,配置哪些资源需要哪些权限才能访问
    anon为不需要认证和授权都可以访问
    perms为需要什么权限才能访问资源
    authc为需要认证后才能访问资源
    注意:一定要按照以上的顺序写配置,不然权限拦截会失败
-->
<property name="filterChainDefinitions">
    <value>
        /error.html = anon
        /login.html = anon
        /login.do = anon
        /custom.html = perms["用户"]
        /manager.html = perms["管理员","超级管理员"]
        /*.do = authc
        /*.html = authc
    </value>
</property>

在之前的自定义的UserRealm的授权方法给当前认证的用户授权,给予当前认证的用户授予‘用户’的权限,当用户已经认证后,访问custom.html成功,但是访问manager.html失败。

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    System.out.println("Shrio执行授权方法");
    //获取当前主体的primaryPrincipal(当前主体已经被认证)
    String primaryPrincipal = (String)principals.getPrimaryPrincipal();
    System.out.println("当前已经登录认证的用户是"+primaryPrincipal);
    //根据身份primaryPrincipal获取权限信息,通常这里查询数据库,这里模拟当前用户的权限查到为用户
    //封装简单的授权信息对象
    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    ArrayList<String> permisions = new ArrayList<>();
    permisions.add("用户");
    simpleAuthorizationInfo.addStringPermissions(permisions);
    return simpleAuthorizationInfo;
}

如果想让/manager.html可以被管理员或者(OR)超级管理员其一访问,那么就要自定义授权过滤器,实际上就相当于改变了过滤器的规则,像上面perms就代表了一种过滤器。

写一个自定义的过滤器MyAuthorizationFilter:

public class MyAuthorizationFilter extends AuthorizationFilter{

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {

        //获取配置文件中声明的权限列表
        String[] perms = (String[])o;
        //获取主体
        Subject subject = getSubject(servletRequest, servletResponse);
        //改变授权逻辑(权限有其中之一也可以访问资源)
        if(perms==null||perms.length==0)
        {
            return true;
        }
        for (String perm : perms) {
            if(subject.isPermitted(perm))
            {
                return true;
            }
        }
        return false;
    }

修改配置文件

<!--自定义授权过滤器-->
<bean id="myAuthorizationFilter" class="work.yanghao.filter.MyAuthorizationFilter"></bean>
<!--Shrio过滤器工厂Bean 通过属性注入的方式注入-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" depends-on="myAuthorizationFilter">
    <!--安全管理器-->
    <property name="securityManager" ref="securityManager"/>
    <!--认证相关配置:当用户没有登陆访问资源就会跳转到此页面-->
    <property name="loginUrl" value="/login.html"/>
    <!--授权相关配置:当用户没有访问权限的资源就跳转到次页面-->
    <property name="unauthorizedUrl" value="/error.html"/>
    <!--perms为过滤器的字段名称,这里配置会顶替内置的perms过滤器-->
    <property name="filters">
        <map>
            <entry key="perms" value-ref="myAuthorizationFilter"></entry>
        </map>
    </property>
    <!--过滤链:针对不同url指定不同的过滤器-->
    <!--配置login.doerror.html为不需要认证的URL,配置哪些资源需要哪些权限才能访问
        anon为不需要认证和授权都可以访问
        perms为需要什么权限才能访问资源
        authc为需要认证后才能访问资源
        注意:一定要按照以上的顺序写配置,不然权限拦截会失败
    -->
    <property name="filterChainDefinitions">
        <value>
            /error.html = anon
            /login.html = anon
            /login.do = anon
            /html/custom.html = perms["用户"]
            /html/manager.html = perms["管理员","超级管理员"]
            /*.do = authc
            /*.html = authc
            /* = authc
        </value>
    </property>
</bean>

到此,权限规则则交给我们自定义的过滤器来实现。

--------------------------------------------------------------------------------------------------------------------------------

以上都是基于URL进行权限控制的,想对程序实现更加精细的控制可以使用Shiro的注解来实现:

待续。。。。。。。。。

项目源码地址参考:https://github.com/dlyanghao/ShrioDemo


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值