Shiro 认证授权详解

  • Post author:
  • Post category:其他


1     权限管理

1.1用户身份认证

1.1.1  概念

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用

指纹

等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。

1.1.2  用户名密码身份认证流程


1.1.3  关键对象

上边的流程图中需要理解以下关键对象:


  • Subject:主体

访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;



  • Principal

    :身份

    credential

    信息

是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。


  • Credential:凭证信息

是只有主体自己知道的安全信息,如密码、证书等。

1.2   授权

1.2.1  概念

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

1.2.2 授权流程

下图中橙色为授权流程。

1.2.3 关键对象

授权可简单理解为who对what(which)进行How操作:

  • Who,即主体(Subject),主体需要访问系统中的资源。
  • What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
  • How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。

权限分为粗颗粒和细颗粒,粗颗粒权限是指对资源类型的权限,细颗粒权限是对资源实例的权限。

主体、资源、权限关系如下图:


1.2.4 权限模型

对上节中的主体、资源、权限通过数据模型表示。

主体(账号、密码)

资源(资源名称、访问地址)

权限(权限名称、资源id)

角色(角色名称)

角色和权限关系(角色id、权限id)

主体和角色关系(主体id、角色id)

如下图:

1.2.5  权限分配

对主体分配权限,主体只允许在权限范围内对资源进行操作,比如:对u01用户分配商品修改权限,u01用户只能对商品进行修改。

权限分配的数据通常需要持久化,根据上边的数据模型创建表并将用户的权限信息存储在数据库中。

1.2.6  权限控制

用户拥有了权限即可操作权限范围内的资源,系统不知道主体是否具有访问权限需要对用户的访问进行控制。

1.2.6.1 基于角色的访问控制

RBAC基于角色的访问控制(Role-BasedAccess Control)是以角色为中心进行访问控制,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:

上图中的判断逻辑代码可以理解为:

if(主体.hasRole(“总经理角色id”)){

查询工资

}

缺点:以角色进行访问控制粒度较粗,如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断主体的角色是否是总经理或部门经理”,系统可扩展性差。

修改代码如下:

if(主体.hasRole(总经理角色id”) ||  主体.hasRole(“部门经理角色id”)){

查询工资

}

1.2.6.2 基于资源的访问控制

RBAC基于资源的访问控制(Resource-BasedAccess Control)是以资源为中心进行访问控制,比如:主体必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:

上图中的判断逻辑代码可以理解为:

if(主体.hasPermission(“查询工资权限标识”)){

查询工资

}

优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也只需要将“查询工资信息权限”添加到“部门经理角色”的权限列表中,判断逻辑不用修改,系统可扩展性强。

1.3   Shiro架构

1.3.1  Subject

Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。         Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权

1.3.2  SecurityManager

SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。

SecurityManager是一个接口,继承了Authenticator,Authorizer, SessionManager这三个接口。

1.3.3  Authenticator

Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

1.3.4  Authorizer

Autorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

1.3.5  Realm

Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。

1.3.6  sessionManager

sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

1.3.7  SessionDAO

SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

1.3.8  CacheManager

CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。

1.3.9  Cryptography

Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

1.4   shiro的jar包

与其它java开源框架类似,将shiro的jar包加入项目就可以使用shiro提供的功能了。shiro-core是核心包必须选用,还提供了与web整合的shiro-web、与spring整合的shiro-spring、与任务调度quartz整合的shiro-quartz等,下边是shiro各jar包的maven坐标。

     <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-core</artifactId>

          <version>1.2.3</version>

      </dependency>

      <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-web</artifactId>

          <version>1.2.3</version>

      </dependency>

      <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-spring</artifactId>

          <version>1.2.3</version>

      </dependency>

      <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-ehcache</artifactId>

          <version>1.2.3</version>

      </dependency>

    <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-quartz</artifactId>

          <version>1.2.3</version>

      </dependency>

