网上有很多文章介绍如何使用shiro,如果你是第一次接触shiro,各种文章可能看了好多篇也还是一头雾水。笔者当初也是这样,想找一个简单的入门教程看看,了解一下shiro是什么,能做什么,以及怎么操作,结果看了很多也不太明白,走了很多弯路。因为网上的文章大多是以过来者的身份介绍,在学习一个新东西的时候,如果你折腾了好长时间,过来了,就能明白那些文章在讲什么,哪些是重点,哪些是坑;如果你折腾了好长时间,还是云里雾里,那么恭喜你,因为你看到了这篇文章,我会用初学者的眼光,带你在最短时间内抓住shiro的本质。
在学新东西的时候,要问三个问题:这个新东西是什么?能做什么?我怎么操作使用它?
如果有余力,最好再问一个为什么,看一看它的源码,理解它的原理,这样从原理到实现细节的方方面面就都掌握了。
Apache Shiro是一个强大且易用的Java安全框架,能做身份验证、授权、密码学和会话管理。
请参考shiro官网介绍
Shiro 最主要的两个部分就是认证和授权,Shiro通过过滤拦截请求实现认证或授权。
shiro 主要框架图
shiro.png
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提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
从应用程序的角度看,它的工作流程是这样的:
shiro_flow.jpeg简单的来说,它的工作流程是:应用代码被包装成 subject,然后 SecurityManager 通过Realm获取用户权限数据对其实现登录和授权的校验,经过授权就可以正常访问了。
Shiro认证与授权在Web中的实现
第一步:添加jar包
第二步:配置web.xml shiro过滤器
第三步:自定义Realm ,继承 AuthorizingRealm ,重写 AuthorizationInfo(授权) ,重写 AuthenticationInfo(认证)
第四步:配置spring-shiro.xml
第五步:在spring-mvc.xml中配置权限的控制 异常的跳转
第六步:在controller中使用
- 第一步:添加jar包,版本1.3.2,最近的稳定版本
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
- 第二步:配置web.xml shiro过滤器;targetFilterLifecycle 为 true 代表由 spring 控制该 filter 的生命周期。shiro 源码中的 demo 配的是/*,因为我只想控制用户访问后端接口的权限,静态资源可以随便访问,所以后端接口都用的api开头,让shiro过滤器只对api开头的请求生效。
<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>/api/*</url-pattern>
</filter-mapping>
- 第三步:自定义Realm ;shiro 中 realm 是进行认证和授权的组件,自带了几种实现,比如 jdbcRealm 和 iniRealm,实际项目中肯定都是自己实现 realm, 这里自定义 MyRealm 继承 AuthorizingRealm,分别实现认证和授权的方法。
public class MyRealm extends AuthorizingRealm {
//用来做测试的数据,实际开发中都是从数据库中取的
private static final String USERNAME = "admin";
private static final String PASSWORD = "123456";
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//principals.getPrimaryPrincipal()获得的就是当前用户名,权限取值在实际中是从数据库中取出的
Set<String> permissions = new HashSet<String>();
permissions.add("sys:page1");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permissions);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
//账号错误
if(!username.equals(USERNAME)) {
throw new AuthenticationException("账号不正确");
}
//密码错误
if(!password.equals(PASSWORD)) {
throw new IncorrectCredentialsException("密码不正确");
}
//getName()返回该realm的名字,代表该认证信息的来源是该realm
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
return info;
}
}
doGetAuthenticationInfo是认证的方法,当用户登陆的时候会调用
doGetAuthorizationInfo是授权的方法,在拦截器中进行权限校验的时候会调用
- 第四步:配置spring-shiro.xml
配置 ShiroFilterFactoryBean ,注意 id 一定要和 web.xml 中 的 filter-name 一致,否则 proxyFilter 找不到实际 filter。
shiro 的内置过滤器有anon、authc、logout、perms、roles等等,可以分成两类,一类会调用 realm 中的认证方法,一类会调用 realm中 的授权方法,更详细的请自行百度。
<!--配置全局权限过滤器-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--设置没有登录时的跳转地址-->
<property name="loginUrl" value="/static/html/login.html"/>
<!--设置权限不足时的跳转地址-->
<property name="unauthorizedUrl" value="/static/html/error.html"/>
<!--对哪个后端接口使用哪个过滤器进行配置,等号后边是shiro内置过滤器的名字-->
<property name="filterChainDefinitions">
<value>
<!--匿名访问,/api/login是登陆接口,当然可以随便访问-->
/api/login = anon
<!--用户退出登录的接口,后端不需要实现该接口,logout拦截到/api/logout的url后,就自动清除登录状态回到首页了-->
<!--因为在web.xml中设置的url-parttern是/api/*,随意只有api开头的url才会被拦截-->
/api/logout = logout
<!--其他所有接口都需要认证,也就是需要之前输入过账号密码登录过-->
/** = authc
</value>
</property>
</bean>
- 在 securityManager 中注入自定义的 realm
<!--非web环境使用DefaultSecurityManager-->
<bean id="myRealm" class="cn.shirodemo.shiro.MyRealm"/>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
- AOP式方法级权限检查
<!-- AOP式方法级权限检查 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
- 第五步:在 spring-mvc.xml 中配置异常的跳转
<!-- 未认证或未授权时跳转必须在springmvc里面配,spring-shiro里的shirofilter配不生效 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--没有授权的异常 -->
<prop key="org.apache.shiro.authz.UnauthorizedException">
<!--捕获该异常时跳转的路径 -->
/error
</prop>
<!--没有认证的异常 -->
<prop key="org.apache.shiro.authz.UnauthenticatedException">
<!--捕获该异常时跳转的路径 -->
/error
</prop>
</props>
</property>
</bean>
- 第六步:在controller中测试使用的验证登入
@RequestMapping(value="/login.do",method=RequestMethod.POST)
@ResponseBody
public String login(String username,String password) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", true);
Subject subjectecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
return JSONUtils.toJSONString(result);
}
现在只需要在相应方法前加上@RequiresPermissions就可以进行权限控制了
@RequiresPermissions("sys:page1")
@RequestMapping(value="/toPage1.do",method= RequestMethod.POST)
@ResponseBody
public String toPage1() {
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", true);
return JSONUtils.toJSONString(result);
}
-
最后补充一点,web 项目的前台页面如何用 shiro 进行配置呢。
-
如果是jsp页面,在 jsp 中引入 shiro 标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:authenticated> <a href="xxx.jsp">xxxxxx</a> </shiro:authenticated>
<shiro:hasRole name="admin"> <a href="xxx.jsp">xxxxxx</a> </shiro:hasRole>
<shiro:hasPermission name="user:create"> <a href="xxx.jsp">xxxxxx</a> </shiro:hasPermission>
- guest标签 :验证当前用户是否为“访客”,即未认证(包含未记住)的用户。
- user标签 :认证通过或已记住的用户。
- authenticated标签 :已认证通过的用户。不包含已记住的用户。
- notAuthenticated标签 :未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。
- principal 标签 :输出当前用户信息,通常为登录帐号信息。
- hasRole标签 :验证当前用户是否属于该角色。
- lacksRole标签 :与hasRole标签逻辑相反,当用户不属于该角色时验证通过。
- hasAnyRole标签 :验证当前用户是否属于以下任意一个角色。
- hasPermission标签 :验证当前用户是否拥有指定权限。
- lacksPermission标签 :与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。
- 如果前台页面是 html,且使用了 thymeleaf 的模板引擎
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
配置一下 thymeleaf
<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
<property name="additionalDialects">
<set>
<bean class="at.pollux.thymeleaf.shiro.dialect.ShiroDialect"/>
</set>
</property>
</bean>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
这样就可以在页面中使用了
<p shiro:hasRole="admin"></p>
<p shiro:hasPermission="userInfo:add"></p>
- 如果前台页面是 html,没有使用模板怎么办?
这样就无法使用shiro标签了,不过这里有个解决思路,前台页面通过ajax获取后台某个角色或是权限的信息,然后在前台用改变标签样式display:none
的方法让相应内容隐显。
网友评论