Shiro简介
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。shiro由三大组件构成:Subject、SecurityManager、Realm
。
Subject
:可以理解为主体或者用户,是一个抽象概念,这个用户不一定是一个具体的人,可以是与当前应用交互的任何东西。
SecurityManager
:安全管理器,即所有与安全有关的操作都会与 SecurityManager 交互;是 Shiro 的核心,它管理着所有 Subject,且负责进行认证和授权、会话、缓存的管理。
Realm
:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
总结一下,就是最简单的一个 Shiro 应用:
应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
接下来就让我们在代码层面继承一下shiro。首先我们要准备好环境,创建一个简单的springboot工程,配置及数据库操作这里不再赘述,有需要的可以后台私聊阿Q呦。
集成Shiro
一、引入shiro的依赖包
1 2 3 4 5
| <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.3</version> </dependency>
|
二、自定义Realm进行权限认证和身份认证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| @Slf4j public class CustomRealm extends AuthorizingRealm { @Autowired private SysUserInfoMapper userInfoMapper;
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { log.info("————权限认证————"); String username = (String) SecurityUtils.getSubject().getPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); String role = userInfoMapper.getRole(username); Set<String> set = new HashSet<>(); set.add(role); info.setRoles(set); return info; }
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { log.info("————身份认证方法————"); UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; SysUserInfo userInfo = userInfoMapper.getUserByLogin(token.getUsername()); if (null == userInfo) { throw new BusinessException(CommonResultStatus.USERNAME_ERROR); } else if (!userInfo.getPassword().equals(new String((char[]) token.getCredentials()))) { throw new BusinessException(CommonResultStatus.PASSWORD_ERROR); } return new SimpleAuthenticationInfo(token.getPrincipal(), userInfo.getPassword(), getName()); } }
|
三、创建ShiroConfig,将ShiroFilterFactoryBean交由spring管理;将自定义的身份认证交由SecurityManager管理
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| @Configuration @Slf4j public class ShiroConfig {
@Bean public ShiroFilterFactoryBean shiroFilter(){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager()); Map<String, Filter> filters = new LinkedHashMap<>(); filters.put("authc", new CustomRolesOrAuthorizationFilter()); shiroFilterFactoryBean.setFilters(filters);
shiroFilterFactoryBean.setLoginUrl("/adminLogin/login"); shiroFilterFactoryBean.setUnauthorizedUrl("/notAuth");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/guest/**", "anon"); filterChainDefinitionMap.put("/user/**", "roles[user]"); filterChainDefinitionMap.put("/admin/**", "roles[admin]"); filterChainDefinitionMap.put("/adminLogin/login", "anon"); filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); log.info("-------Shiro拦截器工厂类注入成功-----------"); return shiroFilterFactoryBean; }
@Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(customRealm()); return securityManager; }
@Bean public CustomRealm customRealm(){ return new CustomRealm(); }
}
|
补充:
anon:无需认证(登陆)可以访问
authc:必须认证才可以访问
user:如果使用rememberMe的功能可以直接访问
perms:该资源必须得到资源权限才可以访问
role:该资源必须得到角色权限才可以访问
四、自定义的过滤器代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter{
@Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { Subject subject = getSubject(request, response); Object principal = subject.getPrincipal(); if(principal==null){ return false; } String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { return true; }
Set<String> roles = CollectionUtils.asSet(rolesArray); for(String role : roles){ if(subject.hasRole(role)){ return true; } } return false; }
}
|
五、接下来根据业务来测试一下shiro的相关功能,附上业务测试代码:登陆代码+商品列表代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @RestController @RequestMapping("adminLogin") public class LoginController {
@Autowired private UserInfoService userInfoService;
@PostMapping("login") public AjaxResult login(@RequestBody SysUserInfo userInfo){ Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(userInfo.getName(),userInfo.getPassword()); subject.login(token); return AjaxResult.success("登陆成功"); } }
@RestController @RequestMapping("/productInfo") public class ProductInfoController {
@Autowired private ProductInfoService productInfoService;
@RequestMapping("/getProductList") public AjaxResult getProductList(){ List<ProductInfo> list = productInfoService.getProductInfoList(); return AjaxResult.success(list); }
}
|
疑问:
成功登录之后,再次请求时,服务器是如何知道已经登录,是哪个用户,是使用HttpSession还是shiro的Session的呢?
在处理请求时,ShiroFilterFactoryBean实现了FactoryBean接口,在加载ShiroFilterFactoryBean时实际会加载SpringShiroFilter并添加到应用的过滤器链中。当有请求进来的时候,都会被SpringShiroFilter拦截到。Subject值就是在SpringShiroFilter拦截的过程中设置到线程变量中的。SpringShiroFilter的拦截方法中最关键的两步是createSubject和bind到ThreadContext里。到这里,可以理出大致的流程,用户发起请求->调用SpringShiroFilter的doFilter->创建Subject->设置到线程变量中,方便在后面取出使用。
创建subject可以分为以下三步:
1 2 3 4 5
| 将request、response封装成shiro的ShiroHttpServletRequest,ShiroHttpServletResponse
获取session、principals的值设置到context里
根据context生成Subject
|
重点:ShiroConfig中ShiroFilterFactoryBean的讲解:
当代码中有filterChainDefinitionMap.put(“/productInfo/**”, “roles[user]”);时,代表商品列表需要权限验证,此时不会去走自定义的过滤器;
而当将代码中的 filters.put(“authc”, new CustomRolesOrAuthorizationFilter());
改为filters.put(“roles”, new CustomRolesOrAuthorizationFilter()); 时,代码会先去走该过滤器进行权限验证,isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)中的mappedValue便是roles[user]中括号里的集合。
想了解更多学习知识,请关注微信公众号“阿Q说”,回复“shiro”可获取本期源码!你也可以后台留言说出你的疑惑,阿Q将会在后期的文章中为你解答。每天学习一点点,每天进步一点点。