本文的架构图
本文重在将shiro整合redis,redis会在后期的文章中
shiro
一.shiro的概述
1.shiro是什么?
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的
API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
2.shiro和springSecurity的简单对比
Shiro:
Shiro较之 Spring Security,Shiro在保持强大功能的同时,还在简单性和灵活性方面拥有巨大优势。
- 易于理解的 Java Security API;
- 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);
- 对角色的简单的签权(访问控制),支持细粒度的签权;
- 支持一级缓存,以提升应用程序的性能;
- 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
- 异构客户端会话访问;
- 非常简单的加密 API;
- 不跟任何的框架或者容器捆绑,可以独立运行
Spring Security:
除了不能脱离Spring,shiro的功能它都有。而且Spring Security对Oauth、OpenID也有支持,Shiro则需要自己手
动实现。Spring Security的权限细粒度更高。
3.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”:一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。
4.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提高了一些常见的加密组件用于如密码加密/解密的。
5.shiro的代码流程
二.shiro的代码开发
1.导入依赖
<!--shiro和spring整合-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--shiro核心包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<!--shiro与redis整合-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.0.0</version>
</dependency>
2.用户登陆接口的代码编写
这里是将用户的登陆认证交给shiro去管理,代码步骤如下.
/**
*用户登陆
*/
@RequestMapping("doLogin")
public JsonResult doLogin(
String username,
String password,
boolean isRememberMe) {
//1.获取subject对象
Subject subject=SecurityUtils.getSubject();
//2.根据前端传入的参数构建UsernamePasswordToken
UsernamePasswordToken token= new UsernamePasswordToken(username, password);
//3.根据前端传入的记住密码选项
if(isRememberMe)token.setRememberMe(true);
//4.subject.login(token)subject将密码token交给securityManager对象.
subject.login(token);//提交给securityManager
return new JsonResult("login ok");
}
3.书写realm
realm就是构建shiro认证用户登陆和授权用户的权限数据.也就是所有的数据库交互的工作是有realm完成的.相当于subject将用户名交给securityManager,SecurityManager将认证和授权委托给realm.
代码:
@Service
public class ShiroUserRealm extends AuthorizingRealm{
@Autowired
private SysUserDao sysUserDao;
/**
* 构建CredentialsMatcher算法匹配器
* securityManager调用认证信息的时候会调用本加密匹配器对密码进行加密之后在匹配密码,这是设置加密算法,和我们存入用户密码时的加密算法一致
* @return
*/
@Override
public CredentialsMatcher getCredentialsMatcher() {
//构建凭证匹配对象
HashedCredentialsMatcher cMatcher=
new HashedCredentialsMatcher();
//设置加密算法
cMatcher.setHashAlgorithmName("MD5");
//设置加密次数
cMatcher.setHashIterations(1);
return cMatcher;
}
/**
* 通过此方法获取用户认证信息,并进行封装,然后返回给
*SecurityManager对象
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//1.获取用户登录时提交用户信息
UsernamePasswordToken uToken=
(UsernamePasswordToken)token;
String username=uToken.getUsername();
//2.基于用户名查找用户
SysUser user=
sysUserDao.findUserByUserName(username);
//3.判定用户是否存在
if(user==null)
throw new UnknownAccountException();
//4.判定用户是否被禁用
if(user.getValid()==0)
throw new LockedAccountException();
//5.封装认证信息
ByteSource credentialsSalt= ByteSource.Util.bytes(user.getSalt());//加密盐
SimpleAuthenticationInfo info=
new SimpleAuthenticationInfo(
user,//principal 用户身份
user.getPassword(),//hashedCredentials
credentialsSalt, //credentialsSalt
getName());//realmName
return info;//返回给SecurityManager
}
@Autowired
private SysUserRoleDao sysUserRoleDao;
@Autowired
private SysRoleMenuDao sysRoleMenuDao;
@Autowired
private SysMenuDao sysMenuDao;
/**
* 获取登录用户的权限信息并进行封装.这是获取用户的权限信息,其实也就是授权信息.
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
System.out.println("==doGetAuthorizationInfo==");
//1.获取登录用户信息
SysUser user=(SysUser)principals.getPrimaryPrincipal();
//2.基于登录用户id获取角色id并进行校验.
List<Integer> roleIds=
sysUserRoleDao.findRoleIdsByUserId(user.getId());
if(roleIds==null||roleIds.size()==0)
throw new AuthorizationException();
//3.基于角色id获取对应菜单id并进行校验
Integer[] array= {};
List<Integer> menuIds=
sysRoleMenuDao.findMenuIdsByRoleIds(
roleIds.toArray(array));
if(menuIds==null||menuIds.size()==0)
throw new AuthorizationException();
//4.基于菜单id获取对应的菜单权限标识(permission)
List<String> permissions=
sysMenuDao.findPermissions(menuIds.toArray(array));
if(permissions==null||permissions.size()==0)
throw new AuthorizationException();
//5.封装用户权限信息,并将此信息返回给SecurityManager
Set<String> stringPermissions=new HashSet<>();
for(String per:permissions) {
if(!StringUtils.isEmpty(per)) {
stringPermissions.add(per);
}
}
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
info.setStringPermissions(stringPermissions);
return info;//返回给securityManager
}
}
4.自定义SessionManager
如果是分布式项目,微服务内部调用,需要重新将请求的头部参数组装调用才能有权限访问其他服务.实现将sessionId存放在相应的头部中,因为我们微服务内部调用需要重新给请求头上设置,Token
public class CustomSessionManager extends DefaultWebSessionManager {
/**
* 头信息中具有sessionid
* 请求头:Authorization: sessionid
*
* 指定sessionId的获取方式
*/
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//获取请求头Authorization中的数据
String id = WebUtils.toHttp(request).getHeader("Authorization");
if(StringUtils.isEmpty(id)) {
//如果没有携带,生成新的sessionId
return super.getSessionId(request,response);
}else{
//请求头信息:bearer sessionid
id = id.replaceAll("Bearer ","");
//返回sessionId;
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
}
}
}
5.配置shiro的配置文件
本配置文件主要配置:
sessionManager:这就是shiro的安全数据存放的地方,如果我们要搭建分布式项目则必须重新配置sessionManager,因为默认shiro的存放数据的session是存放在单台服务器上的.
@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;
}
}
整体来说shiro还是非常简单的.
无外乎:
1.导入依赖
2.用户登的时候把用户名和密码交给shiro管理
3.给shiro配置认证和授权的realm
4.配置shiro的配置文件.
实现的逻辑是:
用户登陆和鉴权
以上就是shiro整合redis实现