概 述
shiro框架是apache旗下的一个开源安全框架,他将软件系统的安全认证相关功能抽取出来,实现用户身份认证,权限授权,加密,会话管理等功能,组成了一个通用的安全认证框架。
Shiro架构
shiro3个核心组件
- subject:主体对象,负责提交用户认证和授权信息,与软件交互的一个特定的实体。
- SecurityManager:安全管理器,负责认证,授权等业务的实现。
- Realm:领域对象,是shiro和应用程序数据交互的桥梁。
shiro详细架构
- 1)subject(主体):当前与软件进行交互的实体(用户,第三方服务,计划任务等)。
- 2)Securitymanager(安全管理器):shiro的核心,用于协调管理组件的工作。
- 3)Authenticator(认证管理器):负责进行认证操作。
- 4)Authorizer(授权管理器):负责授权检测。
- 5)SessionManager(会话管理器):负责创建和管理用的session生命周期,供在所有环境中的用户强大的会话体验。
- 6)SessionDAO:代表SessionManager执行会话持久性(CRUD)操作。 这允许将任何数据存储插入session管理基础结构。
- 7)CacheManager(缓存管理器):创建和管理其他Shiro组件使用的Cache实例生命周期。
- 8)Cryptographic(加密管理器):提供了加密方式的设计及管理。
- 9)Realms(领域对象):充当Shiro与应用程序的安全数据之间的“桥梁”或“连接器”。
一、认证业务实现
流程分析:
- 1)调用Subject的login方法将用户信息提交给SecurityManager
- 2.)SecurityManager将认证操作委托给认证器对象Authenticator
- 3)Authenticator将用户输入的身份信息传递给Realm
- 4)Realm访问数据库获取用户信息然后对信息进行封装并返回
- 5)Authenticator 对realm返回的信息进行身份认证
1、添加shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.2</version>
</dependency>
2、shiro核心对象配置
2.1 创建配置类,添加@Configuration
注解
- 表示为Spring配置类
2.2 创建SecurityManager对象,并交给Spring管理
- 需要把Realms、CacheManager、SessionManager等管理器对象放入SecurityManager对象中。
2.3 创建ShiroFilterFactoryBean对象
- 通过该对象创建过滤器工厂,再通过工厂创建过滤器,通过过滤器对请求进行过滤拦截。
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringShiroConfig {
/**配置shiro中的核心对象:安全管理器*/
@Bean
public SecurityManager securityManager(Realm realm, CacheManager shiroCacheManager, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
return securityManager;
}
/**配置ShiroFilterFactoryBean对象*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {
ShiroFilterFactoryBean sfBean = new ShiroFilterFactoryBean();
// 设置安全管理器
sfBean.setSecurityManager(securityManager);
// 设置登录页面url
sfBean.setLoginUrl("/doLoginUI");
// 设置过滤规则,定义map指定请求过滤规则
LinkedHashMap<String,String> map=
new LinkedHashMap<>();
//静态资源允许匿名访问:"anon"
map.put("/bower_components/**","anon");
map.put("/build/**","anon");
map.put("/dist/**","anon");
map.put("/plugins/**","anon");
map.put("/user/doLogin","anon");
map.put("/doLogout","logout");// logout由shiro提供
//除了匿名访问的资源,其它都要认证("authc")后访问
map.put("/**","authc");
sfBean.setFilterChainDefinitionMap(map);
return sfBean;
}
}
3、Controller业务实现
- 调用Subject的login方法将用户信息传递给securityManager
@RequestMapping("doLogin")
public JsonResult doLogin(String username, String password) {
// 获取subject对象
Subject subject = SecurityUtils.getSubject();
// 把用户信息封装进token中
UsernamePasswordToken token = new UsernamePasswordToken();
token.setUsername(username);
token.setPassword(password.toCharArray());
subject.login(token);
return new JsonResult("login OK");
}
4、认证业务实现
业务的实现要在Realm模型对象中进行实现,要继承AuthorizingRealm
并重写相关方法。
- 重写设置凭证适配器(
setCredentialsMatcher
)方法 - 重写获取认证信息方法(
doGetAuthenticationInfo
)方法
@Service
public class ShiroUserRealm extends AuthorizingRealm {
@Autowired
private SysUserDao sysUserDao;
/**
* 设置凭证匹配器(与用户添加操作使用相同的加密算法)
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
//构建凭证匹配对象
HashedCredentialsMatcher cMatcher=
new HashedCredentialsMatcher();
//设置加密算法
cMatcher.setHashAlgorithmName("MD5");
//设置加密次数
cMatcher.setHashIterations(1);
super.setCredentialsMatcher(cMatcher);
}
/**
* 通过此方法完成认证数据的获取及封装,系统
* 底层会将认证数据传递认证管理器,由认证
* 管理器完成认证操作。
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.获取用户名(用户页面输入)
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
String username=upToken.getUsername();
//2.基于用户名查询用户信息
SysUser user = sysUserDao.findUserByUserName(username);
//3.判定用户是否存在
if (user==null)
throw new UnknownAccountException();
//4.封装用户信息
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
user,//principal (身份)
user.getPassword(),//hashedCredentials
credentialsSalt, //credentialsSalt
getName());//realName
//5.返回封装结果
return info;//返回值会传递给认证管理器(后续认证管理器会通过此信息完成认证操作)
}
}
5、将Realm注入给配置类中的SecurityManager对象
@Bean
public SecurityManager securityManager(Realm realm) {
DefaultWebSecurityManager sManager = new DefaultWebSecurityManager();
sManager.setRealm(realm);
return sManager;
}
二、授权业务实现
流程分析
- 1)调用Subject的相关方法 (例如 checkPermission) 将用户信息递交给SecurityManager
- 2)SecurityManager将权限检测操作委托给Authorizer对象
- 3)Authorizer将用户信息委托给Realm
- 4)Realm访问数据库获取用户权限信息并封装
- 5)Authorizer对用户授权信息进行判定
1、在配置类中配置Bean对象的声明周期管理
Spring Boot可以不配置
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
2、在配置类中配置为目标业务对象创建代理对象
Spring Boot可以不配置
@DependsOn("lifecycleBeanPostProcessor")
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
3、在配置类中配置advisor对象
- shiro框架会通过此对象的matchs方法的返回值(类似于切面) 决定是否创建代理对象,进行权限控制
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
4、业务实现
- 重写获取授权信息
doGetAuthorizationInfo
方法
@Service
public class ShiroUserRealm extends AuthorizingRealm {
@Autowired
private SysUserDao sysUserDao;
@Autowired
private SysUserRoleDao sysUserRoleDao;
@Autowired
private SysRoleMenuDao sysRoleMenuDao;
@Autowired
private SysMenusDao sysMenuDao;
/**负责获取登录用户权限信息并进行封装*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//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
List<Integer> menuIds=
sysRoleMenuDao.findMenuIdsByRoleIds(roleIds.toArray(new Integer[] {}));
if(menuIds==null||menuIds.size()==0)
throw new AuthorizationException();
//4.基于菜单id获取授权标识
List<String> permissions=
sysMenuDao.findPermissions(menuIds.toArray(new Integer[] {}));
if(permissions==null||permissions.size()==0)
throw new AuthorizationException();
//5.对用户权限信息进行封装
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;//此对象会返回给授权管理器
}
}
5、授权访问实现
在需要授权访问的业务成方法上,添加@RequiresPermissions(“需要的权限”)
注解
/**
* 禁用和启用操作
* shiro框架通过这个注解定义切入点,在这里表示
* 访问此方法需要进行授权,需要具备这个注解中的权限标识
* 1、需要系统基于登录用户获取用户权限{"sys:user:update", "sys:user:view",...}
* 2、当用户包含注解中定义的权限表示,就标识用户拥有访问这个方法的权限
* 3=拥有权限则可以由shiro框架进行授权访问
*/
@RequiresPermissions("sys:user:update")
@RequiredLog(operation = "禁用启用")
@Override
public int validById(Long id, Integer valid) {
if (id == null || id < 1)
throw new IllegalArgumentException("id值无效");
System.out.println(valid);
if (valid != 0 && valid != 1)
throw new IllegalArgumentException("状态值不正确");
int row = sysUserDao.validById(id, valid, "admin");// admin是将来的登录用户
if (row == 0)
throw new ServiceException("记录可能不存在!");
return row;
}
三、shiro扩展功能
shiro缓存配置
1、在配置类中配置缓存bean对象
@Bean
public CacheManager shiroCacheManager(){
return new MemoryConstrainedCacheManager();
}
- 注意该缓存对象的名字不能为cacheManager,因为spring容器中已经有一个名为cacheManager的对象了
2、将缓存对象注入给SecurityManager对象
@Bean
public SecurityManager securityManager( Realm realm, CacheManager cacheManager) {
DefaultWebSecurityManager sManager = new DefaultWebSecurityManager();
sManager.setRealm(realm);
sManager.setCacheManager(cacheManager);
return sManager;
}
Shiro RememberMe
1、在客户端提交数据时把是否 记住我 选项一并提交
isRememberMe:$("#rememberId").prop("checked")
2、在controller的登录方法进行判断
@RequestMapping("doLogin")
public JsonResult doLogin(String username, String password, Boolean isRememberMe) {
// 获取subject对象
Subject subject = SecurityUtils.getSubject();
// 把用户信息封装进token中
UsernamePasswordToken token = new UsernamePasswordToken();
token.setUsername(username);
token.setPassword(password.toCharArray());
if (isRememberMe != null) {
token.setRememberMe(isRememberMe);
}
subject.login(token);
return new JsonResult("login OK");
}
3、在配置类中添加RememberMe管理器
@Bean
public RememberMeManager rememberMeManager() {
CookieRememberMeManager cManager = new CookieRememberMeManager();
SimpleCookie cookie = new SimpleCookie("remeberMe");
cookie.setMaxAge(7*24*60*60);
cManager.setCookie(cookie);
return cManager;
}
4、将RememberMe管理器注入securityManager中
@Bean
public SecurityManager securityManager(Realm realm, CacheManager shiroCacheManager, RememberMeManager rememberManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
securityManager.setCacheManager(shiroCacheManager);
securityManager.setRememberMeManager(rememberManager);
return securityManager;
}
5、修改shiro的过滤认证级别
- 将
map.put("/**","authc");
修改为map.put("/**","user");
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {
ShiroFilterFactoryBean sfBean = new ShiroFilterFactoryBean();
// 设置安全管理器
sfBean.setSecurityManager(securityManager);
// 设置登录页面url
sfBean.setLoginUrl("/doLoginUI");
// 设置过滤规则,定义map指定请求过滤规则
LinkedHashMap<String,String> map=
new LinkedHashMap<>();
//静态资源允许匿名访问:"anon"
map.put("/bower_components/**","anon");
map.put("/build/**","anon");
map.put("/dist/**","anon");
map.put("/plugins/**","anon");
map.put("/user/doLogin","anon");
map.put("/doLogout","logout");// logout由shiro提供
//除了匿名访问的资源,其它都要认证("authc")后访问
map.put("/**","user");// 设置记住我后要访问user
sfBean.setFilterChainDefinitionMap(map);
return sfBean;
}
shiro会话时长配置
使用shiro框架完成认证操作后,用户登录成功会将用户的信息写入到会话对象中,默认时长为30分钟。
1、在shiro配置类中,添加会话管理器的配置
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sManager = new DefaultWebSessionManager();
sManager.setGlobalSessionTimeout(60*60*24);
return sManager;
}
2、把会话管理器注入SecurityManager
@Bean
public SecurityManager securityManager(Realm realm, CacheManager shiroCacheManager,
RememberMeManager rememberManager, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
securityManager.setCacheManager(shiroCacheManager);
securityManager.setRememberMeManager(rememberManager);
securityManager.setSessionManager(sessionManager);
return securityManager;
}