Shiro实战+springboot集成

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: Shiro实战+springboot集成


注:本文只做流程分享,重点解读,源码已上传github,源码获取方式(左下角阅读原文)

Springboot+Shiro

版本介绍

Springboot: 2.0.5.RELEASE

Java: JDK1.8

Shiro: 1.4.1

MySQL:8.0

本文简介

该例子中使用Springboot集成Shiro的方式展示Shiro 授权,主要涉及技术点,tkmapper、Druid、maven、mysql、lombok

初始化mysql 数据库:init.sql见resources/db/init.sql

引入maven依赖:见pom.xml

修改generator.xml配置文件(涉及数据库连接地址信息以及对应数据表修改)

点击maven插件(mybatis-generator)生成entity,dao,mapper

生成的代码结构如下

到此权限代码的基础就已经搭建好了,使用的用户-角色-权限模型也是经典的权限设计模型

配置application.xml

server:
  port: 9006
#数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource #Druid连接池
    url: jdbc:mysql://localhost:3306/springboot-demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=true&nullCatalogMeansCurrent=true
    username: root #数据库用户名
    password: root #数据库密码
    driver-class-name: com.mysql.cj.jdbc.Driver #mysql驱动
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
mybatis:
  mapper-locations: classpath:/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    #打印sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper:
  identity: MYSQL # 配置主键自动增长(使用MYSQL原生方式)
logging:
  level:
    com.tz.springbootshiro: info
    
# 分页插件
pagehelper:
  reasonable: true
  page-size-zero: true
  params: pageNum=start;pageSize=limit
  support-methods-arguments: true

配置shiroconfig类,配置我们需要的组件

package com.tz.springbootshiro.config;
import com.oracle.tools.packager.Log;
import com.tz.springbootshiro.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.apache.shiro.mgt.SecurityManager;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * @author tz
 * @Classname ShiroConfig
 * @Description shiro 配置
 * @Date 2019-11-10 09:23
 */
@SpringBootConfiguration
public class ShiroConfig {
    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * 所以我们需要修改下doGetAuthenticationInfo中的代码;
     * )
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列的次数,比如散列两次,相当于 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(2);
        // 是否存储为16进制
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }
    /**
     * Shiro默认提供了三种 AuthenticationStrategy 实现:
     * AtLeastOneSuccessfulStrategy :其中一个通过则成功。
     * FirstSuccessfulStrategy :其中一个通过则成功,但只返回第一个通过的Realm提供的验证信息。
     * AllSuccessfulStrategy :凡是配置到应用中的Realm都必须全部通过。
     * authenticationStrategy
     *
     * @return
     */
    @Bean(name = "authenticationStrategy")
    public AuthenticationStrategy authenticationStrategy() {
        Log.info("----------->>>>>>>>>>>>ShiroConfiguration.authenticationStrategy()");
        return new FirstSuccessfulStrategy();
    }
    /**
     * 安全管理器,并把realm注入
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm());
        return securityManager;
    }
    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *  @RequiresPermissions("topic:list")
     *  @RequiresPermissions
     * @RequiresRoles
     * @RequiresUser
     * @RequiresGuest
     * @RequiresAuthentication
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
        //注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
        //所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/static/**/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/captcha.jpg", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    /**
     * 自定义realm
     */
    @Bean
    public MyRealm myRealm(){
        MyRealm userRealm = new MyRealm();
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return userRealm;
    }
}

自定义Realm 授权验证使用代码

package com.tz.springbootshiro.realm;
import com.tz.springbootshiro.bean.SysUser;
import com.tz.springbootshiro.service.SysPermissionService;
import com.tz.springbootshiro.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.TextConfigurationRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import java.util.List;
/**
 * @author tz
 * @Classname MyRealm
 * @Description
 * @Date 2019-11-09 11:44
 */
