因为平时工作的关系,本篇文章主要按照登录认证和权限管理两部分来介绍shiro。
0.基础介绍
0.0 做什么
我们先看看百度百科怎么介绍“Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序”。由此可以看出来,Apache Shiro主要做三件事,登录认证、权限管理和会话管理。
0.1 组件介绍
Apache Shiro的架构图如下:
Subject:Subject 不仅仅代表某个用户,与当前应用交互的任何东西都是Subject,如网络爬虫等。所有的Subject都要绑定到SecurityManager上,与Subject的交互实际上是被转换为与SecurityManager的交互。
SecurityManager:即所有Subject的管理者,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。作用类似于SpringMVC中的DispatcherServlet,用于拦截所有请求并进行处理。
Realm:Realm是用户的信息认证器和用户的权限人证器,我们需要自己来实现Realm来自定义的管理我们自己系统内部的权限规则。SecurityManager要验证用户,需要从Realm中获取用户。可以把Realm看做是数据源。
Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
SessionManager:管理主体与应用之间交互的数据;如果我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,就可以实现自己的分布式会话。
SessionDAO:DAO大家都用过,数据访问对象,用户会话数据的CURD。
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的。
Cryptography:密码模块。
1 登录认证
1.1介绍
实际上,按照我们平时的系统的系统,我们在登录认证的时候,只需要关注三个点。第一,在哪里输入(传入)账密?第二,在哪里认证这些传入的账密?第三,如果告诉系统或者容器,认证的结果?
1.2,认证流程
如上图所示,身份认证流程如下:
(1) 首先调用 subject.login(token) 进行登录认证,自动委托给 SecurityManager,调用前必须获取 SecurityManager 实例且要将此实例绑定给 SecurityUtils;
(2) SecurityManager 负责身份认证逻辑,会委托给 Authenticator 进行身份认证;
(3) Authenticator 才是真正的身份认证负责人,是 Shiro API 中核心的身份认证入口点,可以自定义插入自己的身份认证实现逻辑;
(4) Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份认证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份认证;
(5) Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份认证信息,如果抛出异常表示身份认证失败。可以配置多个 Realm,将按照对应的顺序及策略进行访问。
1.3,在哪里传入账密?
代码实现如下
// 1.获取SecurityManager工厂,使用ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
// 2.获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
// 3.将SecurityManager实例绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 4.通过SecurityUtils获取Subject
Subject subject = SecurityUtils.getSubject();
// 5.创建用户名、密码身份验证token
UsernamePasswordToken token = new UsernamePasswordToken("zdtest", "aaaaa888");
try {
// 6.使用用户名、密码身份验证token进行身份验证,即登录
subject.login(token);
} catch (AuthenticationException e) {
1.4,realm实现
realm是实现校验账密的地方,主要和数据库打交道。
如下配置realm
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm1" />
</bean>
realm的实现如下
public class CustomRealm1 implements Realm {
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
// 获取用户名
String username = (String) token.getPrincipal();
// 获取密码
String password = new String((char[]) token.getCredentials());
// 如果用户名错误
if (!"zdtest".equals(username)) {
throw new UnknownAccountException();
}
// 如果密码错误
if (!"aaaaa888".equals(password)) {
throw new IncorrectCredentialsException();
}
// 如果身份认证验证成功,返回一个AuthenticationInfo接口实现
return new SimpleAuthenticationInfo(username, password, getName());
}
@Override
public String getName() {
return "CustomRealm1";
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
}
2,授权
在大家访问系统的时候,一般情况下会遇见如下三种情况:
1)访问的url资源不需要任何角色,既可以访问。
2)没有访问的资源需要角色,被拦截到登录页面。
3)有访问资源需要的角色,正常访问。
2.1,shiro角色控制的颗粒度
anon 未认证可以访问
authc 认证后可以访问
perms 需要特定权限才能访问
roles 需要特定角色才能访问
user 需要特定用户才能访问
port 需要特定端口才能访问
reset 根据指定 HTTP 请求访问才能访问
perms[courier:list]:用于用户权限控制
2.2,shiro的filter核心配置
<!-- 配置Shiro核心Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 安全管理器 -->
<property name="securityManager" ref="securityManager" />
<!-- 未认证,跳转到哪个页面 -->
<property name="loginUrl" value="/login.html" />
<!-- 登录页面页面 -->
<property name="successUrl" value="/index.html" />
<!-- 认证后,没有权限跳转页面 -->
<property name="unauthorizedUrl" value="/unauthorized.html" />
<!-- shiro URL控制过滤器规则 -->
<property name="filterChainDefinitions">
<value>
/login.html* = anon
/user_login.action* = anon
/validatecode.jsp* = anon
/css/** = anon
/js/** = anon
/images/** =anon
/pages/base/courier.html* = perms[courier:list]
/pages/base/area.html* = roles[base]
/** =authc
</value>
</property>
</bean>
2.3,shiro授权的实现。
首先自定义realm需要实现AuthorizingRealm 。实现其中的doGetAuthorizationInfo接口。
@Override
// 授权...
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
System.out.println("shiro 授权管理...");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 根据当前登录用户 查询对应角色和权限
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
// 调用业务层,查询角色
List<Role> roles = roleService.findByUser(user);
for (Role role : roles) {
authorizationInfo.addRole(role.getKeyword());
}
// 调用业务层,查询权限
List<Permission> permissions = permissionService.findByUser(user);
for (Permission permission : permissions) {
authorizationInfo.addStringPermission(permission.getKeyword());
}
return authorizationInfo;
}
终上所述,shiro能够很好很方便的实现用户的登录与授权工作,用户只需要实现其中的关键接口就行了,使用起来还是非常方便,配置也是相当的灵活。