首页
友链
关于
免责声明
Search
1
王者营地战绩数据王者荣耀查询网页源码
6,210 阅读
2
群晖Active Backup for Business套件备份Linux服务器教程
4,384 阅读
3
影视分享
4,313 阅读
4
(亲测)Jrebel激活破解方式2019-08-21
4,289 阅读
5
centos7 安装及卸载 jekenis
3,573 阅读
日常
文章
后端
前端
Linux
异常
Flutter
分享
群辉
登录
Search
标签搜索
docker
springboot
Spring Boot
java
linux
Shiro
Graphics2D
图片
游戏账号交易
Mybatis
Spring Cloud
centos
脚本
Web Station
群辉
王者营地
战绩查询
平台对接
Spring Cloud Alibaba
nacos
绿林寻猫
累计撰写
249
篇文章
累计收到
26
条评论
首页
栏目
日常
文章
后端
前端
Linux
异常
Flutter
分享
群辉
页面
友链
关于
免责声明
搜索到
249
篇与
绿林寻猫
的结果
2021-12-08
BigDecimal
运算:加法: add() 减法:subtract()乘法:multiply() 除法:divide() 绝对值:abs()取整:BigDecimal bd = new BigDecimal(“12.1”);// 向上取整long l = bd.setScale( 0, BigDecimal.ROUND_UP ).longValue();// 向下取整long l = bd.setScale( 0, BigDecimal.ROUND_DOWN ).longValue();保留2位小数:BigDecimal bg = new BigDecimal(f);BigDecimal f1 = bg.setScale(2, BigDecimal.ROUND_HALF_UP);比较BigDecimal a = new BigDecimal (101);BigDecimal b = new BigDecimal (111); //使用compareTo方法比较//注意:a、b均不能为null,否则会报空指针if(a.compareTo(b) == -1){ System.out.println("a小于b");} if(a.compareTo(b) == 0){ System.out.println("a等于b");} if(a.compareTo(b) == 1){ System.out.println("a大于b");} if(a.compareTo(b) > -1){ System.out.println("a大于等于b");} if(a.compareTo(b) < 1){ System.out.println("a小于等于b");}int i = bigDecimal.compareTo(BigDecimal.ZERO);i=0:表示bigDecimal的值 等于 0i=1:表示bigDecimal的值与 大于0i=-1:表示bigDecimal的值与 小于 0
2021年12月08日
227 阅读
0 评论
0 点赞
2021-12-08
SpringBoot +Mybatis druid多数据源
配置依赖导入 <!-- springboot,druid 整合包 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!--aop--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- springboot,mybatis 整合包 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency>application.yml# 开发环境配置 server: # 服务器的HTTP端口,默认为80 port: 80 servlet: # 应用的访问路径 context-path: / tomcat: # tomcat的URI编码 uri-encoding: UTF-8 # tomcat最大线程数,默认为200 max-threads: 800 # Tomcat启动初始化的线程数,默认值25 min-spare-threads: 30 # Spring配置 spring: # 模板引擎 thymeleaf: mode: HTML encoding: utf-8 # 禁用缓存 cache: false profiles: active: druid # mybatis 配置 mybatis: mapper-locations: classpath:mybatis/mappers/*.xml configuration: map-underscore-to-camel-case: true #驼峰转换 use-generated-keys: true #获取数据库自增列 use-column-label: true #使用列别名替换列名 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl default-executor-type: reuse #配置默认的执行器 cache-enabled: true #全局映射器启用缓存 type-aliases-package: com.comet #实体类配置别名 application-druid.yml# 数据源配置 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.jdbc.Driver druid: # 主库数据源 master: url: jdbc:mysql://192.168.1.20:3306/comet1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: root # 从库数据源 cluster: # 从数据源开关/默认关闭 enabled: true url: jdbc:mysql://192.168.1.21:3306/comet2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: root # 初始连接数 initialSize: 5 # 最小连接池数量 minIdle: 10 # 最大连接池数量 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最大生存的时间,单位是毫秒 maxEvictableIdleTimeMillis: 900000 # 配置检测连接是否有效 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false # 配置DruidStatFilter webStatFilter: enabled: true url-pattern: "/*" exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*" # 配置DruidStatViewServlet statViewServlet: enabled: true # 设置白名单,不填则允许所有访问 allow: url-pattern: /druid/* # 控制台管理用户名和密码 login-username: login-password: filter: stat: enabled: true # 慢SQL记录 log-slow-sql: true slow-sql-millis: 1000 merge-sql: true wall: config: multi-statement-allow: true代码枚举类 数据类型/** * @author Unclue_liu * @organization comet * @date 2019/9/3 0003 17:00 * @desc TODO 数据类型 */ public enum DataSourceType { /** * 主数据 */ MASTER, /** * 从数据 */ CLUSTER }注解类 自定义注解import com.comet.common.enums.DataSourceType; import java.lang.annotation.*; /** * @author Unclue_liu * @organization comet * @date 2019/9/3 0003 17:00 * @desc TODO 自定义多数据源切换注解,可以用于service类或者方法级别 方法级别优先级 > 类级别 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DataSource { /** * 切换数据源名称 */ DataSourceType value() default DataSourceType.MASTER; }druid 配置属性import com.alibaba.druid.pool.DruidDataSource; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; /** * @author Unclue_liu * @organization comet * @date 2019/10/18 0018 15:36 * @desc TODO druid 配置属性 */ @Data @Configuration public class DruidProperties { @Value("${spring.datasource.druid.initialSize}") private int initialSize; @Value("${spring.datasource.druid.minIdle}") private int minIdle; @Value("${spring.datasource.druid.maxActive}") private int maxActive; @Value("${spring.datasource.druid.maxWait}") private int maxWait; @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") private int timeBetweenEvictionRunsMillis; @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") private int minEvictableIdleTimeMillis; @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") private int maxEvictableIdleTimeMillis; @Value("${spring.datasource.druid.validationQuery}") private String validationQuery; @Value("${spring.datasource.druid.testWhileIdle}") private boolean testWhileIdle; @Value("${spring.datasource.druid.testOnBorrow}") private boolean testOnBorrow; @Value("${spring.datasource.druid.testOnReturn}") private boolean testOnReturn; public DruidDataSource dataSource(DruidDataSource datasource) { /** 配置初始化大小、最小、最大 */ datasource.setInitialSize(initialSize); datasource.setMaxActive(maxActive); datasource.setMinIdle(minIdle); /** 配置获取连接等待超时的时间 */ datasource.setMaxWait(maxWait); /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); /** * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 */ datasource.setValidationQuery(validationQuery); /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ datasource.setTestWhileIdle(testWhileIdle); /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ datasource.setTestOnBorrow(testOnBorrow); /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ datasource.setTestOnReturn(testOnReturn); return datasource; } }数据源切换处理import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Unclue_liu * @organization comet * @date 2019/10/18 0018 15:36 * @desc TODO 数据源切换处理 */ public class DynamicDataSourceContextHolder { public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /** * CONTEXT_HOLDER代表一个可以存放String类型的ThreadLocal对象, * 此时任何一个线程可以并发访问这个变量, * 对它进行写入、读取操作,都是线程安全的。 * 比如一个线程通过CONTEXT_HOLDER.set(“aaaa”);将数据写入ThreadLocal中, * 在任何一个地方,都可以通过CONTEXT_HOLDER.get();将值获取出来。 * 这里写入的就是数据库名, */ private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); /** * 设置数据源的变量 */ public static void setDataSourceType(String dsType) { log.info("切换到{}数据源", dsType); CONTEXT_HOLDER.set(dsType); } /** * 获得数据源的变量 */ public static String getDataSourceType() { return CONTEXT_HOLDER.get(); } /** * 清空数据源变量 */ public static void clearDataSourceType() { CONTEXT_HOLDER.remove(); } }动态数据源import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.Map; /** * @author Unclue_liu * @organization comet * @date 2019/10/18 0018 15:36 * @desc TODO 动态数据源 */ public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }druid 配置多数据源import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties; import com.alibaba.druid.util.Utils; import com.comet.config.datasource.DynamicDataSource; import com.comet.common.enums.DataSourceType; import com.comet.config.properties.DruidProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.servlet.*; import javax.sql.DataSource; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * @author Unclue_liu * @organization comet * @date 2019/10/18 0018 15:36 * @desc TODO druid 配置多数据源 */ @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.druid.master") public javax.sql.DataSource masterDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean @ConfigurationProperties("spring.datasource.druid.cluster") @ConditionalOnProperty(prefix = "spring.datasource.druid.cluster", name = "enabled", havingValue = "true") public javax.sql.DataSource clusterDataSource(DruidProperties druidProperties) { DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return druidProperties.dataSource(dataSource); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dataSource(javax.sql.DataSource masterDataSource, DataSource clusterDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); targetDataSources.put(DataSourceType.CLUSTER.name(), clusterDataSource); return new DynamicDataSource(masterDataSource, targetDataSources); } /** * 去除监控页面底部的广告 */ @SuppressWarnings({ "rawtypes", "unchecked" }) @Bean @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true") public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) { // 获取web监控页面的参数 DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); // 提取common.js的配置路径 String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); final String filePath = "support/http/resources/js/common.js"; // 创建filter进行过滤 Filter filter = new Filter() { @Override public void init(javax.servlet.FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(request, response); // 重置缓冲区,响应头不会被重置 response.resetBuffer(); // 获取common.js String text = Utils.readFromResource(filePath); // 正则替换banner, 除去底部的广告信息 text = text.replaceAll("<a.*?banner\"></a><br/>", ""); text = text.replaceAll("powered.*?shrek.wang</a>", ""); response.getWriter().write(text); } @Override public void destroy() { } }; FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(filter); registrationBean.addUrlPatterns(commonJsPattern); return registrationBean; } }多数据源处理import com.comet.common.annotation.DataSource; import com.comet.common.utils.StringUtils; import com.comet.config.datasource.DynamicDataSourceContextHolder; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * @author Unclue_liu * @organization comet * @date 2019/10/18 0018 15:36 * @desc TODO 多数据源处理 */ @Aspect @Order(1) @Component public class DataSourceAspect { @Pointcut("@annotation(com.comet.common.annotation.DataSource)" + "|| @within(com.comet.common.annotation.DataSource)") public void dsPointCut() { } @Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { DataSource dataSource = getDataSource(point); if (StringUtils.isNotNull(dataSource)) { DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); } try { return point.proceed(); } finally { // 销毁数据源 在执行方法之后 DynamicDataSourceContextHolder.clearDataSourceType(); } } /** * 获取需要切换的数据源 */ public DataSource getDataSource(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); Class<? extends Object> targetClass = point.getTarget().getClass(); DataSource targetDataSource = targetClass.getAnnotation(DataSource.class); if (StringUtils.isNotNull(targetDataSource)) { return targetDataSource; } else { Method method = signature.getMethod(); DataSource dataSource = method.getAnnotation(DataSource.class); return dataSource; } } }至此多数据源配置完成。使用如果主从数据都使用,将从数据源改为true否则反之。这里配置druid的登录账户密码,如果没有配置则直接通过http://127.0.0.1/druid/index.html访问项目启动默认使用主数据源,如需切换从数据可使用如下方式:或
2021年12月08日
549 阅读
0 评论
0 点赞
2021-12-08
SpringBoot+Mybatis启动项目*required a single bean, but 2 were found*
异常问题为controller中存在多个相同的实例*************************** APPLICATION FAILED TO START *************************** Description: Field sysJobService in com.comet.controller.SysJobController required a single bean, but 2 were found: - sysJobServiceImpl: defined in file [G:\IdeaProject\comet\comet-quartz\target\classes\com\comet\service\impl\SysJobServiceImpl.class] - ISysJobService: defined in file [G:\IdeaProject\comet\comet-quartz\target\classes\com\comet\service\ISysJobService.class] Action: Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed 我个人在配置映射时没有制定到mapper包中才出现上述问题 改成这样就能成功启动并访问数据库
2021年12月08日
200 阅读
0 评论
0 点赞
2021-12-08
Spring Boot 整合 Shiro(三)Kaptcha验证码 附源码
前言本文是根据上篇《Spring Boot 整合Shiro(二)加密登录与密码加盐处理》进行修改,如有不明白的转上篇文章了解。1.导入依赖 <!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha --> <dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency> 2.配置创建KaptchaConfig.javaimport com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import java.util.Properties; @Component public class KaptchaConfig { @Bean public DefaultKaptcha getDefaultKaptcha() { com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha(); Properties properties = new Properties(); // 图片边框 properties.setProperty("kaptcha.border", "yes"); // 边框颜色 properties.setProperty("kaptcha.border.color", "105,179,90"); // 字体颜色 properties.setProperty("kaptcha.textproducer.font.color", "red"); // 图片宽 properties.setProperty("kaptcha.image.width", "110"); // 图片高 properties.setProperty("kaptcha.image.height", "40"); // 字体大小 properties.setProperty("kaptcha.textproducer.font.size", "30"); // session key properties.setProperty("kaptcha.session.key", "code"); // 验证码长度 properties.setProperty("kaptcha.textproducer.char.length", "4"); // 字体 properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑"); //可以设置很多属性,具体看com.google.code.kaptcha.Constants // kaptcha.border 是否有边框 默认为true 我们可以自己设置yes,no // kaptcha.border.color 边框颜色 默认为Color.BLACK // kaptcha.border.thickness 边框粗细度 默认为1 // kaptcha.producer.impl 验证码生成器 默认为DefaultKaptcha // kaptcha.textproducer.impl 验证码文本生成器 默认为DefaultTextCreator // kaptcha.textproducer.char.string 验证码文本字符内容范围 默认为abcde2345678gfynmnpwx // kaptcha.textproducer.char.length 验证码文本字符长度 默认为5 // kaptcha.textproducer.font.names 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) // kaptcha.textproducer.font.size 验证码文本字符大小 默认为40 // kaptcha.textproducer.font.color 验证码文本字符颜色 默认为Color.BLACK // kaptcha.textproducer.char.space 验证码文本字符间距 默认为2 // kaptcha.noise.impl 验证码噪点生成对象 默认为DefaultNoise // kaptcha.noise.color 验证码噪点颜色 默认为Color.BLACK // kaptcha.obscurificator.impl 验证码样式引擎 默认为WaterRipple // kaptcha.word.impl 验证码文本字符渲染 默认为DefaultWordRenderer // kaptcha.background.impl 验证码背景生成器 默认为DefaultBackground // kaptcha.background.clear.from 验证码背景颜色渐进 默认为Color.LIGHT_GRAY // kaptcha.background.clear.to 验证码背景颜色渐进 默认为Color.WHITE // kaptcha.image.width 验证码图片宽度 默认为200 // kaptcha.image.height 验证码图片高度 默认为50 Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } } 3.验证码生成/验证import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import com.google.code.kaptcha.impl.DefaultKaptcha; @Controller @RequestMapping(value ="/kaptcha" ) public class KaptchaController { /** * 1、验证码工具 */ @Autowired DefaultKaptcha defaultKaptcha; /** * 2、生成验证码 * @param httpServletRequest * @param httpServletResponse * @throws Exception */ @RequestMapping("/defaultKaptcha") public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { byte[] captchaChallengeAsJpeg = null; ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream(); try { // 生产验证码字符串并保存到session中 String createText = defaultKaptcha.createText(); httpServletRequest.getSession().setAttribute("rightCode", createText); // 使用生产的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中 BufferedImage challenge = defaultKaptcha.createImage(createText); ImageIO.write(challenge, "jpg", jpegOutputStream); } catch (IllegalArgumentException e) { httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组 captchaChallengeAsJpeg = jpegOutputStream.toByteArray(); httpServletResponse.setHeader("Cache-Control", "no-store"); httpServletResponse.setHeader("Pragma", "no-cache"); httpServletResponse.setDateHeader("Expires", 0); httpServletResponse.setContentType("image/jpeg"); ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream(); responseOutputStream.write(captchaChallengeAsJpeg); responseOutputStream.flush(); responseOutputStream.close(); } /** * 3、校对验证码 * @param httpServletRequest * @param httpServletResponse * @return */ @RequestMapping("/imgvrifyControllerDefaultKaptcha") public ModelAndView imgvrifyControllerDefaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { ModelAndView andView = new ModelAndView(); String rightCode = (String) httpServletRequest.getSession().getAttribute("rightCode"); String tryCode = httpServletRequest.getParameter("tryCode"); System.out.println("rightCode:"+rightCode+" ———— tryCode:"+tryCode); if (!rightCode.equals(tryCode)) { andView.addObject("info", "错误的验证码"); andView.setViewName("/login"); } else { andView.addObject("info", "登录成功"); andView.setViewName("success"); } return andView; } } 4.修改ShiroConfig开放相应的验证码路径,避免拦截@Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // Shiro的核心安全接口,这个属性是必须的 shiroFilterFactoryBean.setSecurityManager(securityManager); // 身份认证失败,则跳转到登录页面的配置 shiroFilterFactoryBean.setLoginUrl("/login"); // 权限认证失败,则跳转到指定页面 shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/loginOut", "anon"); filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/front/**", "anon"); filterChainDefinitionMap.put("/api/**", "anon"); filterChainDefinitionMap.put("/kaptcha/**", "anon"); filterChainDefinitionMap.put("/success/**", "anon"); filterChainDefinitionMap.put("/admin/**", "authc"); filterChainDefinitionMap.put("/user/**", "authc"); //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }5.修改LoginController@Controller @RequestMapping public class LoginController { private Logger logger = Logger.getLogger(this.getClass().getName()); /** * 界面 * @return */ @RequestMapping(value = "/login", method = RequestMethod.GET) public String defaultLogin() { return "login"; } /** * 退出 * @return */ @RequestMapping(value = "/loginOut", method = RequestMethod.GET) public String loginOut() { Subject subject = SecurityUtils.getSubject(); subject.logout(); return "login"; } /** * 登录提交 * @param username * @param tryCode * @param password * @param redirectAttributes * @return */ @RequestMapping(value = "/login", method = RequestMethod.POST) public String login(@RequestParam("username") String username, @RequestParam("tryCode") String tryCode, @RequestParam("password") String password, RedirectAttributes redirectAttributes) { //判断验证码 if(StringUtils.isBlank(tryCode)){ logger.info("验证码为空了!"); redirectAttributes.addFlashAttribute("message", "验证码不能为空!"); return "redirect:login"; } Session session = SecurityUtils.getSubject().getSession(); String code = (String) session.getAttribute("rightCode"); System.out.println(code+"*************"+tryCode); if(!tryCode.equalsIgnoreCase(code)){ logger.info("验证码错误!"); redirectAttributes.addFlashAttribute("message", "验证码错误!"); return "redirect:login"; } // 从SecurityUtils里边创建一个 subject Subject subject = SecurityUtils.getSubject(); // 在认证提交前准备 token(令牌) UsernamePasswordToken token = new UsernamePasswordToken(username, password); String attributeValue = null; // 执行认证登陆 try { subject.login(token); } catch (UnknownAccountException uae) { attributeValue="未知账户!"; } catch (IncorrectCredentialsException ice) { attributeValue="密码不正确!"; } catch (LockedAccountException lae) { attributeValue= "账户已锁定"; } catch (ExcessiveAttemptsException eae) { attributeValue= "用户名或密码错误次数过多"; } catch (AuthenticationException ae) { attributeValue= "用户名或密码不正确!"; }finally { redirectAttributes.addFlashAttribute("message", attributeValue); if (subject.isAuthenticated()) { return "success"; } else { token.clear(); return "redirect:login"; } } } }6.界面6.1整合thymeleaf模板添加依赖;<!--thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>编辑application.ymlserver: port: 80 servlet: context-path: / #thymeleaf模板 spring: thymeleaf: cache: true prefix: classpath: /templates/ suffix: .html mode: HTML5 encoding: UTF-8 servlet: content-type: text/html 6.2创建login.html<!DOCTYPE html> <!-- thymeleaf 提示功能 --> <html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html"> <head lang="en"> <meta charset="UTF-8"></meta> <title>验证码</title> </head> <style type="text/css"> body { padding: 10px } </style> <body> <!-- 提示 --> <h3 th:text="${message}"></h3> <div> <!-- 后面添加参数起到清除缓存作用 --> <img alt="验证码" onclick="this.src='/kaptcha/defaultKaptcha?d='+new Date()*1" src="/kaptcha/defaultKaptcha" /> </div> <form action="/login" method="post"> 账户:<input type="text" name="username" /></br> 密码:<input type="text" name="password" /></br> 验证码:<input type="text" name="tryCode" /> <input type="submit" value="提交" ></input> </form> </body> </html> success.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"></meta> <title>成功</title> </head> <body> <h1>登录成功</h1> <a href="/loginOut">退出</a> </body> </html> 7. 实战演练 8.项目源码《源码下载》下篇介绍Spring Boot 整合 Shiro(四)thymeleaf模板权限控制 附源码
2021年12月08日
388 阅读
0 评论
0 点赞
2021-12-08
Spring Boot 整合 RabbitMQ(附源码)
一、前言RabbitMQ 的模式介绍可以看看笔者写的这篇文章《RabbitMQ 工作模式介绍》二、整合 RabbitMQ2.1 添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>2.2 添加配置spring.rabbitmq.host=192.168.2.101 spring.rabbitmq.port=5672 spring.rabbitmq.username=admin spring.rabbitmq.password=admin2.3 配置类AmqpConfirguration.javaimport org.springframework.amqp.core.*; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import java.util.logging.Logger; /** * 配置类: */ @Configuration public class AmqpConfirguration { private final Logger logger = Logger.getLogger(this.getClass().getName()); @Value("${spring.rabbitmq.host}") private String host; @Value("${spring.rabbitmq.port}") private int port; @Value("${spring.rabbitmq.username}") private String username; @Value("${spring.rabbitmq.password}") private String password; @Bean public ConnectionFactory connectionFactory() { CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host,port); connectionFactory.setUsername(username); connectionFactory.setPassword(password); connectionFactory.setVirtualHost("/"); connectionFactory.setPublisherConfirms(true); return connectionFactory; } @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //必须是prototype类型 public RabbitTemplate rabbitTemplate() { RabbitTemplate template = new RabbitTemplate(connectionFactory()); return template; } //=============简单、工作队列模式=============== public static final String SIMPLE_QUEUE = "simple_queue"; @Bean public Queue queue() { return new Queue(SIMPLE_QUEUE, true); } //===============发布/订阅模式============ public static final String PS_QUEUE_1 = "ps_queue_1"; public static final String PS_QUEUE_2 = "ps_queue_2"; public static final String FANOUT_EXCHANGE = "fanout_exchange"; @Bean public Queue psQueue1() { return new Queue(PS_QUEUE_1, true); } @Bean public Queue psQueue2() { return new Queue(PS_QUEUE_2, true); } @Bean public FanoutExchange fanoutExchange() { return new FanoutExchange(FANOUT_EXCHANGE); } @Bean public Binding fanoutBinding1() { return BindingBuilder.bind(psQueue1()).to(fanoutExchange()); } @Bean public Binding fanoutBinding2() { return BindingBuilder.bind(psQueue2()).to(fanoutExchange()); } //===============路由模式============ public static final String ROUTING_QUEUE_1 = "routing_queue_1"; public static final String ROUTING_QUEUE_2 = "routing_queue_2"; public static final String DIRECT_EXCHANGE = "direct_exchange"; @Bean public Queue routingQueue1() { return new Queue(ROUTING_QUEUE_1, true); } @Bean public Queue routingQueue2() { return new Queue(ROUTING_QUEUE_2, true); } @Bean public DirectExchange directExchange() { return new DirectExchange(DIRECT_EXCHANGE); } @Bean public Binding directBinding1() { return BindingBuilder.bind(routingQueue1()).to(directExchange()).with("user"); } @Bean public Binding directBinding2() { return BindingBuilder.bind(routingQueue2()).to(directExchange()).with("order"); } //===============主题模式============ public static final String TOPIC_QUEUE_1 = "topic_queue_1"; public static final String TOPIC_QUEUE_2 = "topic_queue_2"; public static final String TOPIC_QUEUE_3 = "topic_queue_3"; public static final String TOPIC_EXCHANGE = "topic_exchange"; @Bean public Queue topicQueue1() { return new Queue(TOPIC_QUEUE_1, true); } @Bean public Queue topicQueue2() { return new Queue(TOPIC_QUEUE_2, true); } @Bean public Queue topicQueue3() { return new Queue(TOPIC_QUEUE_3, true); } @Bean public TopicExchange topicExchange() { return new TopicExchange(TOPIC_EXCHANGE); } @Bean public Binding topicBinding1() { return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("user.add"); } @Bean public Binding topicBinding2() { return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("user.#"); } @Bean public Binding topicBinding3() { return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("test.#"); } }2.4 消息生产者AmqpSender.javaimport org.springframework.amqp.core.AmqpTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * 消息生产者 */ @Component public class AmqpSender { @Autowired private AmqpTemplate amqpTemplate; /** * 简单模式发送 * * @param message */ public void simpleSend(String message) { this.amqpTemplate.convertAndSend(AmqpConfirguration.SIMPLE_QUEUE, message); } /** * 发布/订阅模式发送 * * @param message */ public void psSend(String message) { this.amqpTemplate.convertAndSend(AmqpConfirguration.FANOUT_EXCHANGE, "", message); } /** * 路由模式发送 * * @param message */ public void routingSend(String routingKey, String message) { this.amqpTemplate.convertAndSend(AmqpConfirguration.DIRECT_EXCHANGE, routingKey, message); } /** * 主题模式发送 * * @param routingKey * @param message */ public void topicSend(String routingKey, String message) { this.amqpTemplate.convertAndSend(AmqpConfirguration.TOPIC_EXCHANGE, routingKey, message); } }2.5 消息消费者AmqpReceiver.javaimport org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; /** * 消息消费者 */ @Component public class AmqpReceiver { //使@RabbitListener 注解监听消息。 /** * 简单模式接收 * * @param message */ @RabbitListener(queues = AmqpConfirguration.SIMPLE_QUEUE) public void simpleReceive1(String message) { System.out.println("接收消息1:" + message); } @RabbitListener(queues = AmqpConfirguration.SIMPLE_QUEUE) public void simpleReceive2(String message) { System.out.println("接收消息2:" + message); } /** * 发布/订阅模式接收 * * @param message */ @RabbitListener(queues = AmqpConfirguration.PS_QUEUE_1) public void psReceive1(String message) { System.out.println(AmqpConfirguration.PS_QUEUE_1 + "接收消息:" + message); } @RabbitListener(queues = AmqpConfirguration.PS_QUEUE_2) public void psReceive2(String message) { System.out.println(AmqpConfirguration.PS_QUEUE_2 + "接收消息:" + message); } /** * 路由模式接收 * * @param message */ @RabbitListener(queues = AmqpConfirguration.ROUTING_QUEUE_1) public void routingReceive1(String message) { System.out.println(AmqpConfirguration.ROUTING_QUEUE_1 + "接收消息:" + message); } @RabbitListener(queues = AmqpConfirguration.ROUTING_QUEUE_2) public void routingReceive2(String message) { System.out.println(AmqpConfirguration.ROUTING_QUEUE_2 + "接收消息:" + message); } /** * 主题模式接收 * * @param message */ @RabbitListener(queues = AmqpConfirguration.TOPIC_QUEUE_1) public void topicReceive1(String message) { System.out.println(AmqpConfirguration.TOPIC_QUEUE_1 + "接收消息:" + message); } @RabbitListener(queues = AmqpConfirguration.TOPIC_QUEUE_2) public void topicReceive2(String message) { System.out.println(AmqpConfirguration.TOPIC_QUEUE_2 + "接收消息:" + message); } @RabbitListener(queues = AmqpConfirguration.TOPIC_QUEUE_3) public void topicReceive3(String message) { System.out.println(AmqpConfirguration.TOPIC_QUEUE_3 + "接收消息:" + message); } }2.6 测试类@RunWith(SpringRunner.class) @SpringBootTest public class RabbitmqApplicationTests { @Autowired private AmqpSender sender; @Test public void testSimpleSend() { //简单模式发送 for (int i = 1; i < 6; i++) { this.sender.simpleSend("test simpleSend " + i); } } @Test public void testPsSend() { //发布/订阅模式发送 for (int i = 1; i < 6; i++) { this.sender.psSend("test psSend " + i); } } @Test public void testRoutingSend() { //路由模式发送 for (int i = 1; i < 6; i++) { this.sender.routingSend("order", "test routingSend " + i); } } @Test public void testTopicSend() { //主题模式发送 for (int i = 1; i < 6; i++) { this.sender.topicSend("user.add", "test topicSend " + i); } } } 三、实战演练 3.1 简单模式与工作队列模式一个消息消费者:结果:两个消息消费者:结果:由两个消息消费者平均消费消息3.2 发布/订阅模式两个消息消费者:结果:所有消息消费者都能收到所有信息 3.3路由模式在AmqpConfirguration定义了“user”和"order",在路由上绑定了routingKey为“user”与“order”两个队列两个消息消费者: 测试:routingKey为“order” 可以看到只有routing_queue_2收到了消息,routing_queue_2的routingKey为“order”3.4 主题模式在路由上绑定了三个队列,routingKey分别为“user.add”、“user.#”、“test.#”3个消息消费者测试:routingKey为“user.add”可以发现topic_queue_1和topic_queue_2都接收到了消息,而topic_queue_3没有。因为主题模式中routingKey里的 符号 “#” 匹配一个或多个词,符号“*”匹配不多不少一个词。相当于模糊匹配,而topic_queue_3匹配不上三、项目源码源码下载
2021年12月08日
255 阅读
0 评论
0 点赞
2021-12-08
Spring Boot 整合Shiro(二)加密登录与密码加盐处理
该项目是根据上篇《Spring Boot 整合Shiro(一)登录认证和授权(附源码)》进行配置,若有不明白的请先查看上篇文章。1.加密工具用户注册时的密码用这个加密保存数据库 /** * 账户密码加密 * @param username * @param pwd * @return */ public static String MD5Pwd(String username, String pwd) { String hashAlgorithmName = "MD5";//加密方式 Object crdentials = pwd;//密码原值 ByteSource salt = ByteSource.Util.bytes(username);//以账号作为盐值 int hashIterations = 1024;//加密1024次 return new SimpleHash(hashAlgorithmName,crdentials,salt,hashIterations).toString(); }2.Shiro配置2.1修改ShiroConfig添加以下方法: /** * 加密配置 * @return */ @Bean(name = "credentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); // 散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 散列的次数,比如散列两次,相当于 md5(md5("")); hashedCredentialsMatcher.setHashIterations(1024); // storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码 hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); return hashedCredentialsMatcher; }修改customRealm: @Bean public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); // 告诉realm,使用credentialsMatcher加密算法类来验证密文 customRealm.setCredentialsMatcher(hashedCredentialsMatcher()); customRealm.setCachingEnabled(false); return customRealm; }2.2修改CustomRealm当中“password”参数值是用户注册时使用加密工具生产保存的密码,可在CustomRealm中配置UserService。/** * 身份认证 * 这里可以注入userService,为了方便演示直接写死账户和密码 * 获取即将需要认证的信息 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("-------身份认证方法--------"); String userName = (String) authenticationToken.getPrincipal(); // String userPwd = new String((char[]) authenticationToken.getCredentials()); //根据用户名从数据库获取密码 String password = "89267a06ce552c28e3edc11be28e4f80";// 使用账户和明文密码:123加密后 // if (userName == null) { // throw new AccountException("用户名不正确"); // } else if (!userPwd.equals(password )) { // throw new AccountException("密码不正确"); // } //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配 ByteSource salt = ByteSource.Util.bytes(userName); return new SimpleAuthenticationInfo(userName, password, salt, getName()); }3.实战演练使用账户:aa和密码:123 进行测试,可以看到提示登录成功。 之前的密码是根据账户和密码进行处理的,要注意的是注册的加密方式和设置的加密方式还有Realm中身份认证的方式都是要一模一样的 下篇介绍《Spring Boot 整合 Shiro(三)Kaptcha验证码》
2021年12月08日
363 阅读
0 评论
0 点赞
2021-12-08
Spring Boot 整合Shiro(一)登录认证和授权(附源码)
shiro Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。主要功能三个核心组件:Subject, SecurityManager 和 Realms.Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。 Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。 SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。 Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。 从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。 Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。 1.项目版本Spring Boot 2.x shiro 1.3.21.1导入依赖 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>2.类配置2.1 ShiroConfig相当于之前的xml配置,包括:过滤的文件和权限,密码加密的算法,其用注解等相关功能。import com.shiro.realm.CustomRealm; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; 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.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import java.util.LinkedHashMap; import java.util.Map; /** * 过滤的文件和权限,密码加密的算法,其用注解等相关功能 */ @Configuration public class ShiroConfig { @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // Shiro的核心安全接口,这个属性是必须的 shiroFilterFactoryBean.setSecurityManager(securityManager); // 身份认证失败,则跳转到登录页面的配置 shiroFilterFactoryBean.setLoginUrl("/login"); // 权限认证失败,则跳转到指定页面 shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/front/**", "anon"); filterChainDefinitionMap.put("/api/**", "anon"); filterChainDefinitionMap.put("/admin/**", "authc"); filterChainDefinitionMap.put("/user/**", "authc"); //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); defaultSecurityManager.setRealm(customRealm()); return defaultSecurityManager; } @Bean public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); return customRealm; } }2.1.1 shiroFilter方法shiro的过滤器,可以设置登录页面(setLoginUrl)、权限不足跳转页面(setUnauthorizedUrl)、具体某些页面的权限控制或者身份认证。注意:这里是需要设置SecurityManager(setSecurityManager)。默认的过滤器还有:anno、authc、authcBasic、logout、noSessionCreation、perms、port、rest、roles、ssl、user过滤器。具体的大家可以查看package org.apache.shiro.web.filter.mgt.DefaultFilter。这个类,常用的也就authc、anno。2.1.2 securityManager 方法public interface SecurityManager extends Authenticator, Authorizer, SessionManager { //登录方法 Subject login(Subject subject, AuthenticationToken authenticationToken) throws AuthenticationException; //注销方法 void logout(Subject subject); //创建subject Subject createSubject(SubjectContext context); }由于项目是一个web项目,所以我们使用的是DefaultWebSecurityManager ,然后设置自己的Realm。2.1.3 CustomRealm 方法将 customRealm的实例化交给spring去管理,当然这里也可以利用注解的方式去注入 2.2 CustomRealm自定义的CustomRealm继承AuthorizingRealm。并且重写父类中的doGetAuthorizationInfo(权限相关)、doGetAuthenticationInfo(身份认证)这两个方法。import org.apache.shiro.SecurityUtils; 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.subject.PrincipalCollection; import java.util.HashSet; import java.util.Set; /** * */ public class CustomRealm extends AuthorizingRealm { /** * 权限相关 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //账户 String username = (String) SecurityUtils.getSubject().getPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //从数据库获取账户权限信息 Set<String> stringSet = new HashSet<>(); stringSet.add("user:show"); stringSet.add("user:admin"); info.setStringPermissions(stringSet); return info; } /** * 身份认证 * 这里可以注入userService,为了方便演示直接写死账户和密码 * 获取即将需要认证的信息 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("-------身份认证方法--------"); String userName = (String) authenticationToken.getPrincipal(); String userPwd = new String((char[]) authenticationToken.getCredentials()); System.out.println(userPwd); //根据用户名从数据库获取密码 String password = "123"; if (userName == null) { throw new AccountException("用户名不正确"); } else if (!userPwd.equals(password )) { throw new AccountException("密码不正确"); } return new SimpleAuthenticationInfo(userName, password,getName()); } } 自定义的Realm类继承AuthorizingRealm类,并且重载doGetAuthorizationInfo和doGetAuthenticationInfo两个方法。doGetAuthorizationInfo: 权限认证,即登录过后,每个身份不一定,对应的所能看的页面也不一样。doGetAuthenticationInfo:身份认证。即登录通过账号和密码验证登陆人的身份信息。 3.实战演练3.1 登陆认证创建LoginControllerimport org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.subject.Subject; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping public class LoginController { @RequestMapping(value = "/login", method = RequestMethod.GET) public String defaultLogin() { return "首页"; } @RequestMapping(value = "/login", method = RequestMethod.POST) public String 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 "未知账户"; } catch (IncorrectCredentialsException ice) { return "密码不正确"; } catch (LockedAccountException lae) { return "账户已锁定"; } catch (ExcessiveAttemptsException eae) { return "用户名或密码错误次数过多"; } catch (AuthenticationException ae) { return "用户名或密码不正确!"; } if (subject.isAuthenticated()) { return "登录成功"; } else { token.clear(); return "登录失败"; } } }测试结果:3.2 权限测试在ShiroConfig添加如下代码,开启注解。@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() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; }创建UserControllerimport com.shiro.utils.BaseController; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RequestMapping("/user") @RestController public class UserController extends BaseController { @RequiresPermissions("user:list") @RequestMapping("/show") public String showUser() { return "张三信息"; } }再创建 BaseController,UserController继承BaseController,用于捕获没有权限时的异常import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.UnauthorizedException; import org.springframework.web.bind.annotation.ExceptionHandler; import java.util.HashMap; import java.util.Map; public class BaseController { /** * 捕获没有权限时的异常 * @return */ @ExceptionHandler({ UnauthorizedException.class, AuthorizationException.class }) public Map<String, Object> authorizationException(){ Map<String, Object> map = new HashMap<String, Object>(); System.out.println("没有权限"); map.put("success", true); map.put("msg", "当前用户没有此权限"); return map; } } 运行测试,先执行 http://127.0.0.1:8080/login,再执行http://127.0.0.1:8080/user/show:方法上是 @RequiresPermissions("user:list"),之前在customRealm只添加了两个所以没有权限现在把方法上的改为@RequiresPermissions("user:show")测试现在就有权限访问数据啦4.项目源码项目地址 下篇介绍《Spring Boot 整合Shiro(二)加密登录与密码加盐处理》
2021年12月08日
425 阅读
0 评论
0 点赞
2021-12-08
Spring Cloud 整合 Bus(附源码)
一、前言本篇笔者是根据上篇进行修改,若有不懂,转《Spring Cloud 入门 之 Config(六)附源码》了解二、介绍Spring Cloud Bus 是 Spring Cloud 家族中的一个子项目,用于实现微服务之间的通信。它整合 Java 的事件处理机制和消息中间件消息的发送和接受,主要由发送端、接收端和事件组成。针对不同的业务需求,可以设置不同的事件,发送端发送事件,接收端接受相应的事件,并进行相应的处理。三、配置3.1config-server3.1.1添加依赖: <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> <version>2.1.0.RELEASE</version> </dependency> 3.1.2 修改 application.yml:添加了 rabbitmq 配置和 management 的配置server: port: 10000spring: application: name: CONFIG cloud: config: server: git: uri: https://github.com/Uncle-LiuY/config.git username: ****** password: ****** rabbitmq: host: 127.0.0.0 port: 5672 username: admin password: admin management: endpoints: web: exposure: include: '*' # 暴露接口 eureka: instance: instance-id: config-api client: service-url: defaultZone: http://localhost:9000/eureka/ # 注册中心访问地址 3.2 api3.2.1 添加依赖; <!-- bus --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> <version>2.1.0.RELEASE</version> </dependency> 3.2.2 修改 bootstrap.yml:添加 rabbitmq 配置spring: application: name: api cloud: config: discovery: enabled: true service-id: CONFIG # config-server 在注册中心的名称 profile: dev # 指定配置文件的环境 rabbitmq: host: 127.0.0.0 port: 5672 username: admin password: admin eureka: client: service-url: defaultZone: http://localhost:9000/eureka/ # 注册中心访问地址 3.2.3 修改TestController添加 @RefreshScope 注解,顺序一定不能错@RestController@RequestMapping("/test")@RefreshScopepublic class TestController { @Value("${env}") private String env; // 从配置中心获取 @RequestMapping("/getConfigInfo") public String getConfigInfo() { return env; } } 4.测试依次启动 eureka-server、config-server、api4.1 参数原来的值:4.2 修改文件值为:dev1234.3 POST执行http://localhost:10000/actuator/bus-refresh间隔几秒再执行 http://localhost/test/getConfigInfo 就会发现获取的值变了。5.项目地址源码下载 到这里还是需要手动调用才能更新,下篇《Spring Cloud 集成 WebHook》讲解在线更新
2021年12月08日
182 阅读
0 评论
0 点赞
2021-12-08
Spring Cloud 集成 WebHook
1.配置 WebHook 地址登录 GitHub,点击 GitHub 的 WebHook 菜单,右侧面板中 Payload URL 填写 <配置中心 url>/actuator/bus-refresh, Content-type 选择 applicaton/json,保存即可。因为需要用到外网,这里使用natapp外网穿透做外网映射设置 WebHook 操作: 2.修改config-server添加:import org.springframework.stereotype.Component;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletRequestWrapper;import java.io.ByteArrayInputStream;import java.io.IOException;@Componentpublic class WebHookFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String url = new String(httpServletRequest.getRequestURI()); // 只过滤 /actuator/bus-refresh 请求 if (!url.endsWith("/actuator/bus-refresh")) { chain.doFilter(request, response); return; } // 使用 HttpServletRequest 包装原始请求达到修改 post 请求中 body 内容的目的 CustometRequestWrapper requestWrapper = new CustometRequestWrapper(httpServletRequest); chain.doFilter(requestWrapper, response); } @Override public void destroy() { } private class CustometRequestWrapper extends HttpServletRequestWrapper { public CustometRequestWrapper(HttpServletRequest request) { super(request); } @Override public ServletInputStream getInputStream() throws IOException { byte[] bytes = new byte[0]; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); return new ServletInputStream() { @Override public boolean isFinished() { return byteArrayInputStream.read() == -1 ? true : false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; } } } 3.测试 完成以上配置后,重启项目 修改github文件 再执行 http://localhost/test/getConfigInfo 因为网络原因,等几秒钟加载几次就能拉去新的数据了
2021年12月08日
176 阅读
0 评论
0 点赞
2021-12-08
java环境变量配置(java和javac命令可用)
第一步第二步在系统变量中新建JAVA_HOME第三步**如果有CLASSPATH则在此变量里添加,没有则创建CLASSPATH变量在变量里添加.;%JAVA_HOME%libdt.jar;%JAVA_HOME%libtools.jar; 一个符号都不能少**第四步在path变量中添加D:APPJavajdk1.8.0_144bin 红色部位为安装路径注:每次配置为之后记得确定保存操作。重新使用win+r打开命令窗口至于“java -version”显示安装成功,这并不能说明什么。在页面内输入java,回车,如果出来的是这些中文,那说明这是正确的在接下来输入javac,回车,如果出现这些中文,说明是环境变量配好了(java是跨平台的语言,在Windows、Linux等多种操作系统平台上都预装有java的运行环境JRE,在它的bin目录下就有java命令(用于运行Java程序),但没有javac命令(用于编译Java程序)。也就是说,即使你不安装JDK,一样可以使用java命令,但不能直接使用javac,除非安装jdk,并设置path环境变量)
2021年12月08日
254 阅读
0 评论
0 点赞
1
...
23
24
25