Shiro-授权

Posted wq3435

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro-授权相关的知识,希望对你有一定的参考价值。

把 realms 配置给SecurityManager

在认证的时候单个realm是这样配置的:

  <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <!-- Single realm app.  If you have multiple realms, use the \'realms\' property instead. -->
        <!-- 配置session的管理方式 -->
        <!-- <property name="sessionMode" value="native"/> -->
        <property name="realm" ref="jdbcRealm"/>
        </property>
    </bean>

 多个realm是这样配置的:

  1).将多个realm配置给 authenticator

<!-- 配置多个Realm -->
    <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <property name="realms">
            <list>
                <ref bean="jdbcRealm"/>
                <ref bean="secondRealm"/>
            </list>
        </property>
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
        </property>
    </bean>

  2).将 authenticator 配置给 SecurityManager

 <!-- 
        1.配置SecurityManager! 
    -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <!-- Single realm app.  If you have multiple realms, use the \'realms\' property instead. -->
        <!-- 配置session的管理方式 -->
        <!-- <property name="sessionMode" value="native"/> -->
        <!-- <property name="realm" ref="jdbcRealm"/> -->
        <!-- 配置多个Realm -->
        <property name="authenticator" ref="authenticator"></property>
    </bean>

 

 

   其实SecurityManager 中有一个 realms属性

<!-- 
        1.配置SecurityManager! 
    -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <!-- Single realm app.  If you have multiple realms, use the \'realms\' property instead. -->
        <!-- 配置session的管理方式 -->
        <!-- <property name="sessionMode" value="native"/> -->
        <!-- <property name="realm" ref="jdbcRealm"/> -->
        <!-- 配置多个Realm -->
        <property name="authenticator" ref="authenticator"></property>
        
        <property name="realms"></property>
    </bean>

 

   那么直接在SecurityManager中配置 realms中是否可以呢,答案是可以的。

 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <!-- Single realm app.  If you have multiple realms, use the \'realms\' property instead. -->
        <!-- 配置session的管理方式 -->
        <!-- <property name="sessionMode" value="native"/> -->
        <!-- <property name="realm" ref="jdbcRealm"/> -->
        <!-- 配置多个Realm -->
        <property name="authenticator" ref="authenticator"></property>
        
        <property name="realms">
            <list>
                <ref bean="jdbcRealm"/>
                <ref bean="secondRealm"/>
            </list>
        </property>
    </bean>
    
    <!-- 配置多个Realm -->
    <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
        </property>
    </bean>

 

  在授权中是需要将realms改为这样配置的   

  可以再 ModularRealmAuthenticator 的setRealms 中打个断点

  

  代码往前翻可以看到这里做了强制类型转换

 

 

 授权:

 

  

 

 默认拦截器:

  •   Shiro内置了很多默认的拦截器,比如身份验证,授权等相关的。默认拦截器可以参考
    org.apache.shiro.web.filter.mgt.DefaultFilter
    public enum DefaultFilter {
    
        anon(AnonymousFilter.class),
        authc(FormAuthenticationFilter.class),
        authcBasic(BasicHttpAuthenticationFilter.class),
        logout(LogoutFilter.class),
        noSessionCreation(NoSessionCreationFilter.class),
        perms(PermissionsAuthorizationFilter.class),
        port(PortFilter.class),
        rest(HttpMethodPermissionFilter.class),
        roles(RolesAuthorizationFilter.class),
        ssl(SslFilter.class),
        user(UserFilter.class);
    
        private final Class<? extends Filter> filterClass;
    
        private DefaultFilter(Class<? extends Filter> filterClass) {
            this.filterClass = filterClass;
        }
    
        public Filter newInstance() {
            return (Filter) ClassUtils.newInstance(this.filterClass);
        }
    
        public Class<? extends Filter> getFilterClass() {
            return this.filterClass;
        }
    
        public static Map<String, Filter> createInstanceMap(FilterConfig config) {
            Map<String, Filter> filters = new LinkedHashMap<String, Filter>(values().length);
            for (DefaultFilter defaultFilter : values()) {
                Filter filter = defaultFilter.newInstance();
                if (config != null) {
                    try {
                        filter.init(config);
                    } catch (ServletException e) {
                        String msg = "Unable to correctly init default filter instance of type " +
                                filter.getClass().getName();
                        throw new IllegalStateException(msg, e);
                    }
                }
                filters.put(defaultFilter.name(), filter);
            }
            return filters;
        }
    }

     

    身份验证相关的有:

     

     

   授权相关的:

    

   其他:

  

 

 为访问路径配置权限:

  1). 修改页面为 list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

    <h4>List Page</h4>
    
    <a href="admin.jsp">Admin Page</a>
    <br><br>
    
    <a href="user.jsp">User Page</a>
    <br><br>
    
    <a href="shiro/logout">Logout</a>
