SHiro学习笔记

SHiro学习笔记

1.简介

Apache Shiro是一个功能强大且易于使用的 Java 安全框架,可执行身份验证、授权、加密和会话管理。借助 Shiro 易于理解的 API,您可以快速轻松地保护任何应用程序,从最小的移动应用程序到最大的 Web 和企业应用程序。

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

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

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

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

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

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

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

    Testing:提供测试支持;

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

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

img

  • Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。

2.结构

2.1外部结构

img

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

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

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

2.2 内部结构

img

  • 基础知识:

principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个Primary principals,一般是用户名/密码/手机号。

credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

最常见的principals和credentials组合就是用户名/密码了。

3. 简单Demo

  • 这是一个简单的身份凭证对比例子,不涉及权限
  • 身份凭证等信息是存储在shiro.ini文件中的,实际开发中是从数据库中取出。
@Test
public void test3(){

    //        1.创建SecurityManger
    DefaultSecurityManager manager = new DefaultSecurityManager();

    //        2.为SecurityManager设置Realm
    manager.setRealm(new IniRealm("classpath:shiro.ini"));

    //        3.设置全局安全工具类
    SecurityUtils.setSecurityManager(manager);

    //        4.获取 关键对象Subject主体
    Subject subject = SecurityUtils.getSubject();

    //        5.创建令牌token
    UsernamePasswordToken token = new UsernamePasswordToken("zs","123");

    //        6.进行身份认证
    try{
        System.out.println("认证状态:"+subject.isAuthenticated());
        subject.login(token);
        System.out.println("认证状态:"+subject.isAuthenticated());
    }catch (Exception e){
        e.printStackTrace();
    }

    //		7.注销认证
    subject.logout();
}
  • shiro.ini
;用户身份、凭证及各自的角色
[users] 
zs = 123, admin
ls = 123, schwartz
ww = 123, goodguy


;用户角色
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5

;配置应用程序的实例及其任何依赖项(如Realms)的地方
[main]

4.认证流程

  • login流程

4.1 调用SecurityManager

  • Subject的实现类的身份认证,其实质上也是调用SecurityManager模块的login方法。

image-20200714155811132

  • SecurityManager的实现类在login方法中进行身份认证。

image-20200714160009122

image-20200714160253117

  • 返回验证信息

image-20200714160452843

4.2 Realm中获取信息

  • 区分多域与单域

image-20200714160654274

  • 从域中查找信息

image-20200714161035962

  • Realm中获取数据
  • 先从缓存中拉取,(当设置数据库数据为Realm时,缓存可以缓解数据库压力)
  • 如未找到,才从Realm中查找数据

image-20200714161517858

  • doGetAuthenticationInfo返回信息

image-20200714161743386

4.3 匹配凭证

image-20200714161917499

image-20200714162355584

image-20200714162226731

4.4 匹配成功

  • 在匹配成功后,对设置的监听器进行唤醒。

image-20200714162816111

4.5 配置RememberMe

image-20200714163045662

  • RememberMe管理器

image-20200714163214366

image-20200714163426204

image-20200714163541433

4.6 结束

image-20200714163745848

5.自定义Realm

5.1 观察Realm关系

  • 发现在Authentication中有一个doGetAuthenticationInfo 方法:用于认证
  • AuthorizingRealm中有一个doGetAuthorizationInfo方法:用于授权

image-20200714184203115

5.2 简单自定义Realm认证

  • 自定义的Realm
public class CustomerRealm extends AuthorizingRealm {
    //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
    //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String principal = (String) token.getPrincipal();
        if ("admin".equals(principal)){
            // 1.正确的身份信息、2.正确的凭证信息、3.返回当前realm名
            SimpleAuthenticationInfo admin = new SimpleAuthenticationInfo("admin", "123", this.getName());
            return admin;
        }
        return null;
    }
}
  • 调用方式与demo差不多
public static void main(String[] args) {
    DefaultSecurityManager manager = new DefaultSecurityManager();
    manager.setRealm(new CustomerRealm());
    SecurityUtils.setSecurityManager(manager);
    Subject subject = SecurityUtils.getSubject();
    //        获取口令/令牌
    UsernamePasswordToken token = new UsernamePasswordToken("admin", "123");

    try{
        subject.login(token);
        System.out.println("认证状态:"+subject.isAuthenticated());
    }catch (UnknownAccountException e){
        e.printStackTrace();
        System.out.println("用户名错误");
    }catch (IncorrectCredentialsException e){
        e.printStackTrace();
        System.out.println("密码错误");
    }catch (Exception e){
        e.printStackTrace();
    }
    subject.logout();
}

5.3 MD5

  • MD5主要用于加密和签名,还有文件检验
5.3.1 MD5加密
  • MD5具有不可逆的特性,对内容相同的数据进行MD5算法处理后,结果也相同

demo:

Md5Hash md5Hash = new Md5Hash("12345678");
System.out.println(md5Hash.toHex()); // 25d55ad283aa400af464c76d713c07ad
5.3.2 盐Salt
  • 为了防止穷举法破解MD5算法,需要在MD5加密前,给数据源进行加盐(Salt)
Md5Hash md5Hash = new Md5Hash("12345678","AF)(0*");
System.out.println(md5Hash.toHex()); //a1917c0170fc8b4e477e10fce3425ed1
5.3.3 Hash散列
  • 为了提高数据安全性,Shiro中加入了Hash散列,可自定义散列次数。
Md5Hash md5Hash = new Md5Hash("12345678","AF)(0*",1024);
System.out.println(md5Hash.toHex()); //1e391e1c442c9a828e54d589fd6320fb

