• 架构探险笔记12-安全控制框架Shiro


    什么是Shiro

    Shiro是Apache组织下的一款轻量级Java安全框架。Spring Security相对来说比较臃肿。

    官网

    Shiro提供的服务

    1.Authentication(认证)

    2.Authorization(授权)

    3.Session Management(会话管理)

    4.Cryptography(加密)

    5.Web Integration(web集成)

    6.Integrations(集成)

    首先,她提供了Authentication(认证)服务,也就是说,通过她可以完成身份认证,让她去判断用户是否为真实的会员。

    其次,她还提供了Authorization(授权)服务,其实就是访问控制服务,也就是让她来识别用户是否可以做某件事情,毕竟不同的用户是拥有不同的权限的。

    还提供了Session Management(会话管理)服务。这个就厉害了,这并不是用户熟知的HTTP Session,而是一个独立的Session管理框架,不管是否为Web应用,都可以用这套框架。

    最后,还提供了Crytography(加密)服务,封装了许多密码学算法,琳琅满目,应有尽有。

    除了以上4个基本服务,她也提供了很好的系统集成方案,用户可以轻松将其运用到Web应用中,可能这也是用户最关心的;此外,还可以集成第三方框架,例如:Spring、Guice、CAS等。

    Hello Shiro

    Maven,在pom.xml加入坐标

            <!--SLFJ-->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
                <version>1.6.4</version>
            </dependency>
            <!--Shiro核心包,一定要用到的-->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.2.3</version>
            </dependency>

    Shiro依赖于SLFJ日志框架,而SLFJ只是一个接口,并没有提供具体的实现。可以选择Log4J作为它的实现,正好SLFJ也提供了一个slf4j-log4j12的Artifact,所以这里就可以用上了。

    Maven示意图如下:

    使用Log4J,就应该在classpath下提供一个log4j.properties文件:

    log4j.rootLogger=ERROR,console,file
    
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=%-5p %c(%L) -%m%n
    log4j.logger.org.autumn=DEBUG

    通过上卖弄的配置将日志输出到控制台上,并配置了日志输出格式。

    同样,既然使用了Shiro,那么久应该在classpath下提供一个shiro.ini文件:

    [users]
    shiro = 1995

    我们配置一个用户名为shiro,密码为1995的用户。这里仅为演示,在实际项目中肯定不会把用户信息定义在配置文件中。

    测试一下Shiro的认证服务功能,用main方法测试

    package org.autumn;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.util.Factory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * Created by Administrator on 2019/1/28.
     */
    public class HelloShiro {
        private static final Logger LOGGER = LoggerFactory.getLogger(HelloShiro.class);
    
        public static void main(String[] args) {
            //初始化SecurityManager
            Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
            SecurityManager securityManager = factory.getInstance();
            SecurityUtils.setSecurityManager(securityManager);
    
            //获取当前用户
            Subject subject = SecurityUtils.getSubject();
            //登录
            UsernamePasswordToken token = new UsernamePasswordToken("shiro","19950");
            try {
                subject.login(token);
            }catch (AuthenticationException ae){
                LOGGER.info("登录失败");
                return ;
            }
            LOGGER.info("登录成功!Hello "+subject.getPrincipal());
            subject.logout();
        }
    }

    结果

    解析:

    (1)需要读取classpath下的shiro.ini配置文件,并通过工厂类创建SecurityManager对象,最终将其放入SecurityUtils中,供Shiro框架随时获取。

    (2)同样通过SecurityUtils类获取Subject对象,其实就是当前用户,只不过在Shiro的世界里优雅地将其称为Subject(主体);

    (3)首先使用一个Username与Password来创建一个UsernamePasswordToken对象,然后通过这个Token对象调用Subject对象的login方法,让Shiro进行用户身份认证。

    (4)当登录失败时,可以使用AuthenticationException来捕获这个异常;当登录成功时,可以调用Subject对象的getPrincipal方法来获取Username,此时Shiro已经创建了一个Session。

    (5)最后还是通过Subject对象的logout方法来注销本次Session。

    通过Subject调用SecurityManager,通过SecurityManager调用Realm。这个Realm感觉有点生僻,其实就是提供用户信息的数据源。上面的Shiro中叫IniRealm。除此以外,Shiro还提供了其他几种Realm:PropertiesRealm、JdbcRealm、JndiLdapRealm、ActiveDirectoryReam等;当然也可以定制Realm来满足业务需求。

    不难发现,SecurityManager才是Shiro的真正的核心,只需通过Subject就可以操作SecurityManager,尤其是在Web应用中。

    在Web开发中使用Shiro

    我们可以直接在Web应用中使用Shiro官方提供的Web模块--- shiro-web。

    只需要在pom.xml中增加如下配置:

            <!--支持Web需要引入的Shiro包-->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-web</artifactId>
                <version>1.2.3</version>
            </dependency>

    在web.xml中加入如下配置

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
    
        <context-param>
            <param-name>shiroEnvironmentClass</param-name>
            <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
        </context-param>
        <context-param>
            <param-name>shiroConfigLocations</param-name>
            <param-value>classpath:shiro.ini</param-value>
        </context-param>
        
        <!--监听器,用来读取上面的ini配置文件-->
        <listener>
            <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
        </listener>
        <!--过滤器-->
        <filter>
            <filter-name>ShiroFilter</filter-name>
            <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>ShiroFilter</filter-name>
            <!--拦截所有请求-->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    </web-app>

    实际上就是通过EnvironmentLoaderListener这个监听器来初始化SecurityManager,并通过ShiroFilter来完成认证与授权。

    可以使用以下数据结构来存放用户机器权限的相关数据,这就是RBAC模型

    然后通过Shiro的JdbcReam来进行认证与授权,只需要在shiro.ini中做如下配置:

    [main]
    authc.loginUrl = /login    #配置跳转的的URL地址,通过Servlet映射后可定位到login页面
    
    ds = org.apache.commons.dbcp.BasicDataSource   #JDBC数据库配置
    ds.driverClassName = com.mysql.jdbc.Driver
    ds.url=jdbc:mysql://localhost:3306/bookkeeping
    ds.username=root
    ds.password=root
    
    jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm   #realm为jdbc
    #将上面配置的jdbc赋给realm
    jdbcRealm.dataSource = $ds
    #提供身份认证,即通过username查询password     
    jdbcRealm.authenticationQuery = SELECT pwd FROM users where userCode = ?
    #基于角色的授权验证(粗粒度),通过username查询role_name
    jdbcRealm.userRolesQuery = SELECT sys_role.roleName FROM sys_role,sys_user_role,users WHERE sys_role.roleCode = sys_user_role.roleCode AND sys_user_role.userCode = users.id AND users.userCode = ?
    #基于权限的授权验证(粗粒度),通过role_name查询model_name,此时需要开启permissionsLookupEnabled开关,默认是关上的
    jdbcRealm.permissionsQuery = SELECT sys_module.moduleName FROM sys_module, sys_role, sys_role_module where sys_module.moduleCode = sys_role_module.moduleCode AND sys_role_module.roleCode = sys_role.roleCode     and sys_role.roleName = ?
    
    jdbcRealm.permissionsLookupEnabled = true
    securityManager.realms = $jdbcRealm
    
    [urls]
    /=anon   #对于"/"请求(首页)可以匿名访问;
    /space/**=authc   #对于以"/space/"开头的请求,均由authc过滤器处理,也就是完成身份认证操作。

    还要补充说明的是,在permission表中存放了所有的权限名,实际上是一个权限字符串,推荐使用“资源:操作”这种格式来命名,例如:product:view(查看产品权限)、product:edit(产品编辑权限)、product:delete(产品删除权限)等。

    过滤器名称 功能  配置项(及默认值)
    anon 确保只有未登录(匿名)的用户发送的请求才能通过 -
    authc 去报只有已认证的用户发送的请求才能通过(若未认证,则跳转到登录页面)

    authc.loginUrl = /login.jsp

    authc.successUrl = /

    authc.usernameParam = username

    authc.passwordParam = password

    authc.rememerMeParam = rememberMe

    authc.failureKeyAttribute = shiroLoginFailure

    authcBasic 提供BasicHttp认证功能(在浏览器中弹出一个登录对话框) authcBasic.applicationName = application
    logout 接收结束会话的请求 logout.redirectUrl = /
    noSessionCreation 提供No Session解决方案(若有Session就会报错) -
    perms 确保只有拥有特定权限的用户发送的请求才能通过 -
    port 确保只有特定端口的请求才能通过 port = 80
    rest 提供REST解决方案(根据REST URL计算权限字符串) -
    roles 确保只有拥有特定角色的用户发送的请求才能通过 -
    ssl 确保只有HTTPS的请求才能通过 -
    user 确保只有已登录的用户发送的请求才能通过(包括:已认证或已记住) -

    在index.jsp中判断该用户是游客还是已登录的用户:

    <%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" language="java" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
    <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    <html>
    <head>
        <title>首页</title>
    </head>
    <body>
        <shiro:guest>
            <p>身份:游客</p>
            <a href="<c:url value="/login"/>">登录</a>
            <a href="<c:url value="/register"/>">注册</a>
        </shiro:guest>
    
        <shiro:user>
            <p>身份:<shiro:principal/></p>
            <a href="<c:url value="/space"/>">空间</a>
            <a href="<c:url value="/logout"/>">退出</a>
        </shiro:user>
    </body>
    </html>

    需要使用Shiro提供的JSP标签:

    <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

    随后就可以使用shiro标签的相关功能了。

    标签 功能
    <shiro:guest></shiro:guest> 判断当前用户是否为游客
    <shiro:user></shiro:user>

    判断当前用户是否已登录

    (包括认证或已认证)

    <shiro:authecticated></shiro:authecticated> 判断当前用户是否已认证通过(不包括已记住)
    <shiro:notAuthecticated></shiro:notAuthecticated> 判断当前用户是否未认证通过
    <shiro:hasRole name="foo"></shiro:hasRole> 判断当前用户是否具有某种角色
    <shiro:lacksRole name="foo"></shiro:lacksRole> 判断当前用户是否缺少某种角色
    <shiro:hasAnyRoles name="foo,bar"></shiro:hasAnyRoles> 判断当前用户是否具有任意一种角色(foo或bar)
    <shiro:hasPermission name="foo"></shiro:hasPermission> 判断当前用户是否具有某种权限
    <shiro:lacksPermission name="foo"></shiro:lacksPermission> 判断当前用户是否缺少某种权限
    <shiro:principal/> 获取当前用户的相关信息,例如:用户名

    依葫芦画瓢,可以实现其他需要的shiro标签

    除了JSP标签,Shiro还提供了Java注解,只需将这些注解定义在想要安全控制的方法上即可。

    注解 功能
    RequiresGuest 确保被标注的方法可被匿名用户访问
    RequiresUser 确保被标注的方法只能被已登录的用户访问(包括:已认证或已记住)
    RequiresAuthentication 确保被标注的方法只能被已认证的用户访问(不包括已记住)
    RequiresRoles  确保被标注的方法仅被指定角色的用户访问 
    RequiresPermissions 确保被标注的方法仅被指定权限的用户访问

     注意:当使用了RequiresRoles与RequiresPermissions注解,也就意味着把代码写死了;这样如果数据库里的Role或Permission更改了,代码也就无效了。这或许是Shiro的一点不完美的地方,不过瑕不掩瑜。

    每次认证与授权都需要与数据库打交道,这会对性能产生一定的开销:关于这一点Shiro也为我们先到了,只需在main片段中增加如下配置即可:

    cacheManager=org.apache.shiro.cache.MemoryConstrainedCacheManager
    securityManager.cacheManager=$cacheManager

    此时Shiro就会在内存中使用一个Map来缓存查询结果,从而减少了数据库的操作次数,提高了查询方面的性能。Shiro也提供了E和Cache的扩展,为缓存提供了更加“高大上”的解决方案。

    目前在数据库里保存的是明文的密码,这样不太安全,如何将其加密呢?Shiro同样提供了非常优雅的解决方案,只需在[amin]片段下增加如下配置即可:

    passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
    jdbcRealm.credentialsMatcher=$passwordMatcher

    其实,Shiro对密码的加密与解密提供了非常强大的支持,这里仅仅是一种最简单的情况。需要确保在创建密码的时候使用对应的加密算法,Shiro给我们提供了PasswordService接口,可以这样来使用:

    PasswordService passwordService = new DefaultPasswordService();
    String encryptedPassword = passwordService.encryptPassword(plantextPassword);

    只需将这个encryptedPassword存入数据库即可。

    其实关于Shiro的用法还有很多,我们可以从Shrio的官网上学到更多的特性,包括集成EhCache、集成Spring、集成CAS等。

    最后结果如下

  • 相关阅读:
    django:开发一个下载图片的接口
    django:cbv模式和fbv模式的区别
    java生成二维码/java解析二维码
    java如何台生成二维码详解
    html5 WebSocket的Js实例教程
    vue 组件传参
    Vue路由注意事项
    Vue全家桶
    浅谈vue对seo的影响
    vue打包详情
  • 原文地址:https://www.cnblogs.com/aeolian/p/10329853.html
Copyright © 2020-2023  润新知