也可以通过引入shiro-all包括shiro所有的包:

  <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-all</artifactId>

          <version>1.2.3</version>

   </dependency>

1.5   shiro认证流程


1.5.1  认证代码

        // 用户登陆、用户退出
	@Test
	public void testLoginLogout() {

		// 构建SecurityManager工厂,IniSecurityManagerFactory可以从ini文件中初始化SecurityManager环境
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(
				"classpath:shiro.ini");

		// 通过工厂创建SecurityManager
		SecurityManager securityManager = factory.getInstance();
		
		// 将securityManager设置到运行环境中
		SecurityUtils.setSecurityManager(securityManager);

		// 创建一个Subject实例,该实例认证要使用上边创建的securityManager进行
		Subject subject = SecurityUtils.getSubject();

		// 创建token令牌,记录用户认证的身份和凭证即账号和密码 
		UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");

		try {
			// 用户登陆
			subject.login(token);
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// 用户认证状态

		Boolean isAuthenticated = subject.isAuthenticated();

		System.out.println("用户认证状态:" + isAuthenticated);

		// 用户退出

		subject.logout();

		isAuthenticated = subject.isAuthenticated();

		System.out.println("用户认证状态:" + isAuthenticated);

	}

1.5.2  认证执行流程

1、 创建token令牌,token中有用户提交的认证信息即账号和密码

2、 执行subject.login(token),最终由securityManager通过Authenticator进行认证

3、 Authenticator的实现 ModularRealmAuthenticator调用realm从ini配置文件取用户真实的账号和密码,这里使用的是IniRealm(shiro自带)

4、 IniRealm先根据token中的账号去ini中找该账号,如果找不到则给ModularRealmAuthenticator返回null,如果找到则匹配密码,匹配密码成功则认证通过。

1.5.3  常见的异常

  • UnknownAccountException

账号不存在异常如下


:org.apache.shiro.authc.UnknownAccountException



: No account found for user


。。。。

  • IncorrectCredentialsException

当输入密码错误会抛此异常,如下:


org.apache.shiro.authc.IncorrectCredentialsException



: Submitted credentials for    token[org.apache.shiro.authc.UsernamePasswordToken – zhangsan, rememberMe=false] didnot match the expected credentials.

更多如下:

DisabledAccountException(帐号被禁用)

LockedAccountException(帐号被锁定)

ExcessiveAttemptsException(登录失败次数过多)

ExpiredCredentialsException(凭证过期)等

1.6 自定义Realm

1.6.1 Shiro提供的Realm

最基础的是Realm接口,CachingRealm负责缓存处理,AuthenticationRealm负责认证,AuthorizingRealm负责授权,通常自定义的realm继承AuthorizingRealm。

1.6.2  自定义Realm

public class CustomRealm1 extends AuthorizingRealm {

	@Override
	public String getName() {
		return "customRealm1";
	}

	//支持UsernamePasswordToken
	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof UsernamePasswordToken;
	}

	//认证
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		
		//从token中 获取用户身份信息
		String username = (String) token.getPrincipal();
		//拿username从数据库中查询
		//....
		//如果查询不到则返回null
		if(!username.equals("zhang")){//这里模拟查询不到
			return null;
		}
		
		//获取从数据库查询出来的用户密码 
		String password = "123";//这里使用静态数据模拟。。
		
		//返回认证信息由父类AuthenticatingRealm进行认证
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
				username, password, getName());

		return simpleAuthenticationInfo;
	}
	
//授权
@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		// 获取身份信息
		String username = (String) principals.getPrimaryPrincipal();
		// 根据身份信息从数据库中查询权限数据
		//....这里使用静态数据模拟
		List<String> permissions = new ArrayList<String>();
		permissions.add("user:create");
		permissions.add("user.delete");
		
		//将权限信息封闭为AuthorizationInfo
		
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		for(String permission:permissions){
			simpleAuthorizationInfo.addStringPermission(permission);
		}
		
		return simpleAuthorizationInfo;

}


