一、什么是Shiro
Apache Shiro
是一个强大且易用的
Java
安全框架
,
执行
身份验证、授权、密码和会话管理
。使用
Shiro
的易于理解的
API,
您可以快速、轻松地获得任何应用程序
,
从最小的移动应用程序到最大的网络和企业应用程序。
Apache Shiro
的首要目标是易于使用和理解。安全有时候是很复杂的,甚至是痛苦的,但它没有必要这样。框架
应该尽可能掩盖复杂的地方,露出一个干净而直观的
API
,来简化开发人员在使他们的应用程序安全上的努力。以
下是你可以用
Apache Shiro
所做的事情:
- 验证用户来核实他们的身份
- 对用户执行访问控制,如:①判断用户是否被分配了一个确定的安全角色 ② 判断用户是否被允许做某事
- 在任何环境下使用 Session API,即使没有 Web 或 EJB 容器。
- 在身份验证,访问控制期间或在会话的生命周期,对事件作出反应。
- 聚集一个或多个用户安全数据的数据源,并作为一个单一的复合用户“视图”。
- 启用单点登录(SSO)功能。
- 为没有关联到登录的用户启用"Remember Me"服务
二、Shiro的功能模块
Shiro
可以非常容易的开发出足够好的应用,其不仅可以用在
JavaSE
环境,也可以用在
JavaEE
环境。
Shiro
可以帮助
我们完成:认证、授权、加密、会话管理、与
Web
集成、缓存等。这不就是我们想要的嘛,而且
Shiro
的
API
也是非
常简单;其基本功能点如下图所示:
- Authentication:身份认证/登录,验证用户是不是拥有相应的身份。
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情。
- Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话 中;会话可以是普通JavaSE环境的,也可以是如Web环境的。
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。
- Web Support:Shiro 的 web 支持的 API 能够轻松地帮助保护 Web 应用程序。
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。
- Concurrency:Apache Shiro 利用它的并发特性来支持多线程应用程序。
- Testing:测试支持的存在来帮助你编写单元测试和集成测试,并确保你的能够如预期的一样安全。
- "Run As":一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。
- "Remember Me":记住我。
三、Shiro的内部结构
Subject
:主体,可以看到主体可以是任何可以与应用交互的
“
用户
”
;
SecurityManager
:相当于
SpringMVC
中的
DispatcherServlet
或者
Struts2
中的
FilterDispatcher
;是
Shiro
的心
脏;所有具体的交互都通过
SecurityManager
进行控制;它管理着所有
Subject
、且负责进行认证和授权、及会
话、缓存的管理。
Authenticator
:认证器,负责主体认证的,这是一个扩展点,如果用户觉得
Shiro
默认的不好,可以自定义实
现;其需要认证策略(
Authentication Strategy
),即什么情况下算用户认证通过了;
Authrizer
:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的
哪些功能;
Realm
:可以有
1
个或多个
Realm
,可以认为是安全实体数据源,即用于获取安全实体的;可以是
JDBC
实现,也可
以是
LDAP
实现,或者内存实现等等;由用户提供;注意:
Shiro
不知道你的用户
/
权限存储在哪及以何种格式存储;
所以我们一般在应用中都需要实现自己的
Realm
;
SessionManager
:如果写过
Servlet
就应该知道
Session
的概念,
Session
呢需要有人去管理它的生命周期,这个
组件就是
SessionManager
;而
Shiro
并不仅仅可以用在
Web
环境,也可以用在如普通的
JavaSE
环境、
EJB
等环境;
所有呢,
Shiro
就抽象了一个自己的
Session
来管理主体与应用之间交互的数据;
SessionDAO
:
DAO
大家都用过,数据访问对象,用于会话的
CRUD
,比如我们想把
Session
保存到数据库,那么可
以实现自己的
SessionDAO
,通过如
JDBC
写到数据库;比如想把
Session
放到
Memcached
中,可以实现自己的
Memcached SessionDAO
;另外
SessionDAO
中可以使用
Cache
进行缓存,以提高性能;
CacheManager
:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到
缓存中后可以提高访问的性能
Cryptography
:密码模块,
Shiro
提高了一些常见的加密组件用于如密码加密
/
解密的。
四、应用程序使用Shiro
也就是说对于我们而言,最简单的一个
Shiro
应用:
1
、应用代码通过
Subject
来进行认证和授权,而
Subject
又委托给
SecurityManager
;
2
、我们需要给
Shiro
的
SecurityManager
注入
Realm
,从而让
SecurityManager
能得到合法的用户及其权限进行判
断。
从以上也可以看出,
Shiro
不提供维护用户
/
权限,而是通过
Realm
让开发人员自己注入。
五、应用实战
1、引入依赖 父工程pom.xml
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency
2、ihrm_system模块登录方法
@RequestMapping(value="/login",method = RequestMethod.POST)
public Result login(@RequestBody Map<String,String> loginMap) {
String mobile = loginMap.get("mobile");
String password = loginMap.get("password");
try {
//1.构造登录令牌 UsernamePasswordToken
//加密密码
password = new Md5Hash(password,mobile,3).toString(); //1.密码,盐,加密次数
UsernamePasswordToken upToken = new UsernamePasswordToken(mobile,password);
//2.获取subject
Subject subject = SecurityUtils.getSubject();
//3.调用login方法,进入realm完成认证
subject.login(upToken);
//4.获取sessionId
String sessionId = (String)subject.getSession().getId();
//5.构造返回结果
return new Result(ResultCode.SUCCESS,sessionId);
}catch (Exception e) {
return new Result(ResultCode.MOBILEORPASSWORDERROR);
}
3、自定义realm域
Realm
域:
Shiro
从
Realm
获取安全数据(如用户、角色、权限),就是说
SecurityManager
要验证用户身份,那么
它需要从
Realm
获取相应的用户进行比较以确定用户身份是否合法;也需要从
Realm
得到用户相应的角色
/
权限进行
验证用户是否能进行操作;可以把
Realm
看成
DataSource
,即安全数据源
注:主要考虑到授权、认证的方法
public class CustomRealm extends AuthorizingRealm {
@Override
public void setName(String name) {
super.setName("customRealm");
}
@Autowired
private UserService userService;
/**
* 构造授权方法
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection
principalCollection) {//1.获取认证的用户数据
User user = (User)principalCollection.getPrimaryPrincipal();
//2.构造认证数据
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<Role> roles = user.getRoles();
for (Role role : roles) {
//添加角色信息
info.addRole(role.getName());
for (Permission permission:role.getPermissions()) {
//添加权限信息
info.addStringPermission(permission.getCode());
}
}
return info;
}
/**
* 认证方法
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken
authenticationToken) throws AuthenticationException {
//1.获取登录的upToken
UsernamePasswordToken upToken = (UsernamePasswordToken)authenticationToken;
//2.获取输入的用户名密码
String username = upToken.getUsername();
String password = new String(upToken.getPassword());
//3.数据库查询用户
User user = userService.findByName(username);
//4.用户存在并且密码匹配存储用户数据
if(user != null && user.getPassword().equals(password)) {
return new
SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
}else {
//返回null会抛出异常,表明用户不存在或密码不匹配
return null;
}
}
}
@Configuration
public class ShiroConfiguration {
//配置自定义的Realm
@Bean
public CustomRealm getRealm() {
return new CustomRealm();
}
//配置安全管理器
@Bean
public SecurityManager securityManager(CustomRealm realm) {
//使用默认的安全管理器
DefaultWebSecurityManager securityManager = new
DefaultWebSecurityManager(realm);
//将自定义的realm交给安全管理器统一调度管理
securityManager.setRealm(realm);
return securityManager;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
//1.创建shiro过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
//2.设置安全管理器
filterFactory.setSecurityManager(securityManager);
//3.通用配置(配置登录页面,登录成功页面,验证未成功页面)
filterFactory.setLoginUrl("/autherror?code=1"); //设置登录页面
filterFactory.setUnauthorizedUrl("/autherror?code=2"); //授权失败跳转页面
//4.配置过滤器集合
/**
* key :访问连接
* 支持通配符的形式
* value:过滤器类型
* shiro常用过滤器
* anno :匿名访问(表明此链接所有人可以访问)
* authc :认证后访问(表明此链接需登录认证成功之后可以访问)
*/
Map<String,String> filterMap = new LinkedHashMap<String,String>();
// 配置不会被拦截的链接 顺序判断
filterMap.put("/user/home", "anon");
filterMap.put("/user/**", "authc");
//5.设置过滤器
filterFactory.setFilterChainDefinitionMap(filterMap);
return filterFactory;
}
//配置shiro注解支持
@Bean
public AuthorizationAttributeSourceAdvisor
authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new
AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
四、Shiro的配置
SecurityManager
是
Shiro
架构的心脏,用于协调内部的多个组件完成全部认证授权的过程。例如通过调用
realm
完成认证与登录。使用基于
springboot
的配置方式完成
SecurityManager
,
Realm
的装配
package com.ihrm.system;
import com.ihrm.common.shiro.realm.IhrmRealm;
import com.ihrm.common.shiro.session.CustomSessionManager;
import com.ihrm.system.shiro.realm.UserRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfiguration {
//1.创建realm
@Bean
public IhrmRealm getRealm() {
return new UserRealm();
}
//2.创建安全管理器
@Bean
public SecurityManager getSecurityManager(IhrmRealm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
//将自定义的会话管理器注册到安全管理器中
securityManager.setSessionManager(sessionManager());
//将自定义的redis缓存管理器注册到安全管理器中
securityManager.setCacheManager(cacheManager());
return securityManager;
}
//3.配置shiro的过滤器工厂
/**
* 再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
*
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
//1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
//2.设置安全管理器
filterFactory.setSecurityManager(securityManager);
//3.通用配置(跳转登录页面,未授权跳转的页面)
filterFactory.setLoginUrl("/autherror?code=1");//跳转url地址
filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url
//4.设置过滤器集合
Map<String,String> filterMap = new LinkedHashMap<>();
//anon -- 匿名访问
filterMap.put("/sys/login","anon");
filterMap.put("/sys/faceLogin/**","anon");
filterMap.put("/autherror","anon");
//注册
//authc -- 认证之后访问(登录)
filterMap.put("/**","authc");
//perms -- 具有某中权限 (使用注解配置授权)
filterFactory.setFilterChainDefinitionMap(filterMap);
return filterFactory;
}
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
/**
* 1.redis的控制器,操作redis
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
return redisManager;
}
/**
* 2.sessionDao
*/
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO sessionDAO = new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
return sessionDAO;
}
/**
* 3.会话管理器
*/
public DefaultWebSessionManager sessionManager() {
CustomSessionManager sessionManager = new CustomSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
//禁用cookie
//sessionManager.setSessionIdCookieEnabled(false);
//禁用url重写 url;jsessionid=id
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}
/**
* 4.缓存管理器
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
//开启对shior注解的支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
5、shiro中的过滤器
注意:
anon, authc, authcBasic, user
是第一组认证过滤器,
perms, port, rest, roles, ssl
是第二组授权过滤
器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权
)
才能走第二组授权器
(例如访问需要
roles
权限的
url
,如果还没有登陆的话,会直接跳转到
shiroFilterFactoryBean.setLoginUrl();
设置的
url
)
6、授权
授权:即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情
shiro
支持基于过滤器的授权方式也支持注解的授权方式
6.1、基于配置的授权
在shiro中可以使用过滤器的方式配置目标地址的请求权限
//配置请求连接过滤器配置
//匿名访问(所有人员可以使用)
filterMap.put("/user/home", "anon");
//具有指定权限访问
filterMap.put("/user/find", "perms[user-find]");
//认证之后访问(登录之后可以访问)
filterMap.put("/user/**", "authc");
//具有指定角色可以访问
filterMap.put("/user/**", "roles[系统管理员]");
基于配置的方式进行授权,一旦操作用户不具备操作权限,目标地址不会被执行。会跳转到指定的
url
连接地
址。所以需要在连接地址中更加友好的处理未授权的信息提示
6.2、基于注解的授权
(
1
)
RequiresPermissions
配置到方法上,表明执行此方法必须具有指定的权限
//查询
@RequiresPermissions(value = "user-find")
public String find() {
return "查询用户成功";
}
(
2
)
RequiresRoles
配置到方法上,表明执行此方法必须具有指定的角色
//查询
@RequiresRoles(value = "系统管理员")
public String find() {
return "查询用户成功";
}
基于注解的配置方式进行授权,一旦操作用户不具备操作权限,目标方法不会被执行,而且会抛出
AuthorizationException
异常。所以需要做好统一异常处理完成未授权处理