Shiro-授权

时间:2023-03-09 10:06:10
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-授权

  

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

Shiro-授权

授权:

Shiro-授权

  Shiro-授权

 默认拦截器:

  •   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;
    }
    }

    身份验证相关的有:

    Shiro-授权

  授权相关的:

  Shiro-授权  

  其他:

  Shiro-授权

为访问路径配置权限:

  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();
}
 public Set asSet() {
if (realmPrincipals == null || realmPrincipals.isEmpty()) {
return Collections.EMPTY_SET;
}
Set aggregated = new LinkedHashSet();
Collection<Set> values = realmPrincipals.values();
for (Set set : values) {
aggregated.addAll(set);
}
if (aggregated.isEmpty()) {
return Collections.EMPTY_SET;
}
return Collections.unmodifiableSet(aggregated);
}

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

Shiro-授权

启动Tomcat

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

Shiro-授权

  • Permissions

  Shiro-授权

Shiro-授权

  • 授权流程

  Shiro-授权

  Shiro-授权

  Shiro-授权