</body>
</html>

 

  2). 在配置文件的过滤器中添加

  

<property name="filterChainDefinitions">
            <value>
                /login.jsp= anon
                /shiro/login= anon
                /shiro/logout = logout
                
                /user.jsp = roles[user]
                /admin.jsp = roles[admin]
                
                # everything else requires authentication:
                
                /** = authc
            </value>
        </property>

 

 

  3).授权需要继承 AuthorizingRealm 类,并实现其 doGetAuthorizationInfo 方法

   AuthorizingRealm 类继承自 AuthorizingRealm , 但没有实现 AuthorizingRealm 中的 doGetAuthenticationInfo,

    所以认证和授权 只需要继承 AuthorizingRealm就可以了,同时实现他们两个抽象方法。

  

package com.java.shiro.realms;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class TestRealm extends AuthorizingRealm {

    //授权需要实现的方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        // TODO Auto-generated method stub
        return null;
    }

    //认证需要实现的方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        // TODO Auto-generated method stub
        return null;
    }

}

 

 

 

  查看源码得到,多Realm授权中只要有一个通过就可以 ModularRealmAuthorizer

public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
        assertRealmsConfigured();
        for (Realm realm : getRealms()) {
            if (!(realm instanceof Authorizer)) continue;
            if (((Authorizer) realm).hasRole(principals, roleIdentifier)) {
                return true;
            }
        }
        return false;
    }

 

 

  授权Realm的实现

    1). 在原先的ShiroRealm的基础上修改,修改其继承

package com.java.shiro.realms;

import java.util.HashSet;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

public class ShiroRealm extends AuthorizingRealm {

    /**
     * 用于认证的Realm保持不变
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        System.out.println("[FirstRealm] doGetAuthenticationInfo " + token);

        // 1. 把AuthenticationToken 转换为UsernamePasswordToken
        UsernamePasswordToken up = (UsernamePasswordToken) token;
        // 2. 从UsernamePasswordToken 中来获取username
        String username = up.getUsername();
        // 3. 调用数据库的方法,从数据库中查询username对应的用户记录
        System.out.println("从数据库中获取userName :" + username + " 所对应的用户信息.");
        // 4. 若用户不存在,则可以抛出 UnknownAccoountException 异常
        if ("unknown".equals(username)) {
            throw new UnknownAccountException("用户不存在");
        }
        // 5. 根据用户信息的情况,决定是否需要抛出其他的AuthencationException 异常 假设用户被锁定
        if ("monster".equals(username)) {
            throw new LockedAccountException("用户被锁定");
        }
        // 6. 根据用户的情况,来构建AuthenticationInfo 对象并返回,通常使用的是
        // SimpleAuthenticationInfo
        // 以下信息是从数据库获取的.

        Object principal = username; // principal 认证的实体信息.
                                        // 可以是username,也可以是数据表对应的用户的实体类对象
                                        // String credentials =
                                        // "fc1709d0a95a6be30bc5926fdb7f22f4";
                                        // // credentials:密码
        String credentials = null; // credentials:密码
        String realmName = getName();
        AuthenticationInfo info = null;/*
                                         * new
                                         * SimpleAuthenticationInfo(principal,
                                         * credentials, realmName);
                                         */

        if ("admin".equals(username)) {
            credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
        } else if ("user".equals(username)) {
            credentials = "098d2c478e9c11555ce2823231e02ec1";
        }

        ByteSource credentialsSalt = ByteSource.Util.bytes(username);// 这里的参数要给个唯一的;

        info = new SimpleAuthenticationInfo(principal, credentials,
                credentialsSalt, realmName);

        return info;
    }

    public static void main(String[] args) {
        String hashAlgorithmName = "MD5";
        String credentials = "123456";
        int hashIterations = 1024;
        ByteSource credentialsSalt = ByteSource.Util.bytes("user");
        Object obj = new SimpleHash(hashAlgorithmName, credentials,
                credentialsSalt, hashIterations);
        System.out.println(obj);
    }

    /**
     * 这个是用于授权的Realm
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        // 1. 从 PrincipalCollection 中获取来获取登录用户的信息
        //由于我们配置了多个Realm 一个返回的是 seconde,一个返回的是认证实体,这两个Realm在配置认证的时候是有顺序地
        /**
         * <property name="realms">
         *    <list>
         *        <ref bean="jdbcRealm"/>
         *        <ref bean="secondRealm"/>
         *    </list>
         *    </property>
         *
         * 当我们在获取Principal的时候也是有顺序的
         */
        Object principal = principals.getPrimaryPrincipal();

        // 2. 利用登录用户的信息来判断当前用户的角色或权限(可能需要查询数据库)
        Set<String> roles = new HashSet<>();
        roles.add("user"); // 这里无论登录的是user 还是 admin 都存放一个user角色
        if ("admin".equals(principal)) {
            roles.add("admin"); // 如果是admin 授权一个admin角色
        }
        AuthorizationInfo info = new SimpleAuthorizationInfo(roles);
        return info;
    }
}

 

   

  

  由于我们配置了多个Realm 一个返回的是 seconde,一个返回的是认证实体,这两个Realm在配置认证的时候是有顺序地

    <property name="realms">
            <list>
                <ref bean="jdbcRealm"/>
                <ref bean="secondRealm"/>
            </list>
        </property>

 

  当我们在获取Principal的时候也是有顺序的

  通过查看源代码

