在阅读这篇文章以前假设你已经对Apache Shiro(后面统一用Shiro做为代指)有了必定的了解,若是你还对Shiro不熟悉的话在这篇文章的结尾附有相关的学习资料,关于Shiro是用来作什么的这里有个不错的介绍,在后面的文章中就不在对其进行描述了。后面的文章将围绕着 Spring Boot 集成Shiro 来进行展开。html
<!-- 使用Shiro认证 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.5</version> </dependency>
因为篇幅缘由这里不进行展开 提供一个参考前端
AbstractUserRealm继承AuthorizingRealm,并重写doGetAuthorizationInfo(用于获取认证成功后的角色、权限等信息) 和 doGetAuthenticationInfo(验证当前登陆的Subject)方法:web
public abstract class AbstractUserRealm extends AuthorizingRealm { private static final Logger logger = LoggerFactory.getLogger(AbstractUserRealm.class); @Autowired private UserRepository userRepository; //获取用户组的权限信息 public abstract UserRolesAndPermissions doGetGroupAuthorizationInfo(User userInfo); //获取用户角色的权限信息 public abstract UserRolesAndPermissions doGetRoleAuthorizationInfo(User userInfo); /** * 获取受权信息 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String currentLoginName = (String) principals.getPrimaryPrincipal(); Set<String> userRoles = new HashSet<>(); Set<String> userPermissions = new HashSet<>(); //从数据库中获取当前登陆用户的详细信息 User userInfo = userRepository.findByLoginName(currentLoginName); if (null != userInfo) { UserRolesAndPermissions groupContainer = doGetGroupAuthorizationInfo(userInfo); UserRolesAndPermissions roleContainer = doGetGroupAuthorizationInfo(userInfo); userRoles.addAll(groupContainer.getUserRoles()); userRoles.addAll(roleContainer.getUserRoles()); userPermissions.addAll(groupContainer.getUserPermissions()); userPermissions.addAll(roleContainer.getUserPermissions()); } else { throw new AuthorizationException(); } //为当前用户设置角色和权限 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRoles(userRoles); authorizationInfo.addStringPermissions(userPermissions); logger.info("###【获取角色成功】[SessionId] => {}", SecurityUtils.getSubject().getSession().getId()); return authorizationInfo; } /** * 登陆认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authenticationToken) throws AuthenticationException { //UsernamePasswordToken对象用来存放提交的登陆信息 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //查出是否有此用户 User user = userRepository.findByLoginName(token.getUsername()); if (user != null) { // 若存在,将此用户存放到登陆认证info中,无需本身作密码对比,Shiro会为咱们进行密码对比校验 return new SimpleAuthenticationInfo(user.getLoginName(), user.getPassword(), getName()); } return null; } protected class UserRolesAndPermissions { Set<String> userRoles; Set<String> userPermissions; public UserRolesAndPermissions(Set<String> userRoles, Set<String> userPermissions) { this.userRoles = userRoles; this.userPermissions = userPermissions; } public Set<String> getUserRoles() { return userRoles; } public Set<String> getUserPermissions() { return userPermissions; } }
@Component public class UserRealm extends AbstractUserRealm { @Override public UserRolesAndPermissions doGetGroupAuthorizationInfo(User userInfo) { Set<String> userRoles = new HashSet<>(); Set<String> userPermissions = new HashSet<>(); //TODO 获取当前用户下拥有的全部角色列表,及权限 return new UserRolesAndPermissions(userRoles, userPermissions); } @Override public UserRolesAndPermissions doGetRoleAuthorizationInfo(User userInfo) { Set<String> userRoles = new HashSet<>(); Set<String> userPermissions = new HashSet<>(); //TODO 获取当前用户下拥有的全部角色列表,及权限 return new UserRolesAndPermissions(userRoles, userPermissions); } }
这是最重要的一步等价于常规的Spring web应用的配置文件,将相关的配置托管给Spring 管理。spring
@Configuration public class ShiroConfiguration { private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class); /** * Shiro的Web过滤器Factory 命名:shiroFilter<br /> * * @param securityManager * @return */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { logger.info("注入Shiro的Web过滤器-->shiroFilter", ShiroFilterFactoryBean.class); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //Shiro的核心安全接口,这个属性是必须的 shiroFilterFactoryBean.setSecurityManager(securityManager); //要求登陆时的连接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); //登陆成功后要跳转的链接,逻辑也能够自定义,例如返回上次请求的页面 shiroFilterFactoryBean.setSuccessUrl("/index"); //用户访问未对其受权的资源时,所显示的链接 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); /*定义shiro过滤器,例如实现自定义的FormAuthenticationFilter,须要继承FormAuthenticationFilter **本例中暂不自定义实现,在下一节实现验证码的例子中体现 */ /*定义shiro过滤链 Map结构 * Map中key(xml中是指value值)的第一个'/'表明的路径是相对于HttpServletRequest.getContextPath()的值来的 * anon:它对应的过滤器里面是空的,什么都没作,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 * authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter */ Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置退出过滤器,其中的具体的退出代码Shiro已经替咱们实现了 filterChainDefinitionMap.put("/logout", "logout"); // <!-- 过滤链定义,从上向下顺序执行,通常将 /**放在最为下边 -->:这是一个坑呢,一不当心代码就很差使了; // <!-- authc:全部url都必须认证经过才能够访问; anon:全部url都均可以匿名访问--> filterChainDefinitionMap.put("/login", "anon");//anon 能够理解为不拦截 filterChainDefinitionMap.put("/reg", "anon"); filterChainDefinitionMap.put("/plugins/**", "anon"); filterChainDefinitionMap.put("/pages/**", "anon"); filterChainDefinitionMap.put("/api/**", "anon"); filterChainDefinitionMap.put("/dists/img/*", "anon"); filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public EhCacheManager ehCacheManager() { EhCacheManager cacheManager = new EhCacheManager(); return cacheManager; } /** * 不指定名字的话,自动建立一个方法名第一个字母小写的bean * @Bean(name = "securityManager") * @return */ @Bean public SecurityManager securityManager(UserRealm userRealm) { logger.info("注入Shiro的Web过滤器-->securityManager", ShiroFilterFactoryBean.class); DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); //注入缓存管理器; securityManager.setCacheManager(ehCacheManager());//这个若是执行屡次,也是一样的一个对象; return securityManager; } /** * Shiro生命周期处理器 * @return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 * 配置如下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)便可实现此功能 * @return */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
@Controller public class SecurityController { private static final Logger logger = LoggerFactory.getLogger(SecurityController.class); @Autowired private UserService userService; @GetMapping("/login") public String loginForm() { return "login"; } @PostMapping("/login") public String login(@Valid User user, BindingResult bindingResult, RedirectAttributes redirectAttributes) { if (bindingResult.hasErrors()) { return "login"; } String loginName = user.getLoginName(); logger.info("准备登录用户 => {}", loginName); UsernamePasswordToken token = new UsernamePasswordToken(loginName,user.getPassword()); //获取当前的Subject Subject currentUser = SecurityUtils.getSubject(); try { //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查 //每一个Realm都能在必要时对提交的AuthenticationTokens做出反应 //因此这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法 logger.info("对用户[" + loginName + "]进行登陆验证..验证开始"); currentUser.login(token); logger.info("对用户[" + loginName + "]进行登陆验证..验证经过"); } catch (UnknownAccountException uae) { logger.info("对用户[" + loginName + "]进行登陆验证..验证未经过,未知帐户"); redirectAttributes.addFlashAttribute("message", "未知帐户"); } catch (IncorrectCredentialsException ice) { logger.info("对用户[" + loginName + "]进行登陆验证..验证未经过,错误的凭证"); redirectAttributes.addFlashAttribute("message", "密码不正确"); } catch (LockedAccountException lae) { logger.info("对用户[" + loginName + "]进行登陆验证..验证未经过,帐户已锁定"); redirectAttributes.addFlashAttribute("message", "帐户已锁定"); } catch (ExcessiveAttemptsException eae) { logger.info("对用户[" + loginName + "]进行登陆验证..验证未经过,错误次数过多"); redirectAttributes.addFlashAttribute("message", "用户名或密码错误次数过多"); } catch (AuthenticationException ae) { //经过处理Shiro的运行时AuthenticationException就能够控制用户登陆失败或密码错误时的情景 logger.info("对用户[" + loginName + "]进行登陆验证..验证未经过,堆栈轨迹以下"); ae.printStackTrace(); redirectAttributes.addFlashAttribute("message", "用户名或密码不正确"); } //验证是否登陆成功 if (currentUser.isAuthenticated()) { logger.info("用户[" + loginName + "]登陆认证经过(这里能够进行一些认证经过后的一些系统参数初始化操做)"); return "redirect:/index"; } else { token.clear(); return "redirect:/login"; } } @GetMapping("/logout") public String logout(RedirectAttributes redirectAttributes) { //使用权限管理工具进行用户的退出,跳出登陆,给出提示信息 SecurityUtils.getSubject().logout(); redirectAttributes.addFlashAttribute("message", "您已安全退出"); return "redirect:/login"; } @GetMapping("/reg") @ResponseBody public Result<String> reg(@Valid User user, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return Result.error("用户信息填写不完整"); } userService.save(user); return Result.ok(); } }
一个简单 form表单提交的demo数据库
<form action="login" method="POST"> <div class="form-group has-feedback"> <input name="loginName" type="text" class="form-control" placeholder="用户名"/> <span class="glyphicon glyphicon-user form-control-feedback"></span> </div> <div class="form-group has-feedback"> <input name="password" type="password" class="form-control"/> <span class="glyphicon glyphicon-lock form-control-feedback"></span> </div> <div class="row"> <!-- /.col --> <div class="col-xs-12"> <button type="submit" class="btn btn-primary btn-block btn-flat">登 录</button> </div> <!-- /.col --> </div> </form>
权限注解:apache
@RequiresAuthentication 表示当前Subject已经经过login进行了身份验证;即Subject. isAuthenticated()返回true。 @RequiresUser 表示当前Subject已经身份验证或者经过记住我登陆的。 @RequiresGuest 表示当前Subject没有身份验证或经过记住我登陆过,便是游客身份。 @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND) 表示当前Subject须要角色admin和user。 @RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR) 表示当前Subject须要权限user:a或user:b。
标签
代码验证:
(暂时忽略)留待补充api
Shiro 做为一款安全框架为咱们提供了经常使用的功能,已经足够应对绝大多数的业务须要,在下一篇文章中将介绍一款更增强大的安全框架 Spring Security。缓存
Spring Boot系列(十五) 安全框架Apache Shiro(一)基本功能
Spring Boot Shiro 权限管理安全
Apache Shiro 使用手册
《跟开涛学Shiro》 - 博客版
跟开涛学 Shiro - wiki版
官方文档app