授权,就是访问控制,控制某个用户在应用程序中是否有权限做某件事
2.授权检查的例子
用户是否能访问某个网页,编辑数据,或打使用这台打印机
3.角色与权限关联
需要在应用程序中对用户和权限建立关联:通常的做法是将权限分配给角色,然后将角色分配给一个或多个用户。
4.授权三要素
4.1、权限
权限是shiro权限系统中的原子单位,明确定义了用户的行为,例如用户在系统中可以做什么不可以做什么。
例如:下载某个文件,访问/user/list链接,删除用户等等。权限一般都是基于资源和对资源的操作(例如CRUD等)的。
权限只是反映了行为(对资源的操作),不反应谁去执行。权限要分配给用户,或者角色,来确定哪些用户拥有某些权限。
一个格式良好的权限声明可以清晰表达出用户对该资源拥有的权限。在 Shiro 中主要通过通配符表达式来完成权限的描述。
4.2、资源
系统中的所有可访问,可操作的所有的东西。例如,系统中的链接,按钮,方法,等等。
4.3、用户
也叫主体(Subjects),访问应用的人或者其他的,例如另一个应用程序。用户只有被授权了才可以访问系统的资源。
A、权限
权限是Apache Shiro安全机制最核心的元素。它在应用程序中明确声明了被允许的行为和表现。一个格式良好好的权限声明可以清晰表达出用户对该资源拥有的权限。
大多数的资源会支持典型的CRUD操作(create,read,update,delete),但是任何操作建立在特定的资源上才是有意义的。因此,权限声明的根本思想就是建立在资源以及操作上。
而我们通过权限声明仅仅能了解这个权限可以在应用程序中做些什么,而不能确定谁拥有此权限。 于是,我们就需要在应用程序中对用户和权限建立关联。 通常的做法就是将权限分配给某个角色,然后将这个角色关联一个或多个用户。
B、角色
1、传统角色(隐式角色):
一个角色代表着一系列的操作,当需要对某一操作进行授权验证时,只需判断是否是该角色即可。这种角色权限相对简单、模糊,不利于扩展。
2、权限角色(显式角色):
一个角色拥有一个权限的集合。授权验证时,需要判断当前角色是否拥有该权限。这种角色权限可以对该角色进行详细的权限描述,适合更复杂的权限设计。 Shiro官方推荐使用这种方式。
下面将详细描述对两种角色模式的授权实现。
Shiro支持三种方式实现授权过程:
• 编码实现
• 注解实现
• JSP Taglig实现
1、在应用程序中调用授权验证方法(Subject的isPermitted*或hasRole*等)
2、Subject的实例通常是DelegatingSubject类(或子类)的实例对象,在认证开始时,会委托应用程序设置的securityManager实例调用相应的isPermitted*或hasRole*方法。
3、接下来SecurityManager会委托内置的Authorizer的实例(默认是ModularRealmAuthorizer 类的实例,类似认证实例,它同样支持一个或多个Realm实例认证)调用相应的授权方法。
4、每一个Realm将检查是否实现了相同的 Authorizer 接口。然后,将调用Reaml自己的相应的授权验证方法。
Step 1:应用程序或框架代码调用任何 Subject 的hasRole*, checkRole*, isPermitted*,或者checkPermission*方法的变体,传递任何所需的权限
Step 2:Subject 的实例—通常是 DelegatingSubject(或子类),调用securityManager 的对应的方法。
Step 3:SecurityManager 调用 org.apache.shiro.authz.Authorizer 接口的对应方法。默认情况下,authorizer 实例是一个 ModularRealmAuthorizer 实例,它支持协调任何授权操作过程中的一个或多个Realm 实例。
Step 4:每个配置好的 Realm 被检查是否实现了相同的 Authorizer 接口。如果是,Realm 各自的 hasRole*, checkRole*,isPermitted*,或 checkPermission* 方法将被调用。
在认证、授权内部实现机制中都有提到,最终处理都将交给Realm进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。
正如前文所提到的,Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:
1、检查提交的进行认证的令牌信息
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
3、对用户信息进行匹配验证。
4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException异常信息。
运行流程:
|---Shiro授权流程
|---构造SecurityManager环境
|---Subject.isPermited()授权
|---SecurityManager.isPermited()执行授权
|---Authorizer执行授权
|---Realm根据身份获取资源权限信息
5.三种授权方式
(一)编程式:通过写if/else授权代码快完成
5.1、通过使用 subject 的方法来实现角色的判断,常用的 API:
hasRole(String roleName)
hasRoles(List<String> roleNames)
hasAllRoles(Collection<String> roleNames)
5.2、断言支持
Shiro 还支持以断言的方式进行授权验证。断言成功,不返回任何值,程序继续执行;断言失败时,将抛出异常信息。常用方法:
checkRole(String roleName)
checkRoles(Collection<String>roleNames)
checkRoles(String… roleNames)
5.3、基于权限对象的实现
isPermitted(Permission p)
isPermitted(List<Permission> perms)
isPermittedAll(Collection<Permission> perms)
5.4、基于字符串的实现
if (currentUser.isPermitted("printer:print:laserjet4400n"))
isPermitted(String perm)
isPermitted(String... perms)
isPermittedAll(String... perms)
5.5、权限的实现也都可以采用断言的方式,相关方法
checkPermission(Permission p)
checkPermission(String perm)
checkPermissions(Collection<Permission> perms)
checkPermissions(String... perms)
示例:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")){
//有权限
} else{
//无权限
}
(二)注解式:通过在执行的JAVA方法上放置相应的注解完成
基于注解的授权,需要有AOP的支持,我们这里使用spring,你也可以使用AspectJ或者 Guice,创建一个Java工程(注意,不是web工程,两者的配置有一些差别)
自定义Realm(这一步和上面的MyRealm1一模一样,复制一份即可),在src下创建spring配置文件(applicationContext.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.shiro.anno"></context:component-scan>
<bean id="TestShrio" class="com.shiro.anno.TestShrio"></bean>
<!-- 配置Realm -->
<bean id="MyRealm1" class="com.shiro.anno.MyRealm1"></bean>
<!-- 配置securityManager -->
<bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
<property name="realm" ref="MyRealm1" />
</bean>
<!-- 生命周期 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!-- 启用shiro注解 -->
<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>
<!--
让securityManager这个bean成为静态单例的bean
注意:在web应用中,不要配置这个
-->
<bean
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod"
value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>
</beans>
写一个类用于注解授权
package com.shiro.anno;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;
@Service
public class TestShrio {
public void login(){
Subject currenUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
token.setRememberMe(true);
currenUser.login(token);
}
/**
* 有printer:print权限才能调用该方法
* 否则抛异常
*/
@RequiresPermissions({"printer:print"})
public void testAnnotation(){
System.out.println("使用注解方式。。。");
}
}
测试类:
package com.shiro.anno;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
TestShrio TestShrio = (TestShrio)ctx.getBean("TestShrio");
TestShrio.login();
//有权限时,该方法才正常调用,否则抛异常
TestShrio.testAnnotation();
}
}
当@RequiresPermissions({"printer:print"}) 是有权限,执行效果如下:
修改@RequiresPermissions({"printer:delete"})时,执行效果如下:
其他一些注解:
注解 | 说明 |
---|---|
@RequiresAuthentication | 要求当前Subject 已经在当前的session 中被验证通过才能被注解的类/实例/方法访问或调用 |
@RequiresGues | 要求当前的Subject 是一个“guest”,也就是他们必须是在之前的session中没有被验证或记住才能被注解的类/实例/方法访问或调用 |
@RequiresPermissions | 要求当前的Subject 被允许一个或多个权限,以便执行注解的方法,比如:@RequiresPermissions(“account:create”) |
@RequiresRoles | 要求当前的Subject 拥有所有指定的角色。如果他们没有,则该方法将不会被执行,而且AuthorizationException 异常将会被抛出。比如:@RequiresRoles(“administrator”) |
@RequiresUser | 需要当前的Subject 是一个应用程序用户才能被注解的类/实例/方法访问或调用。要么是通过验证被确认,或者在之前session 中的’RememberMe’服务被记住 |
(三)JSP/GSP标签:在JSP/GSP页面通过相应的标签完成
这里以SSM框架来演示,如何创建SSM框架可参考:http://blog.youkuaiyun.com/u011781521/article/details/53725788,在applicationContext.xml配置文件中加入shiro的一些配置:
<!-- 配置Realm: -->
<bean id="myRealm" class="com.fendo.shiro.anno.Realm.MyRealm1"></bean>
<!-- 启用shiro注解 -->
<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>
<!-- 配置securityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm" />
</bean>
<!-- 生命周期 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!-- shiro过滤器bean,id要和web.xml中filter-name一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="filterChainDefinitions">
<value>
#这里相当于ini配置文件中的[urls]
#url=拦截器[参数],拦截器
# some example chain definitions:
/admin/** = authc, roles[admin]
/docs/** = authc, perms[document:read]
# 当访问login时,不用进行认证(anon表示匿名)
/login = anon
/** = authc
# more URL-to-FilterChain definitions here
</value>
</property>
</bean>
在web.xml配置shiro过滤器起作用
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>Archetype Created Web Application</display-name>
<!-- name要和 applicationContext.xml中的对应的bean的id一致 -->
<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>
<!-- 加载SpringMVC -->
<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
<!-- 加载Spring -->
<!-- needed for ContextLoaderListener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- Bootstraps the root web application context before servlet initialization -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
在web目录下创建login.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>登陆页面</title>
</head>
<body>
<h1>login</h1>
<form action="login">
<label>username:</label>
<input type="text" name="username"/>
<label>password:</label>
<input type="text" name="password"/>
<input type="submit" value="submit"/>
</form>
</body>
</html>
对应的处理登录请求的controller
package com.fendo.shiro.anno.MySSM.controller;
import javax.security.auth.Subject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.fendo.shiro.anno.MySSM.entity.TestShrio;
@Controller
public class LoginController {
@Autowired
private TestShrio TestShrio;
@RequestMapping("/login")
public String test(String username,String password){
System.out.println("username:"+username);
System.out.println("password"+password);
//登录
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
org.apache.shiro.subject.Subject currentUser = SecurityUtils.getSubject();
//如果登录失败,会抛异常,应该要捕捉异常
currentUser.login(token);
if(currentUser.isAuthenticated()){
System.out.println("认证成功");
//有权限才能调用该方法,没权限将抛异常
TestShrio.testAnnotation();
}
return "success";
}
}
TestShrio
package com.fendo.shiro.anno.MySSM.entity;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;
@Service
public class TestShrio {
public void login(){
Subject currenUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
token.setRememberMe(true);
currenUser.login(token);
}
/**
* 有printer:print权限才能调用该方法
* 否则抛异常
*/
@RequiresPermissions({"printer:print"})
public void testAnnotation(){
System.out.println("使用注解方式。。。");
}
}
自定义Realm
package com.fendo.shiro.anno.Realm;
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.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyRealm1 extends AuthorizingRealm {
private static final transient Logger log = LoggerFactory.getLogger(MyRealm1.class);
/**
* 获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) getAvailablePrincipal(principals);
//通过用户名从数据库获取权限字符串
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//权限
Set<String> s = new HashSet<String>();
s.add("printer:print");
s.add("printer:query");
info.setStringPermissions(s);
//角色
Set<String> r = new HashSet<String>();
r.add("role1");
info.setRoles(r);
return info;
}
/**
* 在这个方法中,进行身份验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//用户名
String username = (String) token.getPrincipal();
//密码
String password = new String((char[])token.getCredentials());
//从数据库获取用户名密码进行匹配,这里为了方面,省略数据库操作
if(!"admin".equals(username)){
throw new UnknownAccountException();
}
if(!"123".equals(password)){
throw new IncorrectCredentialsException();
}
//身份验证通过,返回一个身份信息
AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName());
return aInfo;
}
}
在web目录下创建success.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!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>权限</title>
</head>
<body>
<h1>successful</h1>
<!-- 如果当前用户有printer:print权限,标签内的内容才显示 -->
<shiro:hasPermission name="printer:print">
我有打印机的打印权限
</shiro:hasPermission>
<shiro:hasPermission name="printer:query">
我有打印机的查询权限
</shiro:hasPermission>
<shiro:hasPermission name="printer:delete">
我有打印机的删除权限
</shiro:hasPermission>
</body>
</html>
在我们自定义的Realm中,我们给了该用户两个权限(printer:print和printer:query),因此success.jsp页面应该打印出有查询和打印两个权限
完整示例:http://download.youkuaiyun.com/detail/u011781521/9900377
shiro还提供了以下一些JSP/GSP Tag:
1.<shiro:guest/>:当前用户为游客身份,没有登录,或者没有与之关联的“Remember Me”身份标识时,并且系统没有这些限制时展现的内容,使用<shiro:guest/>标签。它与<shrio:user/>标签逻辑上是相反的。
验证当前用户是否为“访客”,即未认证(包含未记住)的用户
示例:
<shiro:guest>
你好!请你 <a href="login.jsp">登录</a> or <a href="signup.jsp">注册</a> !
</shiro:guest>
2.<shiro:user/>:它包围起来的内容只对系统可识别的Subject,之前登录过或者在“Remember Me”服务中有记录的用户可见。值得注意的是它与<shiro:authenticated/>标签不同,后者更严格。<shiro:user/>逻辑上与<shiro:gust/>标签相反。
认证通过或已记住的用户
示例:
<shiro:user>
欢迎回来约翰!不是约翰吗? <a href="login.jsp">点击这里<a> 登录.
</shiro:user>
3.<shiro:principal/>:显示用户规则或用户的主要规则。
输出当前用户信息,通常为登录帐号信息
示例:
你好, <shiro:principal/>, 今天怎么样?
4.<shiro:hasPermission/>:只有当前Subject(用户)拥有某特定权限时(也就是,用户拥有某项特定能力),才显示被它包围的内容。
验证当前用户是否拥有制定权限
示例:
<shiro:hasPermission name="user:create">
<a href="createUser.jsp">创建一个新的用户</a>
</shiro:hasPermission>
5.<shiro:lacksPermission/>:当前用户不具有某特定权限时,显示被它包围的内容。逻辑与<shiro:hasPermission/>相反。
当前用户没有制定权限时,验证通过
示例:
<shiro:hasPermission name="user:create">
<a href="createUser.jsp">创建一个新的用户</a>
</shiro:hasPermission>
6.<shiro:hasRole/>:当前用户拥有某特定角色时,显示被它包围的内容。
验证当前用户是否属于该角色
示例:
<shiro:hasRole name="administrator">
<a href="admin.jsp">管理系统</a>
</shiro:hasRole>
7.<shiro:lacksRole/>:当前用户不具有某特定角色时,显示被它包围的内容。逻辑与<shiro:hasRole/>相反。
当用户不属于该角色时验证通过
示例:
<shiro:lacksRole name="administrator">
对不起,您不能管理系统。
</shiro:lacksRole>
8.<shiro:hasAnyRoles/>:当前用户拥有特定“角色集合”中的任意一个角色时,显示被它包围的内容。这个特定的“角色集合”是由角色名称组成的,使用逗号分隔角色名称。
验证当前用户是否属于以下任意一个角色
示例:
<shiro:hasAnyRoles name="developer,project manager,administrator">
您要么是一个开发人员,要么是项目经理,要么是管理员。
</shiro:lacksRole>
9.<shiro:authenticated/>:用户在当前session中已得到认证时,显示被它包围的内容。它比<shiro:user>更严格,与
<shiro:notAuthenticated/>标签逻辑相反。
已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。
示例:
<shiro:authenticated>
<a href="updateAccount.jsp">更新你的联系信息</a>.
</shiro:authenticated>
10.<shiro:notAuthenticated/>:用户在当前session中认证失败时,显示被它包围的内容。
未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。
示例:
<shiro:notAuthenticated>
请 <a href="login.jsp">登录</a> 以更新您的信用卡信息。
</shiro:notAuthenticated>