public Object getPrimaryPrincipal() {
        if (isEmpty()) {
            return null;
        }
        return iterator().next();
    }
public Iterator iterator() {
        return asSet().iterator();
    }

 

 1 public Set asSet() {
 2         if (realmPrincipals == null || realmPrincipals.isEmpty()) {
 3             return Collections.EMPTY_SET;
 4         }
 5         Set aggregated = new LinkedHashSet();
 6         Collection<Set> values = realmPrincipals.values();
 7         for (Set set : values) {
 8             aggregated.addAll(set);
 9         }
10         if (aggregated.isEmpty()) {
11             return Collections.EMPTY_SET;
12         }
13         return Collections.unmodifiableSet(aggregated);
14     }

  上述代码第6行得到  realmPrincipals 的类型 LinkedHashMap,只有这样才能保证我得到的是希望的那个值。

 

 

 启动Tomcat

使用admin登录 可以访问两个页面,user登录只能访问一个页面

 

  • Permissions

  

 

 

 

  • 授权流程

  

 

   

 

  

 

以上是关于Shiro-授权的主要内容,如果未能解决你的问题,请参考以下文章

全栈编程系列SpringBoot整合Shiro(含KickoutSessionControlFilter并发在线人数控制以及不生效问题配置启动异常No SecurityManager...)(代码片段

如何仅使用 Apache Shiro 进行授权?

Shiro安全框架——快速入门登录拦截用户认证请求授权

006-shiro授权

Apache Shiro学习----授权

SpringBoot实现基于shiro安全框架的,配合thymeleaf模板引擎的用户认证和授权