Shiro-00-shiro 概览

RBAC

RBCA
RBCA zh_CN

Shiro

Apache Shiro 是一个强大且易于使用的 Java 安全框架,负责执行身份验证、授权、加密和会话管理。

通过 Shiro 的易于理解的 API,您可以快速而轻松地保护任何应用程序,从最小的移动应用到最大的 Web 和企业应用。

Shiro 提供了应用程序安全 API,用于执行以下方面:

  • 身份验证 - 验证用户身份,通常称为用户"登录"
  • 授权 - 访问控制
  • 加密 - 保护或隐藏数据免受窥探
  • 会话管理 - 每个用户的时限敏感状态

Subject->SecurityManager: SecurityManager->Realms: shiro

shiro zh_CN

Hello world

  • pom.xml
xml 复制代码
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.9</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.1.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.2.2</version>
    </dependency>
</dependencies>
  • shiro.ini

create this file under the classpath.

ini 复制代码
[users]
ryo=123
wang=123
  • ShiroTest.java
java 复制代码
@Test
public void testHelloworld() {
    Factory<SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro.ini");

    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("ryo", "123");

    try {
        subject.login(token);
    } catch (AuthenticationException e) {
        //login falied
    }

   assertEquals(true, subject.isAuthenticated());   //assert user has logined.

   //logout
   subject.logout();
}

Realms

Realms(领域)充当 Shiro 与您的应用程序安全数据之间的"桥梁"或"连接器"。

当需要实际与安全相关的数据进行交互,例如从用户帐户执行身份验证(登录)和授权(访问控制)时,Shiro 会查找配置为应用程序的一个或多个领域中的许多信息。

  • Realm.java
java 复制代码
public interface Realm {
    String getName();   //返回一个唯一的Realm名字

    boolean supports(AuthenticationToken var1); //判断此Realm是否支持此Token

    AuthenticationInfo getAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;  //根据Token获取认证信息
}
  • MyRealm.java
java 复制代码
public class MyRealm implements Realm {
    @Override
    public String getName() {
        return "firstRealm";
    }

    @Override
    public boolean supports(AuthenticationToken authenticationToken) {
        //仅支持UsernamePasswordToken类型的Token
        return authenticationToken instanceof UsernamePasswordToken;
    }

    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username = (String) authenticationToken.getPrincipal();      //get username
        String password = new String((char[]) authenticationToken.getCredentials());    //get password
        if (!"ryo".equals(username)) {
            throw new UnknownAccountException();
        }
        if (!"123".equals(password)) {
            throw new IncorrectCredentialsException();
        }

        //如果身份认证验证成功,返回一个AuthenticationInfo实现;
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}
  • shiro-realm.ini

create this file under the classpath.

ini 复制代码
#declear realm
firstRealm=com.ryo.shiro.MyRealm
#point the realms impls of securityManager
securityManager.realms=$firstRealm
  • test()
java 复制代码
@Test
public void testRealm() {
    Factory<SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro-realm.ini");
    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("ryo", "123");

    try {
        subject.login(token);
    } catch (AuthenticationException e) {
    }

    assertEquals(true, subject.isAuthenticated());

    subject.logout();
}

multi-realm

  • define another realm SecondRealm.java
java 复制代码
public class SecondRealm implements Realm {
    public String getName() {
        return "secondRealm";
    }

    public boolean supports(AuthenticationToken authenticationToken) {
        return authenticationToken instanceof UsernamePasswordToken;
    }

    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username = (String) authenticationToken.getPrincipal();
        String password = new String((char[]) authenticationToken.getCredentials());
        if (!"wang".equals(username)) {
            throw new UnknownAccountException();
        }
        if (!"123".equals(password)) {
            throw new IncorrectCredentialsException();
        }
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}
  • define shiro-multi-realm.ini
ini 复制代码
[main]
#define
firstRealm=com.ryo.shiro.FirstRealm
secondRealm=com.ryo.shiro.SecondRealm
#use
securityManager.realms=$firstRealm,$secondRealm
  • test()
java 复制代码
@Test
public void testMultiRealm() {
    Factory<SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro-multi-realm.ini");

    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);

    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("wang", "123");

    try {
        subject.login(token);
    } catch (AuthenticationException e) {
        e.printStackTrace();
    }

    Assert.assertEquals(true, subject.isAuthenticated());

    subject.logout();
}

Notice

The realm worked only after you used it.

JDBC Realm

  • Add jars info your pom.xml, here I user MySQL and druid datasource for test.
xml 复制代码
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.25</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>0.2.23</version>
</dependency>
  • Here are some sql to init database.
