Cadre d'autorisation de sécurité - Shiro (1)

C'est le 26e jour de ma participation au défi de mise à jour d'août. Pour plus de détails sur l'événement, veuillez consulter : Défi de mise à jour d'août

1. Introduction

  • Apache Shiro est un framework de sécurité (autorisation) pour Java ;

  • Shiro peut facilement développer une application assez bonne, qui peut être utilisée non seulement dans l'environnement JavaSE, mais aussi dans l'environnement JavaEE ;

  • Fonctions principales : Authentification, Autorisation, chiffrement, gestion des sessions, intégration avec le Web, mise en cache, etc. ;

Architecture de Shiro

  • Sujet : L'objet avec lequel le code de l'application interagit directement est le Sujet , ce qui signifie que le cœur de l'API externe de Shiro est le Sujet. Le sujet représente "l'utilisateur" actuel , cet utilisateur n'est pas nécessairement une personne spécifique, tout ce qui interagit avec l'application actuelle est un sujet, comme les robots d'indexation Web, les robots, etc. ; toutes les interactions avec le sujet seront déléguées à SecurityManager ; le sujet est en fait a Sur la façade, SecurityManager est le véritable exécuteur ;
  • SecurityManager : Security Manager ; c'est-à-dire que toutes les opérations liées à la sécurité interagiront avec SecurityManager ; et il gère tous les sujets ; on peut voir que c'est le noyau de Shiro, qui est responsable de l'interaction avec les autres composants de Shiro , ce qui est équivalent au DispatcherServlet dans le caractère SpringMVC de
  • Realm : Shiro obtient des données de sécurité (telles que les utilisateurs, les rôles, les autorisations) de Realm, c'est-à-dire que si le SecurityManager veut vérifier l'identité de l'utilisateur, il doit alors obtenir l'utilisateur correspondant de Realm pour comparaison afin de déterminer si l'utilisateur l'identité est légale ; il doit également obtenir l'identité correspondante de l'utilisateur auprès de Realm. Le rôle/l'autorité pour vérifier si l'utilisateur peut fonctionner ; Realm peut être considéré comme une source de données

2. Développement intégré avec Spring

2.1 Certification Shiro

2.1.1 Fichier de configuration

web.xml

<!-- Shiro 过滤器定义 -->  
	<filter>
		<filter-name>shiroFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<init-param>
		<!-- 该值缺省为 false,表示生命周期由 SpringApplicationContext;
			 设置为 true 表示由 ServletContainer 管理
		 -->
			<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>
复制代码

applicationContext.xml

    <!-- 1.安全管理器SecurityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myRealm"/>
    </bean>

    <!--2.配置CacheManager
        2.1 需要加入ehcache 的jar包,及配置文件
    -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
    </bean>

    <!-- 3.自定义Realm
     -->
    <bean id="myRealm" class="com.xiaojian.shiro.realms.MyRealm"/>


    <!-- 4.保证实现了Shiro内部lifecycle函数的bean执行
        可以来调用IOC容器中 shiro bean 生命周期方法
     -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 5.开启Shiro注解,必须配置了lifecycleBeanPostProcessor -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <!-- 5.Shiro过滤器
        id 必须和web.xml文件中配置DelegatingFilterProxy的<filter-name>一致
        因为Shiro会在 IOC容器中查询和 <filter-name> 名字对应的 filter bean
     -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- Shiro的核心安全接口,这个属性是必须的 -->
        <property name="securityManager" ref="securityManager"/>
        <!-- 身份认证失败,则跳转到登录页面的配置 -->
        <property name="loginUrl" value="/login.jsp"/>
        <property name="successUrl" value="success.jsp"/>
        <property name="unauthorizedUrl" value="unauth.jsp"/>

        <!-- Shiro连接约束配置,即过滤链的定义 -->
        <property name="filterChainDefinitions">
            <value>
                /login.jsp=anon
                /**=authc
            </value>
        </property>
    </bean>
复制代码

MyRealm.java

public class MyRealm extends AuthorizingRealm{
	
	@Resource
	private BloggerService bloggerService;
	
	/**
	 * AuthorizationInfo:角色的权限集合
	 * 获取授权信息
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * AuthenticationInfo:用户的角色集合
	 * 登录验证
	 * token: 令牌,基于用户名密码的名牌
	 */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证逻辑");

        // 假设用户名、密码
        String username = "xiaojian";

        // 1.转换类型,获取Token
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        // 2.对比用户名
        if(!token.getUsername().equals(username)){
            // 用户名不存在
            return null;    //// Shiro底层会抛出 UnknownAccountException 异常
        }
        // 3.根据用户情况,构建AuthenticationInfo对象返回。通常使用实现类:SimpleAuthenticationInfo
        // 3.1 principal:认证的实体信息,可以是username,也可以是对应的实体类对象
        Object principal = null;
        // 3.2 hashedCredentials:密码
        Object hashedCredentials = "1234"; //a20b8e682a72eeac0049847855cecb86
        // 3.3 realName:当前realm对象的name,调用父类的getName()方法
        String realmName = getName();

        SimpleAuthenticationInfo simpleAuthenticationInfo = null;
       simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,hashedCredentials,realmName);

        return simpleAuthenticationInfo;
    }
}
复制代码

