Apache Shiro是Java的一个安全框架,主要完成:认证、授权、加密、会话管理、缓存等功能,而且API简单,越来越多人使用该框架。
基本功能如下:
,域的概念,Shiro从Realm中获取安全数据(如权限等),提供给SecurityManager来判断用户的权限。
通过上述的简介,可以得知,如果我们要对用户分不同的权限,如管理员、普通用户;要对会话进行管理,就需要用到Shiro这个安全框架。Shiro在SpringBoot中的使用就需要创建一个配置类,同时要配置Realm。
Shiro配置类中,需要配置生命周期处理器、SecurityManager,在该安全管理器中配置Realm、ShiroFilter过滤器、aop注解支持等。
/** * spring-boot集成shiro配置类 */ @Configuration public class ShiroConfiguration { /** * 实例化lifecycleBeanPostProcessor * shiro生命周期处理器 * * @return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 实例化DefaultAdvisorAutoProxyCreator * * @return */ @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass(true); return daap; } /** * 实例化自定义realm * * @return */ @Bean public Realm getRealm() { return new UserRealm(); } /** * 实例化securityManager * * @return */ @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(getRealm()); return securityManager; } /** * 实例化shiroFilter * * @return */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean() { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager()); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面; shiroFilterFactoryBean.setUnauthorizedUrl("/admin/403"); return shiroFilterFactoryBean; } /** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( org.apache.shiro.mgt.SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } } 复制代码
一个应用中可以有一个或者多个Realm,可以通过JDBC、内存来实现。在该系列文章中Server-2 MyBatis逆向生成中,观察仔细的朋友会发现,我创建了permission、role、role-permission三张数据表,分别表示了权限类型、角色类型、角色所拥有的权限这三种含义。这三张表与Realm之间有什么关系呢?在项目中创建了UserRealm用户认证、授权接口。具体代码如下:
public class UserRealm extends AuthorizingRealm { private final String realmName = "UserRealm.class"; @Autowired private UserMapper userMapper; @Autowired private PermissionService permissionService; @Autowired private RoleMapper roleMapper; //授权方法 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { Object principal = principals.getPrimaryPrincipal(); try { if (principal instanceof String) { UserExample example = new UserExample(); UserExample.Criteria criteria = example.createCriteria(); criteria.andAccountEqualTo((String) principal); Integer roleId = userMapper.selectByExample(example).get(0).getRoleId(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Role role = roleMapper.selectByPrimaryKey(roleId); info.addRole(role.getName()); Set<PermissionVO> permissions = permissionService.getPermissionsBy(roleId); for (PermissionVO permission : permissions) { info.addStringPermission(permission.getName()); } return info; } } catch (Exception e) { e.printStackTrace(); } return null; } /** * 认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { Object principal = token.getPrincipal(); if (principal instanceof String) { String phone = (String) principal; UserExample example = new UserExample(); UserExample.Criteria criteria = example.createCriteria(); criteria.andAccountEqualTo(phone); List<User> users = userMapper.selectByExample(example); if (null != users && users.size() > 0) { User user = users.get(0); return new SimpleAccount(user.getAccount(), user.getPassword(), realmName); } } return null; } } 复制代码
在UserRealm中,可以看出,其实认证和授权都是通过数据库的数据来进行判断,比如认证,其实就是通过MyBatis来查找数据库中是否存在该用户;授权方法,则是传入用户的账号,查看该用户的角色,再查找该角色所拥有的权限,将账号、角色、权限信息存储到AuthorizationInfo中,即完成用户授权。
下面几行简单的代码就完成了用户的认证。
Subject subject = SecurityUtils.getSubject(); //获取Subject AuthenticationToken token = new UsernamePasswordToken(account, MD5Util.encrypt(password)); //根据用户账号、密码获取令牌。 try{ subject.login(token); }catch( ..){ ... } 复制代码
首先,第一行代码,getSubject(),这里有个巨坑,我怀疑getSubject()这个方式是根据请求中是否带有cookie,来判断是新的subject还是已经存在的subject,因为如果没有带cookie的请求,每次获取的subject都不一致。如果Subject不唯一,那么会导致Session会话也不唯一,那么session保存的键值对(通常是一些状态、参数)就无法使用,导致功能的异常。 如果登录(用户认证)失败,那么就会抛出不同的异常,比如UnknownAccountException(账号不存在)、IncorrectCredentialsException(账号密码不匹配)、LockedAccountException(不允许登录)等。