sql 复制代码
DROP DATABASE IF EXISTS shiro;
CREATE DATABASE shiro;
USE shiro;

CREATE TABLE users (
  id            BIGINT AUTO_INCREMENT,
  username      VARCHAR(100),
  password      VARCHAR(100),
  password_salt VARCHAR(100),
  CONSTRAINT pk_users PRIMARY KEY (id)
)
  CHARSET = utf8
  ENGINE = InnoDB;
CREATE UNIQUE INDEX idx_users_username ON users (username);

CREATE TABLE user_roles (
  id        BIGINT AUTO_INCREMENT,
  username  VARCHAR(100),
  role_name VARCHAR(100),
  CONSTRAINT pk_user_roles PRIMARY KEY (id)
)
  CHARSET = utf8
  ENGINE = InnoDB;
CREATE UNIQUE INDEX idx_user_roles ON user_roles (username, role_name);

CREATE TABLE roles_permissions (
  id         BIGINT AUTO_INCREMENT,
  role_name  VARCHAR(100),
  permission VARCHAR(100),
  CONSTRAINT pk_roles_permissions PRIMARY KEY (id)
)
  CHARSET = utf8
  ENGINE = InnoDB;
CREATE UNIQUE INDEX idx_roles_permissions ON roles_permissions (role_name, permission);

INSERT INTO users (username, password) VALUES ('wang', '123');
INSERT INTO users (username, password) VALUES ('ryo', '123');
  • shiro-jdbc-realm.ini
ini 复制代码
[main]
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3307/shiro
dataSource.username=root
dataSource.password=${youOwnSQLPassword}
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm


;1、varName=className    auto create an instance of class.
;2、varName.property=val         auto call the set()
;3、$varname             reference an object define before;
  • test()
ini 复制代码
@Test
public void testJDBCRealm() {
    Factory<org.apache.shiro.mgt.SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");

    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);

    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("ryo", "123");

    try {
        subject.login(token);
    } catch (AuthenticationException e) {
        e.printStackTrace();
    }

    Assert.assertEquals(true, subject.isAuthenticated());

    subject.logout();
}

Authenticator

  • 在单领域应用程序中,ModularRealmAuthenticator 将直接调用单个领域。

  • 如果您希望使用自定义的 Authenticator 实现配置 SecurityManager,您可以在 shiro.ini 中进行配置,例如:

ini 复制代码
[main]
authenticator = com.foo.bar.CustomAuthenticator

securityManager.authenticator = $authenticator

SecurityManager.java

java 复制代码
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
}

Authenticator.java

java 复制代码
public interface Authenticator {
    AuthenticationInfo authenticate(AuthenticationToken var1) throws AuthenticationException;
}

AuthenticationStrategy

当为应用程序配置了两个或更多领域时,ModularRealmAuthenticator 依赖于内部的 AuthenticationStrategy 组件来确定身份验证尝试成功或失败的条件。

AuthenticationStrategy 类 描述
AtLeastOneSuccessfulStrategy 如果一个或多个领域成功验证,则将考虑整体尝试成功。如果没有一个验证成功,则尝试失败。
FirstSuccessfulStrategy 仅使用从第一个成功验证的领域返回的信息。所有后续领域将被忽略。如果没有一个验证成功,则尝试失败。
AllSuccessfulStrategy 所有配置的领域必须成功验证才能考虑整体尝试成功。如果有任何一个验证不成功,则尝试失败。

1、这里定义了三个用于测试的领域:

  • FirstRealm ryo/123 成功,返回 ryo/123

  • SecondRealm wang/123 成功,返回 wang/123

  • ThirdRealm ryo/123 成功,返回 ryo@gmail.com/123

2、shiro-authenticator-all-success.ini

ModularRealmAuthenticator 默认使用 AtLeastOneSuccessfulStrategy 实现,因为这是最常用的策略。

但是,如果需要,您可以配置不同的策略。

ini 复制代码
[main]
#point out securityManager's authenticator  
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator  
securityManager.authenticator=$authenticator  
  
#Point out securityManager.authenticator's authenticationStrategy  
allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy  
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy  

#Define and use realms
firstRealm=com.ryo.shiro.FirstRealm
secondRealm=com.ryo.shiro.SecondRealm
thirdRealm=com.ryo.shiro.ThirdRealm
securityManager.realms=$firstRealm,$thirdRealm

3、AuthenticatorTest.java

java 复制代码
@Test
public void testAllSuccessfulStrategyWithSuccess() {
    Subject subject = getSubjectByPath("classpath:shiro-authenticator-all-success.ini");

    UsernamePasswordToken token = new UsernamePasswordToken("ryo", "123");
    subject.login(token);

    PrincipalCollection principalCollection = subject.getPrincipals();
    assertEquals("ryo,ryo@gmail.com", principalCollection.toString());
}