2.1.2 Le filtre par défaut dans Shiro

classe de filtre nom du filtre Exemple
anon Pas de paramètres, accès anonyme /login.jsp=anon
authc Aucun paramètre, une authentification (login) est nécessaire pour accéder /admin/**=authentique
utilisateur Aucun paramètre, indiquant qu'il doit y avoir un utilisateur
permanentes Il peut y avoir plusieurs paramètres, qui doivent être placés entre guillemets, et les paramètres sont séparés par des virgules. Lorsqu'il y a plusieurs paramètres, chaque paramètre doit être passé, ce qui équivaut à la méthode isPermitedAll() /admin/*=perms[user:add] /admin/**=perms["user:add,user:update"]
les rôles Le filtre de rôle, qui détermine si l'utilisateur actuel spécifie un rôle. Les règles sont les mêmes que ci-dessus. Équivalent à la méthode hasAllRoles() /admin/**=rôles["admin,invité"]
Se déconnecter Lors de la déconnexion, une certaine fonction est terminée : toute session existante sera invalide et toute identité sera perdue (dans le programme Web, le cookie RememberMe sera également supprimé)

2.1.3 Modèle de correspondance d'URL

Utilisez le mode de style Ant, le caractère générique du chemin Ant prend en charge ?, *, **, la correspondance des caractères génériques n'inclut pas le séparateur de répertoire "/"

  • ? : correspond à un caractère, tel que : /admin?, correspond à : /admin1 ; ne correspond pas : /admin123, /admin/
  • * : correspond à zéro ou plusieurs chaînes ou à un chemin
  • ** : correspond à zéro ou plusieurs chemins dans le chemin

2.1.4 Ordre de correspondance des URL

​ 第一次匹配优先的方式。所以一般 /* 的路径访问都放在后面。

2.1.5 Shiro加密

Shiro 认证中密码比对:使用AuthorizingRealm中的 credentialsMatcher 进行的密码比对。

1、md5加密(不可逆的)

(1).如何把一个字符串加密为MD5;

(2).替换当前 Realm的credentialsMatcher 属性,直接使用HashedCredentialsMather对象,并设置加密算法。

applicationContext.xml

    <!-- 3.自定义Realm
        实现了 Realm 接口的bean
     -->
    <bean id="myRealm" class="com.xiaojian.shiro.realms.MyRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!--加密方式-->
                <property name="hashAlgorithmName" value="MD5"/>
                <!--加密的次数-->
                <property name="hashIterations" value="1024"/>
            </bean>
        </property>
    </bean>
复制代码

这时候服务器会自动将浏览器传来的密码使用 MD5加密,加密1024次。

2、md5盐值加密

applicationContext.xml不变

(1). doGetAuthenticationInfo方法返回值创建SimpleAuthenticationInfo,使用构造器:

​ SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, realmName);

(2). 使用ByteSource.Util.bytes(username);加密盐值

(3). 盐值需唯一,一般使用随机字符串、user id

(4). 可以使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 计算盐值加密后的值。

   /**
     * 执行认证逻辑
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证逻辑");

        // 1.转换类型,获取Token
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        String username = token.getUsername();

        //3.根据用户情况,构建AuthenticationInfo对象返回,通常使用实现类 SimpleAuthenticationInfo
// 3.1 principal:认证的实体信息,可以是username,也可以是对应的实体类对象
        Object principal = username;
// 3.2 hashedCredentials:密码
        Object hashedCredentials = null; //a20b8e682a72eeac0049847855cecb86
        // 2.对比用户名、密码
        if("user".equals(username)){
            hashedCredentials = "3e042e1e3801c502c05e13c3ebb495c9";
        } else if("admin".equals(username)){
            hashedCredentials = "c34af346c89b8b03438e27a32863c9b5";
        }
// 3.3 credentialsSalt:加密盐值
        ByteSource credentialsSalt = ByteSource.Util.bytes(username);
// 3.4 realName:当前realm对象的name,调用父类的getName()方法
        String realmName = getName();

        SimpleAuthenticationInfo simpleAuthenticationInfo =  new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, realmName);

        return simpleAuthenticationInfo;
    }
复制代码

MD5Test.java

public class MD5Test {
    public static void main(String[] args) {
        String hashAlgorithmName = "MD5";
        Object credentials = "1234";
        Object salt = "admin";
        int hashIterations = 1024;

        Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
        System.out.println(result);
    }
}
复制代码

2.1.6 多Reaml

为什么使用多 Realm ?

​ 根据不同的登录需求,需要做不同的验证。如:手机号登录、邮箱登录。。。

使用 ModularRealmAuthenticator类,属性Collection realms;注入 Realm集合

SecondRealm.java 同MyRealm类,将验证密码改为 SHA1加密后的值。

applicationContext.xml

    <bean id="secondRealm" class="com.xiaojian.shiro.realms.SecondRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!--加密方式-->
                <property name="hashAlgorithmName" value="SHA1"/>
                <!--加密的次数-->
                <property name="hashIterations" value="1024"/>
            </bean>
        </property>
    </bean>
复制代码
认证策略 (AuthenticationStrategy)

AuthenticationStrategy 接口的默认实现:

  • FirstSuccessfulStrategy: 还要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
  • AtLeastOneSuccessfulStrategy: 只要有一个 Realm 验证成功即可,和 FirstSuccessfulStrategy 不同,将返回所有 Realm 身份验证成功的认证信息;
  • AllSuccessfulStrategy: 所有 Realm 验证成功才算成功,且返回所有 Realm 身份验证成功的认证信息,如果有一个失败就失败了。
  • ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy 策略

配置:

<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!--        <property name="realms">-->
<!--            <list>-->
<!--                <ref bean="myRealm"/>-->
<!--                <ref bean="secondRealm"/>-->
<!--            </list>-->
<!--        </property>-->
    <property name="authenticationStrategy">
        <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/>
    </property>
</bean>
复制代码

使用多 Realm 后,可以把 authenticator 配置给 SecurityManager

通常将所有的 Realm 配置给 安全管理器SecurityManager

之后
<!-- 1.安全管理器SecurityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="authenticator" ref="authenticator"/>
        
        <property name="realms">
            <list>
                <ref bean="myRealm"/>
                <ref bean="secondRealm"/>
            </list>
        </property>        
    </bean>
复制代码

2.2 Shiro 授权

多 Realm 实现授权时,有一个通过,都是授权通过。

Shiro过滤器添加角色拦截    
/user.jsp = roles[user]
/admin.jsp = roles[admin]
复制代码

MyRealm.java

/**
 * 执行授权逻辑
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("MyRealm 执行授权逻辑");
    // 1. 从 principalCollection 获取用户信息 (是用户名还是用户对象,取决于你认证时 principal参数 放入的是啥)
    Object principal = principalCollection.getPrimaryPrincipal();
    // 2. 利用登录用户的信息来获取当前用户的角色或权限
    Set<String> roles = new HashSet<>();
    roles.add("user");
    if("admin".equals(principal)){
        roles.add("admin");
    }
    // 3. 创建 SimpleAuthorizationInfo,并设置其 roles 属性
    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    authorizationInfo.setRoles(roles);

    // 4. 返回 SimpleAuthorizationInfo
    return authorizationInfo;
}
复制代码

2.3 Shiro 标签

Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制,如根据登录用户显示相应的页面按钮。

标签 描述 示例
guest 用户没有身份验证时显示相应信息,即游客信息 image-20200307234952932
user 用户已经经过认证/记住我登录后显示相应的信息 image-20200308000528385
authenticated 用户已经身份验证通过,即 Subject.login登录成功,不是记住我登录的 image-20200308000656696
notAuthenticated L'utilisateur n'est pas authentifié, c'est-à-dire qu'il n'appelle pas Subject.login pour se connecter, y compris en se souvenant que ma connexion automatique n'est pas non plus authentifiée. image-20200308000937683
principal Afficher les informations d'identité de l'utilisateur, appelées par défaut image-20200308001012588
aRôle Si le sujet actuel a un rôle, le contenu du corps sera affiché image-20200308001040891
aAnyRoles Si le sujet actuel a un rôle (ou une relation), le contenu du corps sera affiché. image-20200308001147878
manque de rôle Si le sujet actuel n'a aucun rôle, le contenu du corps sera affiché image-20200308001256323
aPermission Si le sujet actuel a la permission, le contenu du corps sera affiché image-20200308001314089
n'a pas la permission Si le sujet actuel n'a pas la permission, le contenu du corps sera affiché image-20200308001326991

Je suppose que tu aimes

Origine juejin.im/post/7000598675672006693
conseillé
Classement