SSM项目集成Spring Security 4.X版本(使用spring-security.xml 配置文件方式)
目录
前言
实战开发:
一、Spring Security整合到SSM项目
1. pom文件引入包
2. web.xml 配置
3. 添加 spring-security.xml 文件
二、Spring Security实战应用
1. 项目结构
2. pom文件引入
3. web.xml 配置
4. Spring 配置 applicationContext.xml
5. spring-security.xml 配置
6. springmvc.xml 配置
7. 创建实体类
8. DAO层实现数据查询
9. SystemDao 接口编写(数据层接口类)
10. SystemService接口及实现类SystemServiceImpl编写
11. SystemController 控制器编写
12. SpringSecurity实战讲解
13. 运行项目查看效果
前言
实战前提条件:
基础的SSM项目已集成完毕。在此基础上集成Spring Security实现web项目的安全保护 。
本文版本说明:
JDK:1.8
spring.version:5.2.12.RELEASE
Spring Security.version:4.2.5.RELEASE
Spring Security标签库:4.2.3.RELEASE
实战目标:
Authentication:认证,实现用户认证登录
Authorization:授权,设定用户的资源,访问权限。
实战开发:
一、Spring Security整合到SSM项目
1. pom文件引入包
<!-- Spring Security,此处引入4.2.5.RELEASE版本。因为spring security 5.X版本需要提供一个PasswordEncorder的实例,否则后台会报错。当然你也可以提供PasswordEncorder的实例 使用5.X版本--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId><version>4.2.5.RELEASE</version></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId><version>4.2.5.RELEASE</version></dependency><!-- Spring Security 标签库--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-taglibs</artifactId><version>4.2.3.RELEASE</version></dependency>
2. web.xml 配置
注:先说说web.xml配置文件中Spring家族的加载顺序。
先启动spring ioc容器 --> 再启动spring-security --> 然后启动springmvc
<!-- https://blog.csdn.net/qyb19970829/article/details/110100544 配置时注意关于spring容器加载顺序的问题,applicationContext.xml,spring-security.xml,springmvc.xml 即这几个配置文件加载顺序--><!-- SpringSecurity过滤器链 --><filter><filter-name>springSecurityFilterChain</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>springSecurityFilterChain</filter-name><url-pattern>/*</url-pattern></filter-mapping><!--配置Spring的监听器,启动spring容器--><display-name>Archetype Created Web Application</display-name><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--配置加载类路径的配置文件,注意加载顺序 先加载spring--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/applicationContext.xml <!-- Spring配置文件 -->classpath:spring/spring-security.xml <!-- SpringSecurity配置文件 --></param-value></context-param>
注:本项目完整的web.xml配置稍后附上
3. 添加 spring-security.xml 文件
注:本文件使用form-login的方式进行认证,在项目resources文件下新建spring文件夹(如果没有的话)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:security="http://www.springframework.org/schema/security"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security.xsd"><!--spring过滤器链配置1) 需要拦截什么资源2) 什么资源对应什么角色权限3) 定制认证方式: HttpBasic or FormLogin4) 自定义登录页面,定义登录请求地址,定义错误处理方式--><security:http><!-- 使用http-basic的方式进行认证 --><!--<security:http-basic/>--><!-- 使用form-login的方式进行认证 --><security:form-login/><!-- 配置资源拦截规则pattern属性指定资源目录: 即需要拦截的资源 /* 代表根目录下的一级目录 /** 代表根目录下的所有目录access(SpEL)方法执行Spring EL表达式。提供如下表达式:permitALL():设置那些路径可以直接访问,不需要认证。直接返回trueisAnonymous():只有匿名用户可以访问,登录用户不可访问isAuthenticated():需要身份认证成功才能访问。如果认证用户不是匿名用户,则返回true,认证通过isFullyAuthenticated():需要身份认证成功才能访问。如果认证用户不是匿名用户或记住我的用户,则返回true,认证通过其它自行查找......--><security:intercept-url pattern="/**" access="isAuthenticated()"/></security:http><!--身份验证管理器--><security:authentication-manager><!--自定义授权提供源,实际开发中提供 自定义用户详情查询获取接口--><security:authentication-provider><security:user-service><security:user name="admin" password="123456" authorities="ROLE_USER"/></security:user-service></security:authentication-provider></security:authentication-manager></beans>
以上为spring-security.xml 的基础配置,到此Spring Security整合到SSM项目中已经完毕!运行项目后,所有资源会被拦截,跳转到默认登录页要求用户进行登录认证后才能访问项目资源。如图:

二、Spring Security实战应用
1. 项目结构


2. pom文件引入
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.wqbr</groupId><artifactId>wqdemotwo</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><name>wqdemotwo Maven Webapp</name><!-- FIXME change it to the project's website --><url>http://www.example.com</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><spring.version>5.2.12.RELEASE</spring.version></properties><dependencies><!--spring 包--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.6.8</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><!-- Spring Security,此处引入4.2.5.RELEASE版本。因为spring security 5.X版本需要提供一个PasswordEncorder的实例,否则后台会报错。当然你也可以提供PasswordEncorder的实例 使用5.X版本--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId><version>4.2.5.RELEASE</version></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId><version>4.2.5.RELEASE</version></dependency><!-- Spring Security 标签库--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-taglibs</artifactId><version>4.2.3.RELEASE</version></dependency><!-- 引入jackson依赖包--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.4</version></dependency><!--JSP(Java Server Pages,Java服务端页面)--><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.0</version><scope>provided</scope></dependency><!-- JSTL标准标签库(Jsp Standarded Tag Library),使用标签取代JSP里的JAVA代码 --><dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><!--servlet API--><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope></dependency><!-- junit测试包 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><!--mybatis 相关包--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.5</version></dependency><dependency><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-core</artifactId><version>1.3.5</version></dependency><!--mybatis和spring集成的依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.7</version></dependency><!--oracle JDBC连接依赖--><dependency><groupId>com.oracle.database.jdbc</groupId><artifactId>ojdbc8</artifactId><version>21.3.0.0</version></dependency><dependency><groupId>cn.easyproject</groupId><artifactId>orai18n</artifactId><version>12.1.0.2.0</version></dependency><!--阿里的连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.12</version></dependency></dependencies><build><finalName>wqdemotwo</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-source-plugin</artifactId><version>2.1.1</version><executions><execution><id>attach-sources</id><goals><goal>jar-no-fork</goal></goals></execution></executions><configuration><attach>true</attach></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-war-plugin</artifactId><version>2.6</version><configuration><includeEmptyDirectories>true</includeEmptyDirectories></configuration></plugin></plugins></build>
</project>
3. web.xml 配置
配置web.xml,加载spring(applicationContext.xml --spring默认配置文件),加载spring-security,加载springmvc。
<?xml version="1.0" encoding="UTF-8"?><web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<!-- https://blog.csdn.net/qyb19970829/article/details/110100544 配置时注意关于spring容器加载顺序的问题,applicationContext.xml,spring-security.xml,springmvc.xml 即这几个配置文件加载顺序--><!--字符编码过滤器一定要放在前面--><filter><filter-name>characterEncodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><param-name>forceEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>characterEncodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!--不拦截所有是html的页面请求,weblogic中部署去掉<servlet-mapping><servlet-name>default</servlet-name><url-pattern>*.html</url-pattern></servlet-mapping>--><!-- SpringSecurity过滤器链 --><filter><filter-name>springSecurityFilterChain</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>springSecurityFilterChain</filter-name><url-pattern>/*</url-pattern></filter-mapping><!--配置Spring的监听器,启动spring容器--><!--配置加载类路径的配置文件,注意加载顺序--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring/applicationContext.xmlclasspath:spring/spring-security.xml</param-value></context-param><display-name>Archetype Created Web Application</display-name><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--配置前端控制器,对浏览器发送的请求进行统一处理--><servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!--加载springmvc.xml配置文件的位置和名称,配置的是Spring配置--><init-param><!--contextConfigLocation:上下文配置路径,固定值--><param-name>contextConfigLocation</param-name><!--classpath:类路径,指的是Java和resources文件夹--><!--springmvc.xml:指的是配置文件的名称:需要配置springmvc.xml,在下面。spring默认配置文件为applicationContext.xml。当中配置spring创建容器时要扫描的包 已经整合到springmvc.xml中--><param-value>classpath:spring/springmvc.xml</param-value></init-param><!--配置启动加载--><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>dispatcherServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping><!--开启项目时打开的页面-->
<!-- <welcome-file-list><welcome-file>/index.html</welcome-file></welcome-file-list>--></web-app>
4. Spring 配置 applicationContext.xml
指定扫描包,数据源,整合集成接管mybatis。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.1.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.1.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"><!--配置spring创建容器时要扫描的包--><!--同时也是 MyBatis托管的包路径--><!-- 扫描除了controller的所有bean 这里一定要 use-default-filters="true"--><context:component-scan base-package="com.wqbr" use-default-filters="true"><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan><!-- 引入配置文件--><bean id="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="location" value="classpath:spring/jdbc.properties" /></bean><!--创建数据源 --><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${driver}"/><property name="url" value="${url}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></bean><!--<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"><property name="jndiName"><value>testJNDI</value></property></bean>--><!--创建sqlSessionFactory,接管了mybatis配置文件。整合Mybatis--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource" /><property name="mapperLocations" ><array><!--映射在class编译路径下的TblsicardDao.xml全路径--><value>classpath:mapping/SystemDao.xml</value></array></property></bean><!--创建DAO,扫描mybatis接口的实现,加入到ioc容器中--><bean id="systemDao" class="org.mybatis.spring.mapper.MapperFactoryBean"><property name="sqlSessionFactory" ref="sqlSessionFactory"/><property name="mapperInterface" value="com.wqbr.persistence.SystemDao"/></bean></beans>
5. spring-security.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:security="http://www.springframework.org/schema/security"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security.xsd"><!--spring过滤器链配置1) 需要拦截什么资源2) 什么资源对应什么角色权限3) 定制认证方式: HttpBasic or FormLogin4) 自定义登录页面,定义登录请求地址,定义错误处理方式--><security:http><!--使用form-login的方式进行认证login-page:指定获取登录页面的url(需要编写controller返回登录页面)login-processing-url:指定登录页面中post请求提交到哪里的url(不需要编写controller,框架已实现)default-target-url:指定登录成功后,跳转到哪个url(需要编写controller)authentication-success-handler-ref:指定登录成功后,由哪个类来进行处理authentication-failure-handler-ref:指定登录失败后,由哪个类来进行处理username-parameter:指定登录表单中用户名的input中name值,如果这里不配置,则默认为usernamepassword-parameter:指定登录表单中密码的input中name值,如果这里不配置,则默认为password--><security:form-login login-page="/login" login-processing-url="/spring_security_check"authentication-success-handler-ref="myAuthenticationSuccessHandler"authentication-failure-handler-ref="myAuthenticationFailureHandler"/><!-- 关闭csrf的保护--><security:csrf disabled="true"/><!-- 配置资源拦截规则pattern属性指定资源目录: 即需要拦截的资源 /* 代表根目录下的一级目录 /** 代表根目录下的所有目录access(SpEL)方法执行Spring EL表达式。提供如下表达式:permitALL():设置那些路径可以直接访问,不需要认证。直接返回trueisAnonymous():只有匿名用户可以访问,登录用户不可访问isAuthenticated():需要身份认证成功才能访问。如果认证用户不是匿名用户,则返回true,认证通过isFullyAuthenticated():需要身份认证成功才能访问。如果认证用户不是匿名用户或记住我的用户,则返回true,认证通过其它自行查找......--><!--开始配置拦截规则,注意拦截规则的位置顺序(如不需要身份认证的规则,要放在前面,需要身份认证的规则放在后面)--><!--permitAll()不需要身份认证,无条件放行--><security:intercept-url pattern="/login" access="permitAll()"/><security:intercept-url pattern="/system/index" access="permitAll()"/><!--进行权限划分:hasRole('ROLE_USER'):表示拥有 ROLE_USER 权限的用户可以访问hasRole('ROLE_ALL'):表示拥有 ROLE_ALL 权限的用户可以访问--><security:intercept-url pattern="/system/add" access="hasAuthority('admin')"/><security:intercept-url pattern="/system/list" access="hasAuthority('ROLE_ALL')"/><!--permitAll()不需要身份认证,无条件放行静态资源--><security:intercept-url pattern="/js/**" access="permitAll()"/><!--拦截所有页面,需要身份认证成功才能访问。如果认证用户不是匿名用户或记住我的用户,则返回true,认证通过--><security:intercept-url pattern="/**" access="isFullyAuthenticated()"/><!--结束配置拦截规则--><!-- 自定义用户访问权限不足的处理方式(需要编写controller返回权限不足的页面) --><security:access-denied-handler error-page="/accessDeny"/><!--加上Remember Me功能,token-validity-seconds:有效时间(秒)--><!--<security:remember-me token-repository-ref="jdbcTokenRepository" token-validity-seconds="604800"/>--><!--<security:logout/>:注销功能logout-url="/logout":springSecurity内LogoutFilter要拦截的url(向这个url发送请求来注销)logout-success-url:用户退出后要被重定向的urlinvalidate-session:默认为true,用户在退出后Http session失效success-handler-ref:指定一个bean(需要实现LogoutSuccessHandler接口),用来自定义退出成功后的操作--><security:logout logout-url="/logout" logout-success-url="/login" invalidate-session="true"/></security:http><!--身份验证管理器--><security:authentication-manager><!--自定义授权提供类MyUserDetailsService,获得登录用户的用户详情信息。此类实现UserDetailsService接口。user-service-ref="myUserDetailsService" : 指定 UserDetailsService 接口的实现类最终都要返回一个UserDetail,用户详情--><security:authentication-provider user-service-ref="myUserDetailsService"><!-- 配置:加密算法对用户输入的密码进行加密,然后和数据库的密码进行配对 --><!--<security:password-encoder ref="bCryptPasswordEncoder"/>--></security:authentication-provider></security:authentication-manager><!--创建 springSecurity 密码加密工具类,使用PasswordEncoder 接口的实现,也可以使用别的--><!--<bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>--><!--springSecurity实现 remember me 功能:如果用户登录选择 remember me ,springSecurity会将其cookie值存入数据库,来实现remember me 功能JdbcTokenRepositoryImpl 用来存取cookie值--><!--<bean id="jdbcTokenRepository" class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl"><property name="dataSource" ref="dataSource"/> <!–数据库数据源–><!–<property name="createTableOnStartup" value="true"/>–> <!–createTableOnStartup属性是当项目启动时,springSecurity创建表存储remember me相关信息,第二次启动时要注释这个属性–></bean>--></beans>
6. springmvc.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"><!--配置spring创建容器时要扫描的包--><!-- 禁用默认扫描规则,use-default-filters="false"--><context:component-scan base-package="com.wqbr" use-default-filters="false"><!--只扫描Controller注解的类--><context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/></context:component-scan><!--处理映射器--><bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/><!--处理器适配器--><bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/><!--配置JSP视图解析器--><bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/"></property> <!--规定跳转页面路径的前缀--><property name="suffix" value=".jsp"></property> <!--规定跳转页面的后缀--></bean><!-- 配置spring开启注解mvc的支持 默认就是开启的 ,要想让其他组件(不包含映射器、适配器、处理器)生效就必须需要配置了--><mvc:annotation-driven/><!-- 让默认servlet处理静态资源。将springMVC不能处理的请求交给servlet,一般用来放行静态资源 --><mvc:default-servlet-handler/></beans>
7. 创建实体类
注:重点在SysUser实体类。实现UserDetails接口 复写接口的方法进行实现,建立各方法的对应属性到用户表中(不一定全建对应属性)。
我们先来看下UserDetails接口类的源码:

红色标注的3项建立在用户实体领域类中,如下SysUser 用户实体类代码:
package com.wqbr.domain;import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;/*** 系统用户,封装用户数据,实现 UserDetails 接口* @author lv* @date 2024年1月11日*/
public class SysUser implements UserDetails {private static final long serialVersionUID = 1L;private String id;private String username; //从UserDetails的重写方法中返回private String password; //从UserDetails的重写方法中返回private Date addtime;private boolean accountnonexpired; //账户是否过期,从UserDetails的重写方法中返回private boolean accountnonlocked; //账户是否锁定,从UserDetails的重写方法中返回private boolean credentialsnonexpired; //密码是否过期,从UserDetails的重写方法中返回private boolean enabled; //账户是否可用,从UserDetails的重写方法中返回// 储存用户拥有的所有权限private List<GrantedAuthority> authorities = new ArrayList<>(); //从UserDetails的重写方法中返回public String getId() {return id;}public void setId(String id) {this.id = id;}public void setUsername(String username) {this.username = username;}public void setPassword(String password) {this.password = password;}public Date getAddtime() {return addtime;}public void setAddtime(Date addtime) {this.addtime = addtime;}// 返回用户权限,上面声明了权限集合对象 authorities@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}public void setAuthorities(List<GrantedAuthority> authorities) {this.authorities = authorities;}// 返回用户密码,上面声明了属性 password@Overridepublic String getPassword() {return password;}// 返回用户名,上面声明了属性 username@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return accountnonexpired;}public void setAccountnonexpired(boolean accountnonexpired) {this.accountnonexpired = accountnonexpired;}@Overridepublic boolean isAccountNonLocked() {return accountnonlocked;}public void setAccountnonlocked(boolean accountnonlocked) {this.accountnonlocked = accountnonlocked;}@Overridepublic boolean isCredentialsNonExpired() {return credentialsnonexpired;}public void setCredentialsnonexpired(boolean credentialsnonexpired) {this.credentialsnonexpired = credentialsnonexpired;}@Overridepublic boolean isEnabled() {return enabled;}public void setEnabled(boolean enabled) {this.enabled = enabled;}
}
注:private List<GrantedAuthority> authorities = new ArrayList<>(); 此属性稍后赋值演示
角色(SysRole )和资源(SysPermission)实体类代码参见以下文章建立: spirng框架之spring security(二)insert 语句补充-CSDN博客https://blog.csdn.net/u011529483/article/details/135467110?spm=1001.2014.3001.5501
8. DAO层实现数据查询
SystemDao.xml,mybatis的mapper文件(映射SQL语句)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间映射到com.wqbr.persistence.SystemDao 类-->
<mapper namespace="com.wqbr.persistence.SystemDao"><!--SysUser findByUsername(String username);方法的映射对应id="findByUsername",resultType返回类型为SysUser实体类--><select id="findByUsername" parameterType="String" resultType="com.wqbr.domain.SysUser">select *from SYS_USERwhere USERNAME = #{username}</select><!--查询当前用户拥有的资源--><select id="findPermissionByUsername" parameterType="String" resultType="com.wqbr.domain.SysPermission">select d.*from sys_user a, sys_user_role b, sys_role_permission c, sys_permission dwhere a.id = b.user_id and b.role_id = c.role_id and c.permission_id = d.idand a.username = #{username}</select>
</mapper>
9. SystemDao 接口编写(数据层接口类)
package com.wqbr.persistence;import com.wqbr.domain.SysPermission;
import com.wqbr.domain.SysUser;import java.util.List;public interface SystemDao {/*** 查询当前用户对象*/public SysUser findByUsername(String username);/*** 查询当前用户拥有的资源*/public List<SysPermission> findPermissionByUsername(String username);
}
10. SystemService接口及实现类SystemServiceImpl编写
package com.wqbr.service;import com.wqbr.domain.SysPermission;import java.util.List;/*** 系统服务接口* @author lv* @date 2024年1月11日*/
public interface SystemService {/*** 查询当前用户拥有的资源*/public List<SysPermission> findPermissionByUsername(String username);
}
package com.wqbr.service.impl;import com.wqbr.domain.SysPermission;
import com.wqbr.persistence.SystemDao;
import com.wqbr.service.SystemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/*** 系统服务接口实现* @author lv* @date 2024年1月11日*/
@Service
public class SystemServiceImpl implements SystemService {@Autowiredprivate SystemDao systemDao;@Overridepublic List<SysPermission> findPermissionByUsername(String username) {return systemDao.findPermissionByUsername(username);}
}
11. SystemController 控制器编写
package com.wqbr.controller;import com.wqbr.domain.Menus;
import com.wqbr.domain.SysPermission;
import com.wqbr.domain.SysUser;
import com.wqbr.service.SystemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;/*** 系统用户控制器* @author lv* @date 2024年1月11日*/
@Controller
@RequestMapping("/system")
public class SystemController {/*** 自动装配SystemService接口*/@Autowiredprivate SystemService systemService;/*** 处理超链接发送出来的请求* @param model* @return*/@RequestMapping(path = "/hello")public String sayHello(Model model){System.out.println("入门方法执行了2...");// 配置了视图解析器后,写法return "main/index";}@RequestMapping(path = "/haa")public String haa(Model model){System.out.println("haa *****bb 2 999999999999999999...");// 向模型中添加属性msg与值,可以在html页面中取出并渲染//model.addAttribute("msg","hello,SpringMVC");// 配置了视图解析器后,写法return "main/index";}@RequestMapping(path = "/index")public String index(){System.out.println("index 页面进入......");return "main/index";}@RequestMapping(path = "/list")public String list(){System.out.println("list方法进入......");return "main/list";}@RequestMapping(path = "/add")public String add(){System.out.println("add方法进入......");return "main/add";}@GetMapping("/findMenu")public ModelAndView findMenus(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {ModelAndView model = new ModelAndView("main/menu");SysUser user = (SysUser) authentication.getPrincipal();String username=user.getUsername();if(username!=null){List<Menus> listMenu = new ArrayList<>();List<SysPermission> pList = systemService.findPermissionByUsername(username);System.out.println("=-----=大小为:"+pList.size());for (SysPermission permission : pList) {if (permission.getResource_type().equals("menu")) {Menus menu = new Menus();menu.setId(Long.parseLong(permission.getId()));menu.setName(permission.getName());menu.setParentId(Long.parseLong(permission.getParent_id()));menu.setParentIds(Long.parseLong(permission.getParent_ids()));menu.setUrl(permission.getUrl());listMenu.add(menu);}}//request.setAttribute("listMenus", listMenu);model.addObject("listMenu",listMenu);// for (Menus menus : listMenu) {
// if (menus.getParentId() == 10000) { //10000为数据库中的值
// System.out.println("==" + menus.getName() + "[" + menus.getUrl() + "]");
// for (Menus menusch : listMenu) {
// if (menus.getId() == menusch.getParentId()) {
// System.out.println("---------" + menusch.getName() + "[" + menusch.getUrl() + "]");
// }
// }
// }
// }}return model;}
}
12. SpringSecurity实战讲解
现在请查看之前配置的spring-security.xml文件。


如图 spring-security.xml 文件中给出了 security:form-login 的4个属性。并禁用了 csrf 。且指定了 error-page 的路径。所以需要编写 Controller 实现 login-page 及 error-page。
- LoginController 控制器编写
package com.wqbr.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;/*** 系统用户控制器* @author lv* @date 2024年1月16日*/
@Controller
public class LoginController {@RequestMapping("/login")public String login(){System.out.println("初始 指定 进入登录页面!。。。。。。。。。。。。。。");return "login";}/*** 自定义用户访问权限不足的处理方式(需要编写controller返回权限不足的页面)* @return*/@RequestMapping("/accessDeny")public String accessDeny(){System.out.println("自定义用户访问权限不足的处理方式(需要编写controller返回权限不足的页面)。。。。。。。。。。。。。。");return "accessdeny";}
}
- 编写 MyAuthenticationSuccessHandler 与 MyAuthenticationFailureHandler 类实现spring-security.xml 文件中 authentication-success-handler-ref 与 authentication-failure-handler-ref 属性指定的接口。(以json格式返回成功或失败)
package com.wqbr.service.impl;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Service;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@Service
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {// new 一个 jackson 的 对象private ObjectMapper objectMapper = new ObjectMapper();/*** 此方法会在登录成功后进行回调** @param authentication:表示认证成功后的信息*/@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {// 自己构造json字符串,返回给前端Map<String,Object> result = new HashMap<>();result.put("authStr", "success");String json = objectMapper.writeValueAsString(result);// 使用response设置响应头为JSONhttpServletResponse.setContentType("text/json;charset=utf-8");// 回写数据httpServletResponse.getWriter().write(json);}
}
package com.wqbr.service.impl;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Service;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@Service
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {// new 一个 jackson 的 对象private ObjectMapper objectMapper = new ObjectMapper();/*** 此方法会在登录失败后进行回调** @param authenticationException:表示认证失败后的信息*/@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException) throws IOException, ServletException {// 自己构造json字符串,返回给前端Map<String,Object> result = new HashMap<>();result.put("authStr", "fail");String json = objectMapper.writeValueAsString(result);// 使用response设置响应头为JSONhttpServletResponse.setContentType("text/json;charset=utf-8");// 回写数据httpServletResponse.getWriter().write(json);}
}
- 编写login.jsp页面,登录提交路径为 spring-security.xml 中指定的 login-processing-url="/spring_security_check" 路径。且运行项目后会根据 spring-security.xml 中的 login-page="/login" 访问 LoginController 控制器的方法跳转到 login.jsp 页。
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
<%@ page isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>登录页面</title>
</head>
<body>
<h3>用户登录</h3>
<form action="${pageContext.request.contextPath}/spring_security_check" method="post">用户名:<input type="text" name="username"/><br>用户密码:<input type="password" name="password"/><br><input type="submit" value="登 录"/>
</form>
</body>
</html>
- spring-security.xml文件中的拦截规则如图:

- spring-security.xml文件中的用户详情接口实现(自定义登录授权实现类)

MyUserDetailsService类实现UserDetailsService接口,重写loadUserByUsername方法,实现此方法(用户登录时调用此方法,通过用户输入的登录信息查找数据库用户表进行身份认证匹配)
package com.wqbr.service.impl;import com.wqbr.domain.SysPermission;
import com.wqbr.domain.SysUser;
import com.wqbr.persistence.SystemDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.List;/*** 系统用户控制器* @author lv* @date 2024年1月16日*/
@Service
public class MyUserDetailsService implements UserDetailsService {@Autowiredprivate SystemDao systemDao;/*** loadUserByUsername:读取用户信息* 返回值类型 UserDetails 是 SpringSecurity 用来封装用户数据的接口*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {System.out.println("-------loadUserByUsername方法加载中。。。。username:"+username);/*** name: 用户名* password: 密码* authorities: 定义权限名称*/
// User user = new User("admin", "123456",
// AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_ALL"));
// return user;SysUser user=systemDao.findByUsername(username);System.out.println("user-====="+user);//判断if(user!=null){System.out.println(user.getUsername()+"---====---"+user.getPassword()+"----"+user.getAddtime());
/* List<SysPermission> permList=systemDao.findPermissionByUsername(user.getUsername());StringBuffer sb = new StringBuffer();for (SysPermission sysPermission : permList) {sb.append(sysPermission.getUrl());sb.append(",");}sb.delete(sb.length()-1,sb.length());*/List<GrantedAuthority> list=AuthorityUtils.createAuthorityList("ROLE_USER","admin");user.setAuthorities(list); //设置权限列表return user;}throw new UsernameNotFoundException(username+"用户名不存在!");}
}
到此spring-security讲解完毕,接下来补全几个测试页面
accessdeny.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
用户访问权限不足!。。。。。。。。。。。。。。。。。。。。。。。
</body>
</html>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
<%@ page isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>welcome!</title>
</head>
<body>
<h3>welcome! index页面</h3>
<form action="#" method="post">
</form>
</body>
</html>
add.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<h2>add...! 内测页面 </h2>
</body>
</html>
list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title>
</head>
<body>
<h2>list...! 列表 显示 </h2>
<h3>list...! || 列表 显示 </h3>
</body>
</html>
13. 运行项目查看效果
登录

输入错误的用户名、密码

输入正确的用户名、密码

登录成功后尝试访问/system/add 和 /system/list 方法请求


如图 /system/add 成功访问,/system/list 无法访问,因为权限不足。如下图用户详情类中没有给用户指定 ROLE_ALL 权限

现在我们关闭浏览器,重新打开浏览器。不登录的情况下访问控制器的 /system/add 请求 和 /system/index 请求。
访问 http://localhost:8080/wqdemotwo_war/system/add
跳回到登录页面。
访问 http://localhost:8080/wqdemotwo_war/system/index
成功访问。
好了关于 spring-security 就结束了。
下一篇讲讲用户认证登录进来以后如何根据角色获取菜单资源
相关文章:
SSM项目集成Spring Security 4.X版本(使用spring-security.xml 配置文件方式)
目录 前言 实战开发: 一、Spring Security整合到SSM项目 1. pom文件引入包 2. web.xml 配置 3. 添加 spring-security.xml 文件 二、Spring Security实战应用 1. 项目结构 2. pom文件引入 3. web.xml 配置 4. Spring 配置 applicationContext.xml 5. sp…...
如何生成开发语言的排名图表
1、解释说明 生成开发语言排名图表,通常需要以下几个步骤: - 首先,我们需要收集一些关于不同编程语言的统计数据,例如使用人数、市场份额等。这些数据可以从各种来源获取,例如网站、报告、数据库等。 - 然后&#x…...
有哪些简单好用、适合中小型企业的CRM系统?
阅读本文,你将了解:一、中小型企业对CRM系统的主要需求;二、盘点四款好用的CRM系统;三、CRM系统实施策略和优秀实践。 在快速变化的商业环境中,中小型企业面临着独特的挑战:如何在有限的资源下高效地管理客…...
Unity 适配器模式(实例详解)
文章目录 简介1. **Input Adapter 示例**2. **Component Adapter 示例**3. **网络数据解析适配器**4. **物理引擎适配**5. **跨平台服务适配** 简介 Unity中的适配器模式(Adapter Pattern)主要用于将一个类的接口转换为另一个接口,以便于原本…...
Spring boot项目java bean和xml互转
Spring boot项目实现java bean和xml互转 项目场景:互转方法使用jackson进行互转使用jaxws进行xml与bean的互转 搞定收工! 项目场景: 工作中需要给下游第三方收费系统做数据挡板,由于下游系统使用的是soap webservice,里面涉及各种…...
数字证书和数字证书认证机构和数字根证书,CA,RCA
文章目录 一、 数字证书1、什么是数字证书2、数字证书干什么的3、风险 二、数字证书认证机构(Certificate Authority,缩写为CA)参考文章 一、 数字证书 维基百科 公开密钥认证(英语:Public key certificateÿ…...
java web mvc-08-Grails 入门介绍
拓展阅读 Spring Web MVC-00-重学 mvc mvc-01-Model-View-Controller 概览 web mvc-03-JFinal web mvc-04-Apache Wicket web mvc-05-JSF JavaServer Faces web mvc-06-play framework intro web mvc-07-Vaadin web mvc-08-Grails 开源 The jdbc pool for java.(java …...
深度学习技术栈 —— Pytorch之TensorDataset、DataLoader
深度学习技术栈 —— Pytorch之TensorDataset、DataLoader 前言一、TensorDataset、DataLoader的用法?二、从.csv文件-->tensor张量总结 前言 简单来说,TensorDataset与DataLoader这两个类的作用, 就是将数据读入并做整合,以便…...
远程git开发
两种本地与远程仓库同步 """ 1)你作为项目仓库初始化人员:线上要创建空仓库 > 本地初始化好仓库 > 建立remote链接(remote add) > 提交本地仓库到远程(push)2)你作为项目后期开发人员:远程项目仓库已经创…...
Codeforces Round 812 (Div. 2) ---- C. Build Permutation --- 题解
目录 C. Build Permutation 题目描述: 编辑 思路解析: 代码实现: C. Build Permutation 题目描述: 思路解析: 先证明在任何情况下答案均存在。 假设我们所求的为 m m1 m2.....n 的排列,我们称不小于n…...
Matlab 将工作区变量保存到文件中(save)
语法 1、save(filename) 2、save(filename,variables) 3、save(filename,variables,fmt) 4、save(filename,variables,version) 5、save(filename,variables,version,-nocompression) 6、save(filename,variables,-append) 7、save(filename,variables,-append,-nocompression…...
源码实现简介
本系列所有代码在文章底部,每一章节代码可独立编译运行 随着科技的飞速发展,自动驾驶技术正逐渐成为现实。而在自动驾驶技术中,感知是至关重要的一个环节。通过感知,自动驾驶车辆能够识别和理解周围环境,进而做出相应…...
我每天如何使用 ChatGPT
我们都清楚互联网的运作方式——充斥着各种“爆款观点”,极端分裂的意见,恶搞和无知现象屡见不鲜。 最近,大家对于人工智能(AI)特别是大语言模型(LLMs)和生成式 AI(GenAI࿰…...
MySQL修炼手册14:用户权限管理:安全保障与数据隔离
目录 写在开头1 用户与权限的关系1.1 用户的创建与删除1.1.1 创建新用户1.1.2 批量创建用户1.1.3 安全删除用户 1.2 授予与撤销权限1.2.1 授予权限1.2.2 批量授予权限1.2.3 撤销权限 2 角色的应用2.1 创建与管理角色2.1.1 创建角色2.1.2 管理角色 2.2 将权限赋予角色2.2.1 将权…...
动态规划解决马尔可夫决策过程
马尔可夫决策过程是强化学习中的基本问题模型之一,而解决马尔可夫决策过程的方法我们统称为强化学习算法。 动态规划( dynamic programming, DP )具体指的是在某些复杂问题中,将问题转化为若干个子问题,并在求解每个子…...
ubuntu1604安装及问题解决
虚拟机安装vmbox7 虚拟机操作: 安装增强功能 sudo mkdir /mnt/share sudo mount -t vboxsf sharefolder /mnt/share第一次使用sudo提示is not in the sudoers file. This incident will be reported 你的root需要设置好密码 sudo passwd root 输入如下指令&#x…...
Leetcode—24. 两两交换链表中的节点【中等】
2023每日刷题(八十七) Leetcode—24. 两两交换链表中的节点 实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x),…...
USRP相关报错解决办法
文章目录 前言一、本地环境二、相关报错信息二、解决办法1、更换电脑操作系统2、升级最新版固件 前言 在进行 USRP 开发时遇到了一些报错,这里做个记录解决问题的方法。 一、本地环境 电脑操作系统:Windows11MATLAB 版本:MATLAB 2021aUSRP …...
【剑指offer】重建二叉树
👑专栏内容:力扣刷题⛪个人主页:子夜的星的主页💕座右铭:前路未远,步履不停 目录 一、题目描述1、题目2、示例 二、题目分析1、递归2、栈 一、题目描述 1、题目 剑指offer:重建二叉树 给定节…...
中仕教育:事业编招考全流程介绍
一、报名阶段 1. 了解查看招聘信息:查看各类事业编岗位的招聘信息,包括岗位职责、招聘条件、报名时间等。 2. 填写报名表:按照要求填写报名表,包括个人信息、教育背景、工作经历等内容。 3. 提交报名材料:将报名表及…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
云原生周刊:k0s 成为 CNCF 沙箱项目
开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...
在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤: 第一步: 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为: // 改为 v…...
