简介:
什么是shiro?
官方文档上介绍到:
Apache Shiro™是功能强大且易于使用的Java安全框架,它执行身份验证,授权,加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。
Shiro是一个强大且易懂的一个安全框架,
强大的Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。
着重说明一下:Shiro是支持多策略的,例如利用原生有的doGetAuthenticationInfo、doGetAuthorizationInfo方法去编写代码完成功能;也可以结合专门做令牌校验JWT来形成一个JwtRealm来完成。
Shiro三个核心组件:
Shiro三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
使用Shiro的方法讲解:
引入相关依赖
首先引入相关依赖,加载jar包:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
AuthorizingRealm类
一开始我们定义一个用户类,去承载用户的信息:
@Data
public class LoginUser {
/**
* 登录人id
*/
private String id;
/**
* 登录人账号
*/
private String username;
/**
* 登录人名字
*/
private String realname;
/**
* 当前登录部门code
*/
private String orgCode;
/**
* 头像
*/
private String avatar;
/**
* 生日
*/
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
/**
* 性别(1:男 2:女)
*/
private Integer sex;
/**
* 电子邮件
*/
private String email;
/**
* 电话
*/
private String phone;
/**
* 状态(1:正常 2:冻结 )
*/
private Integer status;
private String delFlag;
/**
* 同步工作流引擎1同步0不同步
*/
private String activitiSync;
}
为了能够让Shiro能够正确的完成认证和赋权的操作,我们需要实现自定义的Realm。简单来讲也就是实现Shiro相当关键的2个方法:doGetAuthenticationInfo和doGetAuthorizationInfo。
doGetAuthenticationInfo:用来进行身份认证,也就是说验证用户输入的账号和密码是否正确,获取身份验证信息。
doGetAuthorizationInfo:获取用户权限信息,包括角色以及权限。只有当触发检测用户权限时才会调用此方法,例如checkRole,checkPermission。
接下来自定义一个类,去继承AuthorizingRealm类,重写以上方法,从而完成目的:
public class ShiroRealm extends AuthorizingRealm {
/**
* 必须重写此方法,不然Shiro会报错
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
/**
* 功能: 获取用户权限信息,包括角色以及权限。只有当触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
*
* @return AuthorizationInfo 权限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
LoginUser sysUser = null;
String username = null;
if (principals != null) {
sysUser = (LoginUser) principals.getPrimaryPrincipal();
username = sysUser.getUsername();
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 设置用户拥有的角色集合,比如“admin,test”
//根据实际情况自定义修改以下获取方法
Set<String> roleSet = sysUserService.getUserRolesSet(username);
info.setRoles(roleSet);
// 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
//根据实际情况自定义修改以下获取方法
Set<String> permissionSet = sysUserService.getUserPermissionsSet(username);
info.addStringPermissions(permissionSet);
return info;
}
/**
* 功能: 用来进行身份认证,也就是说验证用户输入的账号和密码是否正确,获取身份验证信息,错误抛出异常
*
* @return 返回封装了用户信息的 AuthenticationInfo 实例
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
log.debug("————身份认证————");
String token = (String) auth.getCredentials();
if (token == null) {
throw new AuthenticationException("token为空!");
}
// 校验token有效性
//根据实际情况自定义修改以下获取方法
LoginUser loginUser = this.checkUserTokenIsEffect(token);
return new SimpleAuthenticationInfo(loginUser, token, getName());
}
/**
* 校验token的有效性
*
* @param token
*/
public LoginUser checkUserTokenIsEffect(String token) throws AuthenticationException {
// 解密获得username,用于和数据库进行对比
String username = JwtUtil.getUsername(token);
// 查询用户信息
//根据实际情况自定义修改以下获取方法
LoginUser loginUser = new LoginUser();
SysUser sysUser = sysUserService.getUserByName(username);
if (sysUser == null) {
throw new AuthenticationException("用户不存在!");
}
BeanUtils.copyProperties(sysUser, loginUser);
return loginUser;
}
}
注意:以上代码部分内容需要根据实际情况修改成您所需要的获取方法。
核心的方法写完之后我们吧目光聚焦在ShiroConfig的配置文件上,为Shiro完善规则,使得框架能够尽然有序的进行下去:
@Configuration
public class ShiroConfig {
/**
* Filter Chain定义说明
*
* 1、一个URL可以配置多个Filter,使用逗号分隔
* 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/doc.html", "anon");
filterChainDefinitionMap.put("/**/*.js", "anon");
filterChainDefinitionMap.put("/**/*.css", "anon");
filterChainDefinitionMap.put("/**/*.html", "anon");
filterChainDefinitionMap.put("/**/*.svg", "anon");
filterChainDefinitionMap.put("/**/*.jpg", "anon");
filterChainDefinitionMap.put("/**/*.png", "anon");
filterChainDefinitionMap.put("/**/*.ico", "anon");
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger**/**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/demoTest/**", "anon");
// 添加自己的过滤器并且取名为jwt
Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
filterMap.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
// <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
filterChainDefinitionMap.put("/**", "jwt");
// 未授权界面返回JSON
shiroFilterFactoryBean.setUnauthorizedUrl("/sys/common/403");
shiroFilterFactoryBean.setLoginUrl("/sys/common/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean("securityManager")
public DefaultWebSecurityManager securityManager(ShiroRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
/*
* 关闭shiro自带的session,详情见文档
* http://shiro.apache.org/session-management.html#SessionManagement-
* StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* 配置生命周期
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启aop注解支持
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
说明:上面ShiroConfig 中核心配置了有忽视过滤(请根据自己需要添加修改)、关闭shiro自带的session的方法、开启Shiro的注解的方法、配置生命周期、开启aop注解支持
然后,请关注一下shirFilter方法,Shiro的控制权限是通过filter来完成的,内部预先定义了一部分多个过滤器,并且根据命名可以观察出该过滤器的作用,以下是常用的一些过滤器:
authc:所有已登陆用户可访问
roles:有指定角色的用户可访问,通过[ ]指定具体角色,这里的角色名称与数据库中配置一致
perms:有指定权限的用户可访问,通过[ ]指定具体权限,这里的权限名称与数据库中配置一致
anon:所有用户可访问,通常作为指定页面的静态资源时使用
结语:首先请大家要留意代码中的注释,核心的作用与方法我都有清晰的解释清楚。当你能够理解清晰Shiro中集成AuthorizingRealm的类和ShiroConfig,再结合Shiro流动的过程,就能轻松的使用该框架,嵌合进自己的项目中了。