private Subject getSubjectByPath(String configFilePath) {
    Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFilePath);

    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    return SecurityUtils.getSubject();
}

<label class="label label-info">提示</label>

如果您想自己创建自己的 AuthenticationStrategy 实现,您可以使用 org.apache.shiro.authc.pam.AbstractAuthenticationStrategy 作为起点。

  • OnlyOneAuthenticatorStrategy.java
java 复制代码
public class OnlyOneAuthenticatorStrategy extends AbstractAuthenticationStrategy {
    //Simply returns the aggregate argument without modification.
    @Override
    public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException {
        return super.beforeAllAttempts(realms, token);
    }

    //Simply returns the aggregate method argument, without modification.
    @Override
    public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
        return super.beforeAttempt(realm, token, aggregate);
    }

    /**
     * Base implementation that will aggregate the specified singleRealmInfo into the aggregateInfo and then returns the aggregate.
     * @param realm the realm that was just consulted for AuthenticationInfo for the given token.
     * @param token the AuthenticationToken submitted for the subject attempting system log-in.
     * @param singleRealmInfo the info returned from a single realm.    单个realm信息
     * @param aggregateInfo the aggregate info representing all realms in a multi-realm environment.    总信息
     * @param t the Throwable thrown by the Realm during the attempt, or null if the method returned normally.
     * @return
     * @throws AuthenticationException
     */
    @Override
    public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {
        AuthenticationInfo info;

        if(singleRealmInfo == null) {
            info = aggregateInfo;
        } else if(aggregateInfo == null) {
            info = singleRealmInfo;
        } else {
            info = merge(singleRealmInfo, aggregateInfo);

            if(info.getPrincipals().getRealmNames().size() > 1) {
                throw new AuthenticationException("Authentication token of type [" + token.getClass() + "] " +
                        "could not be authenticated by any configured realms.  Please ensure that only one realm can " +
                        "authenticate these tokens.");
            }
        }

        return info;
    }

    //Merges the specified info argument into the aggregate argument and then returns an aggregate for continued use throughout the login process.
    @Override
    protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) {
        return super.merge(info, aggregate);
    }

    //Base implementation that will aggregate the specified singleRealmInfo into the aggregateInfo and then returns the aggregate.
    @Override
    public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
        return super.afterAllAttempts(token, aggregate);
    }
}
  • shiro-authenticator-onlyone-success.ini
ini 复制代码
[main]
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator

onlyOneAuthenticatorStrategy=com.ryo.shiro.authenticator.strategy.OnlyOneAuthenticatorStrategy
securityManager.authenticator.authenticationStrategy=$onlyOneAuthenticatorStrategy

#define and uer realms.
firstRealm=com.ryo.shiro.FirstRealm
secondRealm=com.ryo.shiro.SecondRealm
securityManager.realms=$firstRealm,$secondRealm
  • test()
java 复制代码
@Test
    public void testOnlyOneAuthenticatorStrategy() {
        Subject subject = getSubjectByPath("classpath:shiro-authenticator-onlyone-success.ini");

        UsernamePasswordToken token = new UsernamePasswordToken("ryo", "123");
        subject.login(token);

        PrincipalCollection principalCollection = subject.getPrincipals();
        assertEquals("ryo", principalCollection.toString());
    }
  • if you change the shiro-authenticator-onlyone-success.ini into
ini 复制代码
#define and uer realms.
firstRealm=com.ryo.shiro.FirstRealm
thirdRealm=com.ryo.shiro.ThirdRealm
securityManager.realms=$firstRealm,$thirdRealm

You will get an error as following.

kotlin 复制代码
org.apache.shiro.authc.AuthenticationException: Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] 
could not be authenticated by any configured realms.  Please ensure that only one realm can authenticate these tokens.

本文由博客一文多发平台 OpenWrite 发布!

相关推荐
魔道不误砍柴功1 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
失落的香蕉1 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v1 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge1 小时前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@1 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄1 小时前
SpringBoot
java·spring
数据小小爬虫2 小时前
如何用Java爬虫“偷窥”淘宝商品类目API的返回值
java·爬虫·php
暮春二十四2 小时前
关于用postman调用接口成功但是使用Java代码调用却失败的问题
java·测试工具·postman
java小吕布2 小时前
Java中Properties的使用详解
java·开发语言·后端
爱吃土豆的程序员3 小时前
在oracle官网下载资源显示400 Bad Request Request Header Or Cookie Too Large 解决办法
java·数据库·oracle·cookie