1.什么是权限管理
- 基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且h只能访问自己被授权的资源。
- 权限管理包括用户身份证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经历过身份认证,认证通过后用户具有该资源的访问权限方可访问。
2.用户身份认证
- 身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
2.1关键对象
- Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
- Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
- credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
shiro介绍
什么是shiro
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架
shiro的作用
既然shiro将安全认证相关的功能抽取出来组成一个框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。
shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro。
java领域中spring security(原名Acegi)也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单、灵活,所以现在越来越多的用户选择shiro
shiro架构
Subject
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。
Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
SecurityManager
SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
Authenticator
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器
Authorizer
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
realm
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
sessionManager
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
SessionDao
SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
CacheManager
CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。
Cryptography
Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
5.入门
添加依赖
<!--pom.xml-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
在web.xml文件中添加spring提供的整合shiro的过滤器
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
配置后项目启动的时候,过滤器的初始化方法中会去spring的容器中去,加载一个id为shiroFilter的bean,如果容器中没有这个bean就会报错。
需要在spring中配置一个id为shiroFilter(和过滤器同名)的bean,class为ShiroFilterFactoryBean。
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" >
<!-- 注入安全管理器 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 注入系统的登录访问路径 -->
<!-- 跳转到登录页面 -->
<property name="loginUrl" value="/login.jsp"></property>
<!-- 成功页面 -->
<property name="successUrl" value="/index.jsp"></property>
<!-- 权限不足的错误提示页面 -->
<property name="unauthorizedUrl" value="/error.jsp"></property>
<!-- 基于URL拦截权限控制 -->
<property name="filters">
<map>
<entry key="authc">
<bean class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter"/>
</entry>
</map>
</property>
<!--
URL路径自上而下进行匹配
-->
<!--
是所有的请求都要经过登录过滤器,如果没有登录就会跳 转到登录界面,所以静态资源同样会被拦截
anon过滤器处理原则 :随便访问
authc需要进行权限认证
-->
<property name="filterChainDefinitions">
<value>
/css/** = anon
/images/** = anon
/js/** = anon
/lib/** = anon
/login.jsp* = anon
/other.jsp = anon
/user/login = anon
/** = authc
</value>
</property>
</bean>
配置安全过滤器
<bean name="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
</bean>
shiro自带过滤器
anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,没有参数
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:/srverName: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是授权过滤器
常用过滤器
anon:匿名过滤器,用户不用进行登录验证就能通过
authc:登录验证过滤器,用户检查当前用户是否已经登录
perms:授权过滤器,用于检测用户是否授权成功
注意:如果用户没有认证的话,授权是不会起作用的,所以在浏览器中输 入 /user/add 并不会跳转到权限不足的页面,只会跳转到 登录界面,因为先要认证成功才能授权
认证操作
- 在登录的action中创建subject对象 Subject subject = SecurityUtils.getSubject();
- 创建用户名密码的令牌 AuthenticationToken token = new UsernamePasswordToken(username,password);
- 使用 subject 对象携带令牌调用安全管理器进行认证subject.login(token);如果认证未能认证成功就会抛出异常,所以需要拿try-catch进行捕获这个异常信息
Subject subject = SecurityUtils.getSubject();
AuthenticationToken token = new UsernamePasswordToken(name, password);
try {
subject.login(token);
} catch (Exception e) {
// TODO: handle exception
//认证失败了
System.out.println("认证失败了");
return "redirect:/login.jsp";
}
- 其内部会调用realm的方法
- 在service层创建包realm创建CRMRealm继承AuthorizingRealm并实现认证和授权的接口
public class SchoolRealm extends AuthorizingRealm{
@Autowired
UserDao userDao;
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
// TODO Auto-generated method stub
return null;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
// TODO Auto-generated method stub
return null;
}
}
- 如果认证的方法返回值为null,那么上述的subject.login(token);就会抛出异常 将CRMRealm配置给安全管理器
<bean id="schoolRealm" class="com.zhiyou.realm.SchoolRealm"></bean>
<bean name="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="schoolRealm"></property>
</bean>
- shiro认证的机制是我们将该用户对应的密码查出来,单纯的去比对密码,如果密码正确就是认证成功,如果密码错误就是认证失败
- 将realm中的参数类型强转成UsernamePasswordToken,通过调用getUsername()和getPassword()方法获取安全管理器传递过来的用户和密码
- 创建一个SimpleAuthenticationInfo 对象,传递三个值
- 第一参数是Object类型用于向subject对象传递值
- 第二个参数是Object 类型用户传递从数据库中查询出来的密码,交给shiro进行比对
- 第三个参数是字符串,可以将当前realm的名称传递过去
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
// TODO Auto-generated method stub
UsernamePasswordToken token = (UsernamePasswordToken)arg0;
User user = userDao.getUserByName(token.getUsername());
if (user == null) {
return null;
}
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(),this.getName());
return info;
}
- 如果密码不匹配在controller中同样会抛出异常
- 如果认证成功,shiro就会将SimpleAuthenticationInfo的第一个参数 绑定一个 threadLocal传递给subject,通过subject对象的getPrincipal()进行获取用户
-
@RequestMapping("login") public String login(String name,String password,HttpSession session){ Subject subject = SecurityUtils.getSubject(); AuthenticationToken token = new UsernamePasswordToken(name, password); try { subject.login(token); } catch (Exception e) { // TODO: handle exception //认证失败了 System.out.println("认证失败了"); return "redirect:/login.jsp"; } User user = (User)subject.getPrincipal(); session.setAttribute("user", user); return "home"; }
授权操作
注解授权
在配置文件中配置一个bean开启自动代理模式,并配置一个切面类
<!-- 开启shiro自动代理 -->
<bean name="defaultAdvisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<!-- 指定强制使用cglib为action创建代理对象 -->
<property name="proxyTargetClass" value="true"></property>
</bean>
<!-- 配置切面类 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>
在 controller 的对应的action上方打注解
@RequiresPermissions("/user/add")
@RequestMapping("add")
public String add(){
return "success";
}
@RequiresPermissions("/user/update")
@RequestMapping("update")
public String update(){
return "success";
}
- 在授权方法中创建SimpleAuthorizationInfo对象 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
- 可以使用两种方法获取认证的对象,使用此对象进行数据库的查询操作 通过安全工具类
- User u = (User)SecurityUtils.getSubject().getPrincipal();
- 通过方法的参数 User u =(User) principal.getPrimaryPrincipal();
- 调用添加授权的方 法: info.addStringPermission("/user/listUser");
- 当然正确的 途径应该从数据库中将权限全部查出来进行添加
- 注意:授权方法是每次都需要调用的
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
// TODO Auto-generated method stub
System.out.println("授权。。。");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("/user/add");
return info;
}
- 注意shiro的注解配置,必须配置在springmvc的配置文件中
2.如果使用注解的形式进行配置,当未认证或者未授权的时候系统就会向 外抛出异常,我们可配置全局异常处理进行出现异常时的页面跳转,未认 证的时候抛出的异常为 UnauthenticatedException 未授权的时候抛 出的异常为 UnauthorizedException
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
System.out.println("异常解析。。。。。");
ModelAndView mav = new ModelAndView();
mav.addObject("obj", handler);
mav.addObject("error", ex);
if(ex instanceof UnauthenticatedException){
mav.setViewName("redirect:/login.jsp");
}else if(ex instanceof UnauthorizedException){
mav.setViewName("redirect:/error.jsp");
}else{
mav.setViewName("forward:/error.jsp");
}
// TODO Auto-generated method stub
return mav;
}
在spring配置文件中配置此异常解析器
<bean class="com.zhiyou.service.MyHandlerException"></bean>
jsp页面标签授权
可以使用shiro中内置的授权的标签对,控制jsp页面中的某些标签显示 和隐藏
- 引入标签库
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
- 在jsp页面中使用shiro标签进行标签内容的控制
<shiro:authenticated></shiro:authenticated>
- 认证通过的 标签,只有认证通过后,才会显示标签中的内容,我们一般的后台系统 只有先登录后页面才能显示,所以这个标签用的频率不高
<shiro:hasPermission name=""></shiro:hasPermission>
- 授 权通过的标签,只有授权通过后才能显示标签中的内容
<shiro:hasPermission name="/user/add">
<a href="user/add">添加学生</a>
<br/>
</shiro:hasPermission>
<shiro:hasPermission name="/user/update">
<a href="user/update">修改学生</a>
<br/>
</shiro:hasPermission>
<shiro:hasPermission name="/user/delete">
<a href="user/detele">删除学生</a>
<br/>
</shiro:hasPermission>