1.7   shiro授权流程

1.8   授权方式

Shiro 支持三种方式的授权:

  • 编程式:通过写if/else授权代码块完成:

Subject subject =SecurityUtils.getSubject();

if(subject.hasRole(“admin”)) {

//有权限

} else {

//无权限

}

  • 注解式:通过在执行的Java方法上放置相应的注解完成:

@RequiresRoles(“admin”)

public void hello() {

//有权限

}

  • JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成:

<shiro:hasRolename=”admin”>

<!— 有权限—>

</shiro:hasRole>

1.8.1  权限字符串规则

权限字符串的规则是:“资源标识符:操作:资源实例标识符”,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。

例子:


用户创建权限:


user:create


,或


user:create:*


用户修改实例


001


的权限:


user:update:001


用户实例


001


的所有权限:


user





*





001

1.8.2  基于角色的授权

// 用户授权检测 基于角色授权
// 是否有某一个角色
System.out.println("用户是否拥有一个角色:" + subject.hasRole("role1"));
// 是否有多个角色
System.out.println("用户是否拥有多个角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));

对应的check方法:

subject.checkRole("role1");
subject.checkRoles(Arrays.asList("role1", "role2"));

上边check方法如果授权失败则抛出异常:


org.apache.shiro.authz.UnauthorizedException:Subject does not have role […..]

1.8.3  基于资源授权

// 基于资源授权
System.out.println("是否拥有某一个权限:" + subject.isPermitted("user:delete"));
System.out.println("是否拥有多个权限:" + subject.isPermittedAll("user:create:1",	"user:delete"));

对应的check方法:

subject.checkPermission("sys:user:delete");
subject.checkPermissions("user:create:1","user:delete");

上边check方法如果授权失败则抛出异常:


org.apache.shiro.authz.UnauthorizedException:Subject does not have permission [….]

1.8.4  授权执行流程

1、 执行subject.isPermitted(“user:create”)

2、 securityManager通过ModularRealmAuthorizer进行授权

3、ModularRealmAuthorizer调用realm获取权限信息

4、 ModularRealmAuthorizer再通过permissionResolver解析权限字符串,校验是否匹配

1.9   shiro与spring web项目整合

1.9.1  web.xml添加shiro Filter

<!-- shiro过虑器,DelegatingFilterProx会从spring容器中找shiroFilter -->

    <filter>

    <filter-name>shiroFilter</filter-name>

    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

       <init-param>

           <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>

1.9.2  spring-shiro.xml

<!-- Shiro 的Web过滤器 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<!-- 如果没有认证将要跳转的登陆地址,http可访问的url,如果不在表单认证过虑器FormAuthenticationFilter中指定此地址就为身份认证地址 -->
		<property name="loginUrl" value="/login.action" />
<!-- 没有权限跳转的地址 -->
		<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- shiro拦截器配置 -->
		<property name="filters">
			<map>
				<entry key="authc" value-ref="formAuthenticationFilter" />
			</map>
		</property>
		<property name="filterChainDefinitions">
			<value>
				<!-- 必须通过身份认证方可访问,身份认 证的url必须和过虑器中指定的loginUrl一致 -->
				/loginsubmit.action = authc
				<!-- 退出拦截,请求logout.action执行退出操作 -->
				/logout.action = logout
				<!-- 无权访问页面 -->
				/refuse.jsp = anon
				<!-- roles[XX]表示有XX角色才可访问 -->
				/item/list.action = roles[item],authc
				/js/** anon
				/images/** anon
				/styles/** anon
				<!-- user表示身份认证通过或通过记住我认证通过的可以访问 -->
				/** = user
				<!-- /**放在最下边,如果一个url有多个过虑器则多个过虑器中间用逗号分隔,如:/** = user,roles[admin] -->

			</value>
		</property>
	</bean>


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

	<!-- 自定义 realm -->
	<bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">
	</bean>
	<!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
	<bean id="formAuthenticationFilter"
		class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
		<!-- 表单中账号的input名称 -->
		<property name="usernameParam" value="usercode" />
		<!-- 表单中密码的input名称 -->
		<property name="passwordParam" value="password" />
		<!-- <property name="rememberMeParam" value="rememberMe"/> -->
		<!-- loginurl:用户登陆地址,此地址是可以http访问的url地址 -->
		<property name="loginUrl" value="/loginsubmit.action" />
	</bean>


securityManager

:这个属性是必须的。


loginUrl

:没有登录认证的用户请求将跳转到此地址,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。


unauthorizedUrl

:没有权限默认跳转的页面。

1.9.3使用shiro注解授权

在springmvc.xml中配置shiro注解支持,可在controller方法中使用shiro注解配置权限:

        <!-- 开启aop,对类代理 -->
	<aop:config proxy-target-class="true"></aop:config>
	<!-- 开启shiro注解支持 -->
	<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
	    <property name="securityManager" ref="securityManager" />
        </bean> 

修改Controller代码,在方法上添加授权注解,如下:

        // 查询商品列表
	@RequestMapping("/queryItem")
	@RequiresPermissions("item:query")
	public ModelAndView queryItem() throws Exception {

上边代码

@RequiresPermissions


(


“item:query”


)


表示必须拥有“


item:query


”权限方可执行。

1.10     session管理

在applicationContext-shiro.xml中配置sessionManager:

        <!-- 安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="userRealm" />
		<property name="sessionManager" ref="sessionManager" />
	</bean>
    <!-- 会话管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- session的失效时长,单位毫秒 -->
        <property name="globalSessionTimeout" value="600000"/>
        <!-- 删除失效的session -->
        <property name="deleteInvalidSessions" value="true"/>
    </bean>

2     附:

2.1   shiro过虑器


过滤器简称


对应的java


anon


org.apache.shiro.web.filter.authc.AnonymousFilter


authc


org.apache.shiro.web.filter.authc.FormAuthenticationFilter


authcBasic


org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter


perms


org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter


port


org.apache.shiro.web.filter.authz.PortFilter


rest


org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter


roles


org.apache.shiro.web.filter.authz.RolesAuthorizationFilter


ssl


org.apache.shiro.web.filter.authz.SslFilter


user


org.apache.shiro.web.filter.authc.UserFilter


logout


org.apache.shiro.web.filter.authc.LogoutFilter

anon:例子/admins/**=anon 没有参数,表示可以匿名使用。

authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数

roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles[“admin,guest”],每个参数通过才算通过,相当于hasAllRoles()方法。

perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms[“user:add:*,user:modify:*”],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。

rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等。

port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString

是你访问的url里的?后面的参数。

authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证

ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查

注:

anon,authcBasic,auchc,user是认证过滤器,

perms,roles,ssl,rest,port是授权过滤器

2.2   shiro的jsp标签

Jsp页面添加:<%@tagliburi=”http://shiro.apache.org/tags” prefix=”shiro”%>


标签名称


标签条件(均是显示标签内容)


<shiro:authenticated>


登录之后


<shiro:notAuthenticated>


不在登录状态时


<shiro:guest>


用户在没有RememberMe


<shiro:user>


用户在RememberMe


<shiro:hasAnyRoles name=”abc,123″ >


在有abc

或者123角色时


<shiro:hasRole name=”abc”>


拥有角色abc


<shiro:lacksRole name=”abc”>


没有角色abc


<shiro:hasPermission name=”abc”>


拥有权限资源abc


<shiro:lacksPermission name=”abc”>


没有abc

权限资源


<shiro:principal>


显示用户身份名称



<shiro:principalproperty=”username”/>

显示用户身份中的属性值

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/chen_changying/article/details/79714112



版权声明:本文为chen_changying原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。