5.4 MD5加密Realm认证

  • 一般登录需要从数据库中取出用户名、数据库密码、随机噪盐(在用户注册时,会随机产生一个随机字串作为盐)
public class CustomerMD5Realm extends AuthorizingRealm {
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        if ("admin".equals(token.getPrincipal())){
//            1.数据库用户名、2.数据库密码、3.数据库噪盐
            return new SimpleAuthenticationInfo("admin","1e391e1c442c9a828e54d589fd6320fb",
                    ByteSource.Util.bytes("AF)(0*"),this.getName());
        }
        return null;
    }
}
  • 在加上随机盐和散列次数后,那么其在使用上与demo有所不同
  • 需要自行设置凭证匹配器
public static void main(String[] args) {

    DefaultSecurityManager manager = new DefaultSecurityManager();
    // 自定义的Realm
    CustomerMD5Realm realm = new CustomerMD5Realm();
    //创建要使用的凭证匹配器,不然会默认进行equals匹配
    HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("md5");
    //        设置散列次数
    md5.setHashIterations(1024);
	//设置凭证匹配器
    realm.setCredentialsMatcher(md5);
    manager.setRealm(realm);

    SecurityUtils.setSecurityManager(manager);
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("admin","12345678");

    try{
        subject.login(token);
        System.out.println("认证状态:"+subject.isAuthenticated());
    }catch (UnknownAccountException e){
        e.printStackTrace();
        System.out.println("用户名错误");
    }catch (IncorrectCredentialsException e){
        e.printStackTrace();
        System.out.println("密码错误");
    }catch (Exception e){
        e.printStackTrace();
    }
    subject.logout();
}

5.5 授权

  • 授权,即访问控制。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是不能访问的。

  • 授权可通过授权标识符来实现

[roles]
role1 = permission

6. Web

6.1 配置

  • [urls]需要在[mainusers][roles] 之后配置。

等号(=)右侧的标记是逗号分隔的过滤器列表,可针对与该路径匹配的请求执行这些过滤器。它必须与以下格式匹配:

url = filter1[optional_config1], filter2[optional_config2], ..., filterN[optional_configN]
  • [参数],中括号里的参数可以根据实际情况可以省略。

  • filterN[main]中定义的过滤器bean的名称

  • = 号后面定义的过滤器列表组成过滤链,因此它们的顺序很重要。

6.2 Shiro自带过滤器

  • Shiro自带了一些常用的过滤器,可以不在[main]中定义,可以直接使用
Filter NameClass功能
anonAnonymousFilter指定Url可以匿名访问
authcFormAuthenticationFilter指定url需要form表单登录,默认会从请求中获取usernamepasswordrememberMe等参数并尝试登录,如果登陆不了就会跳转到loginUrl配置得路径。我们也可以用这个过滤器做默认得登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制。
authcBasicBasicHttpAuthenticationFilter指定url需要basic登录
authcBearerBearerHttpAuthenticationFilter
logoutLogoutFilter登出过滤器,配置指定url就可以实现退出功能,非常方便
noSessionCreationNoSessionCreationFilter禁止创建会话
permsPermissionsAuthorizationFilter需要指定权限才能访问
portPortFilter指定端口才可以访问
restHttpMethodPermissionFilter将HTTP请求方法转化成相应的动词来构造一个权限字符串,有兴趣看源码注释
rolesRolesAuthorizationFilter需要指定角色才能访问
sslSslFilter需要https才能访问
userUserFilter需要已登录或“记住我”的用户才能访问

6.3 自定义过滤器

[main]
...
myFilter = com.company.web.some.FilterImplementation
myFilter.property1 = value1
...

[urls]
...
/some/path/** = myFilter

6.4 与Web Integration

  • 在这里,我在Shiro的自带容器里实例化Realm和SecurityManager
[main]
myRealm = MyRealm
securityManager.realm = $myRealm

[urls]
/login = anon
/index.jsp = authc
  • 在LoginServlet里进行验证
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    req.setCharacterEncoding("utf-8");
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    Subject currentUser = SecurityUtils.getSubject();

    try{
        currentUser.login(token);
    }catch (UnknownAccountException e){
        System.out.println("账户输入有误!");
    }catch (IncorrectCredentialsException e){
        System.out.println("密码错误!");
    }
    if (currentUser.isAuthenticated()){
        resp.sendRedirect("index.jsp");
    }
}
  • Web.xml里只需配置几项
<listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>loginservlet</servlet-name>
    <servlet-class>com.gu_ppy.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>loginservlet</servlet-name>
    <url-pattern>/login</url-pattern>
</servlet-mapping>

<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>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>ASYNC</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

7. 配置

7.1 字符配置

  • 凭证匹配器的编码转换
md5 = org.apache.shiro.authc.credential.HashedCredentialsMatcher
md5.StoredCredentialsHexEncoded = false  ;false为Base64,默认为true十六进制编码

7.2 权限配置

  • 权限与角色相关联
  • rolename= 权限定义1权限定义2,…,权限定义 N
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
  • 注意!
[roles]
role1 = "printer:5thFloor:print,info"    ;当需要在实例区域放多个时需要用""包含,否则容易产生解析错误
role2 = "printer:5thFloor:print,info",find

2*,…,权限定义 N

[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
  • 注意!
[roles]
role1 = "printer:5thFloor:print,info"    ;当需要在实例区域放多个时需要用""包含,否则容易产生解析错误
role2 = "printer:5thFloor:print,info",find
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值