@Slf4j
public class MyRealm extends AuthorizingRealm {
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysPermissionService sysPermissionService;
    /**
     * 授权
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SysUser sysUser = (SysUser) principals.getPrimaryPrincipal();
        List<String> sysPermissions = sysPermissionService.selectPermissionByUserId(sysUser.getUserId());
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(sysPermissions);
        log.info("------------>>>>>>>>>>>>>>>>>>>>>>>>>>>>doGetAuthorizationInfo");
        return info;
    }
    /**
     * 认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        SysUser sysUser = sysUserService.findByUserName(token.getUsername());
        if (sysUser == null) {
            return null;
        }
        log.info("------------>>>>>>>>>>>>>>>>>>>>>>>>>>>>doGetAuthenticationInfo");
        return new SimpleAuthenticationInfo(sysUser, sysUser.getPassword().toCharArray(), ByteSource.Util.bytes(sysUser.getSalt()), getName());
    }
    /**
     * 测试realm
     *
     * @return
     */
    @Bean
    public Realm realm() {
        TextConfigurationRealm realm = new TextConfigurationRealm();
        realm.setUserDefinitions("joe.coder=password,user\n" +
                "jill.coder=password,admin");
        realm.setRoleDefinitions("admin=read,write\n" +
                "user=read");
        realm.setCachingEnabled(true);
        return realm;
    }
}
  1. 到此基础配置就结束了,对应的数据库实体类相关关系类可在github查找,下面掩饰结果,首先分析拦截url
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/static/**/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/captcha.jpg", "anon");
        filterChainDefinitionMap.put("/favicon.ico", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

上方代码,我们在shiroconfig配置中指定的,登录url为/login,此处为get请求,找到controller中我们的/login的地址进行跳转,此处需要注意的是我们的controller不能返回json体,即不能加入RequestBody注解,登录成功的url为/,此处没有该url所以代码不会跳转,我们实际项目中可以把此改为我们登录成功之后跳转的url,setUnauthorizedUrl配置我们未授权页面的url,然后下面配置我门不需要拦截的url,最后加上/**的拦截验证,url依次往下,注意顺序,这个拦截全部一定要放在最后,不然其他配置就不生效,下面放上我controller的代码

package com.tz.springbootshiro.controller;
import com.tz.springbootshiro.common.CodeMsg;
import com.tz.springbootshiro.common.ResultBean;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
/**
 * @author tz
 * @Classname LoginController
 * @Description 登录
 * @Date 2019-11-10 09:52
 */
@Controller
public class LoginController {
    /**
     * shiro 跳转登录页面使用
     * @return
     */
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String defaultLogin() {
        return "/login/login";
    }
    /**
     * 登录页面表单提交使用
     * @param username
     * @param password
     * @return
     */
    @ResponseBody
    @PostMapping(value = "/login")
    public ResultBean login(@RequestParam("userName") String username, @RequestParam("password") String password) {
        // 从SecurityUtils里边创建一个 subject
        Subject subject = SecurityUtils.getSubject();
        // 在认证提交前准备 token(令牌)
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        // 执行认证登陆
        try {
            subject.login(token);
        } catch (UnknownAccountException uae) {
            return new ResultBean(CodeMsg.ACCOUNT_NOT_FOUND);
        } catch (IncorrectCredentialsException ice) {
            return new ResultBean(CodeMsg.PASSWORD_ERROR);
        } catch (LockedAccountException lae) {
            return new ResultBean(CodeMsg.ACCOUNT_LOCK);
        } catch (ExcessiveAttemptsException eae) {
            return new ResultBean(CodeMsg.ERROR_NUM_MORE);
        } catch (AuthenticationException ae) {
            return new ResultBean(CodeMsg.USERNAME_PASSWORD_ERROR);
        }
        if (subject.isAuthenticated()) {
            return new ResultBean(CodeMsg.LOGIN_SUCCESS);
        } else {
            token.clear();
            return new ResultBean(CodeMsg.LOGIN_ERROR);
        }
    }
}

ok,教程到此结束,纯干货,源码----->阅读原文

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
打赏
0
0
0
0
186
分享
相关文章
SpringBoot 通过集成 Flink CDC 来实时追踪 MySql 数据变动
通过详细的步骤和示例代码,您可以在 SpringBoot 项目中成功集成 Flink CDC,并实时追踪 MySQL 数据库的变动。
131 43
SpringBoot集成Tomcat、DispatcherServlet
通过这些配置,您可以充分利用 Spring Boot 内置的功能,快速构建和优化您的 Web 应用。
52 21
Jeesite5:Star24k,Spring Boot 3.3+Vue3实战开源项目,架构深度拆解!让企业级项目开发效率提升300%的秘密武器
Jeesite5 是一个基于 Spring Boot 3.3 和 Vue3 的企业级快速开发平台,集成了众多优秀开源项目,如 MyBatis Plus、Bootstrap、JQuery 等。它提供了模块化设计、权限管理、多数据库支持、代码生成器和国际化等功能,极大地提高了企业级项目的开发效率。Jeesite5 广泛应用于企业管理系统、电商平台、客户关系管理和知识管理等领域。通过其强大的功能和灵活性,Jeesite5 成为了企业级开发的首选框架之一。访问 [Gitee 页面](https://gitee.com/thinkgem/jeesite5) 获取更多信息。
Jeesite5:Star24k,Spring Boot 3.3+Vue3实战开源项目,架构深度拆解!让企业级项目开发效率提升300%的秘密武器
|
1月前
|
使用Spring Boot集成Nacos
通过上述步骤,Spring Boot应用可以成功集成Nacos,利用Nacos的服务发现和配置管理功能来提升微服务架构的灵活性和可维护性。通过这种集成,开发者可以更高效地管理和部署微服务。
222 17
|
1月前
|
Java中的分布式缓存与Memcached集成实战
通过在Java项目中集成Memcached,可以显著提升系统的性能和响应速度。合理的缓存策略、分布式架构设计和异常处理机制是实现高效缓存的关键。希望本文提供的实战示例和优化建议能够帮助开发者更好地应用Memcached,实现高性能的分布式缓存解决方案。
41 9
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
92 11
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
417 12
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
87 8
gitlab-ci 集成 k3s 部署spring boot 应用
gitlab-ci 集成 k3s 部署spring boot 应用
您是否已集成 Spring Boot 与 ActiveMQ?
您是否已集成 Spring Boot 与 ActiveMQ?
79 0

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等