资讯专栏INFORMATION COLUMN

Spring Boot [集成-Shiro]

gclove / 1024人阅读

摘要:后面的文章将围绕着集成来进行展开。表示当前已经身份验证或者通过记住我登录的。表示当前需要角色和。参考资料系列十五安全框架一基本功能权限管理学习资料使用手册跟开涛学博客版跟开涛学版官方文档

导读:

在阅读这篇文章之前假设你已经对Apache Shiro(后面统一用Shiro作为代指)有了一定的了解,如果你还对Shiro不熟悉的话在这篇文章的结尾附有相关的学习资料,关于Shiro是用来做什么的这里有个不错的介绍,在后面的文章中就不在对其进行描述了。后面的文章将围绕着 Spring Boot 集成Shiro 来进行展开。

快速上手: 1.引入pom依赖


    org.apache.shiro
    shiro-spring
    1.2.5
2.实现相关的 用户,角色,权限等代码的编写:

由于篇幅原因这里不进行展开 提供一个参考

3.实现Realm:

AbstractUserRealm继承AuthorizingRealm,并重写doGetAuthorizationInfo(用于获取认证成功后的角色、权限等信息) 和 doGetAuthenticationInfo(验证当前登录的Subject)方法:

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 userRoles = new HashSet<>();
        Set 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 userRoles;
        Set userPermissions;

        public UserRolesAndPermissions(Set userRoles, Set userPermissions) {
            this.userRoles = userRoles;
            this.userPermissions = userPermissions;
        }

        public Set getUserRoles() {
            return userRoles;
        }

        public Set getUserPermissions() {
            return userPermissions;
        }
    }
    
@Component
public class UserRealm extends AbstractUserRealm {

    @Override
    public UserRolesAndPermissions doGetGroupAuthorizationInfo(User userInfo) {
        Set userRoles = new HashSet<>();
        Set userPermissions = new HashSet<>();
        //TODO 获取当前用户下拥有的所有角色列表,及权限
        return new UserRolesAndPermissions(userRoles, userPermissions);
    }

    @Override
    public UserRolesAndPermissions doGetRoleAuthorizationInfo(User userInfo) {
        Set userRoles = new HashSet<>();
        Set userPermissions = new HashSet<>();
        //TODO 获取当前用户下拥有的所有角色列表,及权限
        return new UserRolesAndPermissions(userRoles, userPermissions);
    }
}
4.创建Shiro配置类:

这是最重要的一步等价于常规的Spring web应用的配置文件,将相关的配置托管给Spring 管理。

@Configuration
public class ShiroConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);

    /**
     * Shiro的Web过滤器Factory 命名:shiroFilter
* * @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 filterChainDefinitionMap = new LinkedHashMap(); // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); // :这是一个坑呢,一不小心代码就不好使了; // 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; } }
5.实现登录/退出等操作:
@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 reg(@Valid User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return Result.error("用户信息填写不完整");
        }
        userService.save(user);
        return Result.ok();
    }
}
6.前端页面编写:

一个简单 form表单提交的demo

        
扩展:

权限注解:

@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。  

标签
代码验证:
(暂时忽略)留待补充

结语:

Shiro 作为一款安全框架为我们提供了常用的功能,已经足够应对绝大多数的业务需要,在下一篇文章中将介绍一款更加强大的安全框架 Spring Security。

参考资料:

Spring Boot系列(十五) 安全框架Apache Shiro(一)基本功能
Spring Boot Shiro 权限管理

学习资料:

Apache Shiro 使用手册
《跟开涛学Shiro》 - 博客版
跟开涛学 Shiro - wiki版
官方文档

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/11245.html

相关文章

  • Spring Boot [集成-Shiro]

    摘要:后面的文章将围绕着集成来进行展开。表示当前已经身份验证或者通过记住我登录的。表示当前需要角色和。参考资料系列十五安全框架一基本功能权限管理学习资料使用手册跟开涛学博客版跟开涛学版官方文档 导读: 在阅读这篇文章之前假设你已经对Apache Shiro(后面统一用Shiro作为代指)有了一定的了解,如果你还对Shiro不熟悉的话在这篇文章的结尾附有相关的学习资料,关于Shiro是用来做什...

    superw 评论0 收藏0
  • 《 Kotlin + Spring Boot : 下一代 Java 服务端开发 》

    摘要:下一代服务端开发下一代服务端开发第部门快速开始第章快速开始环境准备,,快速上手实现一个第章企业级服务开发从到语言的缺点发展历程的缺点为什么是产生的背景解决了哪些问题为什么是的发展历程容器的配置地狱是什么从到下一代企业级服务开发在移动开发领域 《 Kotlin + Spring Boot : 下一代 Java 服务端开发 》 Kotlin + Spring Boot : 下一代 Java...

    springDevBird 评论0 收藏0
  • springmvc项目转为springboot

    摘要:说明如果你的项目连项目都不是,请自行转为项目,在按照本教程进行。本教程适用于的项目。处理拦截资源文件问题。 说明 如果你的项目连maven项目都不是,请自行转为maven项目,在按照本教程进行。本教程适用于spring+springmvc+mybatis+shiro的maven项目。1.修改pom文件依赖 删除之前的spring依赖,添加springboot依赖 or...

    wqj97 评论0 收藏0
  • Spring Security

    摘要:框架具有轻便,开源的优点,所以本译见构建用户管理微服务五使用令牌和来实现身份验证往期译见系列文章在账号分享中持续连载,敬请查看在往期译见系列的文章中,我们已经建立了业务逻辑数据访问层和前端控制器但是忽略了对身份进行验证。 重拾后端之Spring Boot(四):使用JWT和Spring Security保护REST API 重拾后端之Spring Boot(一):REST API的搭建...

    keelii 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<