一、概述
Apache Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro 都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro 要简单的多。
什么是认证?
认证就是用户身份识别,常被称为用户“登录”,判断用户是否登陆,如果未登陆则拦截其请求。
什么是授权?
授权即对用户的操作进行控制。当用户访问某些资源时,系统先判断其身份是否有权限访问相应的资源,如果有则允许继续访问;如果没有则进行拦截,不让其访问该资源。
二、Shiro的工作方式
如果从用户角度来看,Shiro框架的工作方式如下图所示:
从上图可以看到,应用程序与Subject(主体)进行交互。Subject代表当前登录用户,它保存了当前登录用户的信息。应用程序通过Subject来进行认证和授权。而主体又委托给Security Manager(安全管理器)来完成相应工作。用户需要将Realm传给会话管理器,从而让会话管理器能够得到合法的用户,以及对用户的访问权限进行判断。
Shiro框架的内部结构如下图所示:
主体(Subject):主体可以是程序,也可以是一个对象。在Web应用中,一般使用当前登录用户作为主体。如果主体要访问系统,那么Shiro就会对主体进行认证、授权等工作;
安全管理器(SecurityManager):执行了主体的认证和授权工作;
认证器(Authenticator):负责执行认证操作;
授权器(Authorizer):负责执行授权操作;
会话管理器(SessionManager):Shiro接管了Web中的Session。Shiro对Session的管理工作就是通过该管理器进行;
领域(Realm):相当于数据源。通过该Realm存取认证、授权相关的数据;
三、SpringBoot整合Shiro的基本步骤
3.1 环境准备
在springboot项目中添加以下坐标:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
然后创建一个配置类,用于配置ShiroFilterFactoryBean对象。
@Configuration
public class ShiroConfiguration {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
return securityManager;
}
}
3.2 身份认证
身份认证即在应用程序证明是不是本人。一般提供一些代表用户身份的信息来表明他就是他本人,如用户名和密码。
在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份。任何东西,如用户名、邮箱等都可以作为principals使用,只需要保证它们能够唯一标识该用户即可。一个主体可以有多个 principals,但只有一个 Primary principals。
3.2.1 实现认证的基本流程
第一步:搜集用户身份信息,如用户名和密码;
第二步:调用Subject对象的login方法进行登录。如果登录失败,该方法会抛出AuthenticationException异常;
下面以用户登录为例,演示如何实现用户身份认证。
@RequestMapping(path="/login.do", produces="application/json;charset=utf-8")
@ResponseBody
public Map login(HttpServletRequest request, Emp emp) {
//第一步:创建令牌
UsernamePasswordToken token = new UsernamePasswordToken(
emp.getUsername(), emp.getPwd());
//第二步:获取Subject对象,该对象封装了一系列的操作
Subject subject = SecurityUtils.getSubject();
//第三步:执行认证
try {
subject.login(token);
return ajaxReturn(true, "登录成功!");
} catch (AuthenticationException e) {
e.printStackTrace();
}
return ajaxReturn(false, "登录失败!");
}
3.2.2 自定义Realm
从上面登录方法看到,登陆时并不直接调用业务逻辑判断用户名密码是否正确,而是将这项工作交给了shiro去完成。那shiro是怎么知道你的用户名和密码是否正确呢? 当然shiro自己肯定不知道,它需要“打听一个人”,那就是Realm。其实真正实现登陆校验的是Realm ,而Shiro只是去调用Realm。
自定义Realm的基本步骤:
第一步:定义一个类,继承AuthorizingRealm,并重写doGetAuthenticationInfo方法;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken arg0) throws AuthenticationException {
//1.得到令牌
UsernamePasswordToken token = (UsernamePasswordToken) arg0;
//2.调用业务组件进行登录判断
String password = new String(token.getPassword());
User user = userService.findUsers(token.getUsername(), password);
//3.判断用户是否为空,如果不为空代表登录成功
if (user != null) {
return new SimpleAuthenticationInfo(
user, //priciple,使用当前登录用户对象
password, //credentials
user.getName()); //realmName
}
//方法返回Null代表验证失败
return null;
}
Shiro执行认证时会自动调用doGetAuthenticationInfo方法。
第二步:在配置类中配置该Realm。
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
@Bean
public SecurityManager securityManager() {
DefaultSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
3.2.3 获取登录用户信息
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
3.2.4 注销
SecurityUtils.getSubject().logout();
3.2.5 认证规则
Shiro过滤器与URL匹配规则都是定义在ShiroFilterFactoryBean对象中。Shiro是以过滤器的方式对访问规则进行控制,并内置了一组过滤器,例如:
anon:不认证也可以访问;
authc:必须认证后才可以访问;
perms:指定资源需要哪些权限才可以访问;
例如:
# 可以匿名使用
/adminjs/*=anon
# 需要认证才能使用
/adminjs/user/*=authc
# 指定资源需要哪些权限才可以访问
/adminjs/user/*=perms[“权限名”]
URL匹配规则:
“?”
:匹配一个字符,如”/admin?”,将匹配“ /admin1”、“/admin2”,但不匹配“/admin”;
“*”
:匹配零个或多个字符串,如“/admin*”,将匹配“ /admin”、“/admin123”,但不匹配“/admin/1”
“**”
:匹配路径中的零个或多个路径,如“/admin/**”,将匹配“/admin/a”、“/admin/a/b”
3.2.6 配置资源访问规则
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//配置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//配置资源的访问规则
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
filterChainDefinitionMap.put("/login.html", "anon");
filterChainDefinitionMap.put("/adminjs/**", "anon");
filterChainDefinitionMap.put("/easyui/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/sys/login.do", "anon");
filterChainDefinitionMap.put("/sys/getCookie.do", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//指定认证失败后跳转的页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
return shiroFilterFactoryBean;
}
3.3 授权
3.3.1 授权流程
授权流程:
1)当调用 Subject.isPermitted方法时,shiro会委托给 SecurityManager,而 SecurityManager 接着会委托给 Authorizer进行授权相关工作。Authorizer 是真正的授权者,如果我们调用如 isPermitted方法,Authorizer 会通过 PermissionResolver 把字符串转换成相应的 Permission 实例。
2)在授权前,Authorizer会调用相应的 Realm 对象,并获取 Subject 相应的角色/权限,用于匹配传入的角色/权限。
3)Authorizer会判断Realm的角色/权限是否和传入的角色/权限相匹配。如果匹配则返回true,代表授权成功;否则返回false,代表授权失败。
3.3.2 授权实现
第一步:重写AuthorizingRealm对象的doGetAuthorizationInfo方法。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
System.out.println("执行授权的方法...");
//获取当前登录用户
Emp emp = (Emp) arg0.getPrimaryPrincipal();
//获取用户所拥有的菜单权限
List<Menu> menus = menuBus.findMenus(emp.getUuid());
//该对象保存了当前登录用户的权限信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for (Menu menu : menus) {
//设置用户所具有的权限信息
info.addStringPermission(menu.getMenuname());
}
return info;
}
第二步:定义规则;
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//配置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//配置资源的访问规则
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
filterChainDefinitionMap.put("/login.html", "anon");
filterChainDefinitionMap.put("/adminjs/**", "anon");
filterChainDefinitionMap.put("/easyui/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/sys/login.do", "anon");
// 权限规则
List<Menu> menus = menuService.findAll();
for (Menu menu : menus) {
filterChainDefinitionMap.put(menu.getUrl(), "perms['" + menu.getName() + "']");
}
filterChainDefinitionMap.put("/**", "authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//指定认证失败后跳转的页面
shiroFilterFactoryBean.setLoginUrl("/login.html");
return shiroFilterFactoryBean;
}
上面代码第15~20行定义权限规则,只有用户具有相对应的权限后,才允许访问指定url的资源。