【SpringSecurity】springboot整合SpringSecurity实现登录校验与权限认证
【SpringSecurity】springboot整合SpringSecurity实现登录校验与权限认证
- 【一】SpringSecurity框架简介
- 【二】SpringSecurity与shiro
- 【1】SpringSecurity特点
- 【2】shiro特点
- 【3】SpringSecurity和shiro总结
- 【三】SpringSecurity过滤器
- 【1】SpringSecurity中常见的过滤器
- 【2】15种过滤器
- (1)WebAsyncManagerIntegrationFilter
- (2)SecurityContextPersistenceFilter
- (3)HeaderWriterFilter
- (4)CsrfFilter
- (5)LogoutFilter
- (6)UsernamePasswordAuthenticationFilter
- (7)DefaultLoginPageGeneratingFilter
- (8)DefaultLogoutPageGeneratingFilter
- (9)BasicAuthenticationFilter
- (10)RequestCacheAwareFilter
- (11)SecurityContextHolderAwareRequestFilter
- (12)AnonymousAuthenticationFilter
- (13)SessionManagementFilter
- (14)ExceptionTranslationFilter
- (15)FilterSecurityInterceptor
- 【3】SpringSecurity基本流程
- (1)整体流程概述
- (2)详细流程步骤
- 【四】PasswordEncoder接口
- 【五】整合SpringSecurity实现登录校验与权限认证
- 【1】身份认证
- (1)controller测试
- (2)登录认证流程
- (3)创建一个用户表
- (4)创建一个MyUserDetailsService类
- (5)通过配置类对AuthenticationManager与自定义的UserDetails和PasswordEncoder进行关联
- (6)在登录方法所在的类中注入AuthenticationManager
- (7)介绍UsernamePasswordAuthenticationToken
- (8)测试登录
- (9)SecurityFilterChain 过滤器
- (10)测试登录
- (11)测试退出登录
- 【2】权限校验
- (1)角色与权限
- (2)角色表与权限表
- (3)权限认证流程
- (4)在MyUserDetailsService中实现用户权限的赋值
- (5)MySysUserDetails中完成角色和权限的赋值
- (6)基于请求
- (7)基于方法
- 【3】总结
- 【五】Springboot整合SpringSecurity入门
- 【1】pom.xml
- 【2】application.properties
- 【3】SecurityConfig
- 【4】启动类
- 【5】User实体类
- 【6】UserService服务层
- 【7】UserMapper
- 【8】UserController控制层
- 【六】微服务认证与授权实现思路
- 【七】微服务代码实例
- 【1】父工程pom.xml
- 【2】common模块
- 【3】common模块->SpringSecurity子模块
- 【4】common模块->service_base
- 【5】gateway模块
- 【八】ruoyi平台整合SpringSecurity案例【待完成】
【一】SpringSecurity框架简介
关于安全方面的两个主要区域是“认证”和“授权”(或者说是访问控制),一般来说,Web应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是SpringSecurity重要核心功能。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录。
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
【二】SpringSecurity与shiro
【1】SpringSecurity特点
(1)与Spring框架无缝整合
(2)全面的权限控制
(3)专门为Web开发而设计
旧版本不能脱离Web环境使用
新版本对整个框架进行了分层抽取,分成了核心模块和Web模块,单独引入了核心模块就可以脱离Web环境
重量级
【2】shiro特点
Apache旗下的轻量级权限控制框架
(1)轻量级
shiro主张的理念是把复杂的事情变简单,针对性能更高要求的互联网应用有更好的变现
(2)通用性
好处:不局限于Web环境,可以脱离Web环境使用
缺陷:在Web环境下一些特定的需求需要手动编写代码定制
【3】SpringSecurity和shiro总结
相对于shiro,在SSM中整合SpringSecurity都是比较麻烦的操作,所以,SpringSecurity虽然功能比shiro强大,但是使用反而没有shiro多,(shiro虽然功能没有SpringSecurity多,但是对于大部分项目而言,shiro也够用了)。自从有了Springboot之后,Springboot对于SpringSecurity提供了自动化配置方案,可以使用更少的配置来使用SpringSecurity。因此,一般来说,常见的安全管理技术栈的组合是这样的:
(1)SSM+shiro
(2)Springboot/SpringCloud+SpringSecurity
以上只是一个推荐的组合而已,如果单从技术上来说,无论怎么组合,都是可以运行的。
【三】SpringSecurity过滤器
【1】SpringSecurity中常见的过滤器
【2】15种过滤器
SpringSecurity采用的是责任链的设计模式,它有一条很长的过滤器链。这些过滤器按特定顺序执行,每个过滤器负责不同的安全任务,如身份验证、授权、会话管理等。下面介绍一些重要的过滤器及其功能:
(1)WebAsyncManagerIntegrationFilter
功能:将 Spring Security 上下文与 Spring 的 WebAsyncManager 集成,确保异步请求也能正确处理安全上下文。
位置:通常位于过滤器链的最前端。
(2)SecurityContextPersistenceFilter
功能:在每个请求开始时,从 HttpSession 中获取安全上下文(SecurityContext),并将其设置到当前线程中;在请求结束时,将安全上下文保存回 HttpSession 中。
位置:在请求处理的早期阶段执行。
(3)HeaderWriterFilter
功能:用于向响应头中添加安全相关的头部信息,如 X-Frame-Options、X-XSS-Protection 等,增强应用的安全性。
位置:在安全上下文设置之后执行。
(4)CsrfFilter
功能:防止跨站请求伪造(CSRF)攻击,验证请求中的 CSRF 令牌是否有效。
位置:在处理表单提交等敏感请求之前执行。
(5)LogoutFilter
功能:处理用户的注销请求,清除安全上下文、销毁会话等。
位置:在处理注销相关的 URL 请求时执行。
(6)UsernamePasswordAuthenticationFilter
功能:处理基于表单的用户名和密码认证,从请求中提取用户名和密码,尝试进行身份验证。
位置:通常在处理登录表单提交的 URL 时执行。
(7)DefaultLoginPageGeneratingFilter
功能:如果没有自定义登录页面,该过滤器会生成一个默认的登录页面。
位置:在处理登录相关请求时,若没有自定义登录页面则会起作用。
(8)DefaultLogoutPageGeneratingFilter
功能:如果没有自定义注销页面,该过滤器会生成一个默认的注销页面。
位置:在处理注销相关请求时,若没有自定义注销页面则会起作用。
(9)BasicAuthenticationFilter
功能:处理基于 HTTP Basic 认证的请求,从请求头中提取基本认证信息进行身份验证。
位置:在处理需要 Basic 认证的请求时执行。
(10)RequestCacheAwareFilter
功能:处理请求缓存,当用户在未认证的情况下访问受保护资源时,会缓存该请求,认证成功后重定向到原请求的资源。
位置:在认证前后处理请求缓存相关操作。
(11)SecurityContextHolderAwareRequestFilter
功能:将 HttpServletRequest 包装成 SecurityContextHolderAwareRequestWrapper,提供额外的安全相关方法。
位置:在请求处理过程中,对请求进行包装。
(12)AnonymousAuthenticationFilter
功能:如果请求在经过前面的过滤器后仍未认证,该过滤器会为请求设置一个匿名身份,避免后续处理因缺少身份信息而出错。
位置:在前面的认证过滤器之后执行。
(13)SessionManagementFilter
功能:管理用户会话,处理会话超时、并发会话控制等问题。
位置:在会话相关操作的处理阶段执行。
(14)ExceptionTranslationFilter
功能:捕获认证和授权过程中抛出的异常,并将其转换为合适的 HTTP 响应,如重定向到登录页面或返回 403 状态码。
位置:在认证和授权过滤器之后,处理异常情况。
(15)FilterSecurityInterceptor
功能:进行最终的授权检查,根据配置的访问规则判断用户是否有权限访问请求的资源。
位置:位于过滤器链的末尾,在所有其他过滤器执行完毕后进行最终的授权决策。
【3】SpringSecurity基本流程
(1)整体流程概述
当一个客户端发起请求时,请求会进入 Spring Security 的过滤器链。过滤器链中的各个过滤器会依次对请求进行处理,其中涉及认证的过滤器会尝试对用户进行身份验证。如果认证成功,用户的身份信息会被存储在安全上下文中;如果认证失败,则会根据配置进行相应的错误处理。
(2)详细流程步骤
(1)请求进入过滤器链
客户端发起请求后,请求首先会到达 Spring Security 的过滤器链。Spring Security 默认有多个过滤器,这些过滤器按特定顺序排列,每个过滤器负责不同的安全任务。例如,SecurityContextPersistenceFilter 是过滤器链中的第一个过滤器,它会在请求开始时从 HttpSession 中获取安全上下文(SecurityContext),并将其设置到当前线程中;在请求结束时,将安全上下文保存回 HttpSession 中。
(2)认证过滤器处理
不同类型的认证方式由不同的认证过滤器处理,以下是几种常见的认证方式及其对应的过滤器:
1-表单登录认证(UsernamePasswordAuthenticationFilter)
请求匹配:当请求的 URL 匹配到配置的登录 URL(默认为 /login),且请求方法为 POST 时,UsernamePasswordAuthenticationFilter 会开始工作。
提取认证信息:该过滤器会从请求中提取用户名和密码,通常是从表单的 username 和 password 字段中获取。
创建认证令牌:使用提取的用户名和密码创建一个 UsernamePasswordAuthenticationToken 对象,该对象实现了 Authentication 接口,用于封装用户的认证信息。
调用认证管理器:将创建的 UsernamePasswordAuthenticationToken 对象传递给 AuthenticationManager 进行认证。
2-HTTP Basic 认证(BasicAuthenticationFilter)
请求匹配:当请求头中包含 Authorization 字段,且值以 Basic 开头时,BasicAuthenticationFilter 会对请求进行处理。
提取认证信息:从 Authorization 字段中提取基本认证信息(通常是经过 Base64 编码的用户名和密码),并进行解码。
创建认证令牌:使用解码后的用户名和密码创建 UsernamePasswordAuthenticationToken 对象。
调用认证管理器:将认证令牌传递给 AuthenticationManager 进行认证。
(3)认证管理器(AuthenticationManager)处理
AuthenticationManager 是一个接口,它的主要职责是对传入的 Authentication 对象进行认证。默认实现是 ProviderManager,它内部维护了一个 AuthenticationProvider 列表。
1-遍历认证提供者:ProviderManager 会遍历 AuthenticationProvider 列表,依次调用每个 AuthenticationProvider 的 authenticate 方法,直到找到能够处理该 Authentication 对象的 AuthenticationProvider。
2-认证处理:AuthenticationProvider 会根据具体的认证逻辑对 Authentication 对象进行验证,例如查询数据库验证用户名和密码是否匹配。如果验证成功,会返回一个已认证的 Authentication 对象;如果验证失败,会抛出相应的异常。
(4)用户详情服务(UserDetailsService)
在认证过程中,AuthenticationProvider 通常会调用 UserDetailsService 来获取用户的详细信息。UserDetailsService 是一个接口,其主要方法是 loadUserByUsername,该方法根据用户名从数据源(如数据库、LDAP 等)中加载用户的详细信息,返回一个 UserDetails 对象。UserDetails 接口封装了用户的核心信息,如用户名、密码、权限等。
(5)认证结果处理
认证成功:如果 AuthenticationProvider 认证成功,会返回一个已认证的 Authentication 对象,其中包含用户的详细信息和权限。UsernamePasswordAuthenticationFilter 或 BasicAuthenticationFilter 会将该对象设置到安全上下文中(SecurityContextHolder),表示用户已成功认证。
认证失败:如果认证失败,AuthenticationProvider 会抛出相应的异常,如 BadCredentialsException(用户名或密码错误)。ExceptionTranslationFilter 会捕获这些异常,并根据配置进行相应的处理,例如重定向到登录页面或返回 401 未授权状态码。
(6)后续请求处理
认证成功后,用户的身份信息会存储在安全上下文中。后续的请求会通过 SecurityContextPersistenceFilter 从 HttpSession 中恢复安全上下文,确保用户在整个会话期间保持认证状态。同时,FilterSecurityInterceptor 会根据配置的访问规则对请求进行授权检查,判断用户是否有权限访问请求的资源。
【四】PasswordEncoder接口
【五】整合SpringSecurity实现登录校验与权限认证
创建一个spring boot项目,并导入一些初始依赖,不赘述
【1】身份认证
(1)controller测试
由于我们加入了 spring-boot-starter-security 的依赖,所以security就会自动生效了。这时直接编写一个controller控制器,并编写一个接口进行测试:
可以看到我们在访问这个接口时出现了拦截,必须要我们进行登录之后才能访问;
(2)登录认证流程
Spring Security 6.x 的认证实现流程如下:
(1)用户提交登录请求
(2)Spring Security 将请求交给 UsernamePasswordAuthenticationFilter 过滤器处理。
(3)UsernamePasswordAuthenticationFilter 获取请求中的用户名和密码,并生成一个 AuthenticationToken 对象,将其交给 AuthenticationManager 进行认证。
(4)AuthenticationManager 通过 UserDetailsService 获取用户信息,然后使用 PasswordEncoder 对用户密码进行校验。
(5)如果密码正确,AuthenticationManager 会生成一个认证通过的 Authentication 对象,并返回给 UsernamePasswordAuthenticationFilter 过滤器。如果密码不正确,则 AuthenticationManager 抛出一个 AuthenticationException 异常。
(6)UsernamePasswordAuthenticationFilter 将 Authentication 对象交给 SecurityContextHolder 进行管理,并调用 AuthenticationSuccessHandler 处理认证成功的情况。
(7)如果认证失败,UsernamePasswordAuthenticationFilter 会调用 AuthenticationFailureHandler 处理认证失败的情况。
看起来有点复杂,其实写起来很简单的。spring security的底层就是一堆的过滤器来是实现的,而我们只需要编写一些重要的过滤器即可,其他的就用spring security默认的实现,只要不影响我们正常的登录功能即可。
(3)创建一个用户表
创建一个用户表用来进行登录实现,注意这个表中的用户名不能重复,我们将用户名作为每一个用户的唯一凭证,就如同人的手机号或者身份证号一样。
实体类、mapper、service、controller等基本配置不赘述
(4)创建一个MyUserDetailsService类
创建一个MyUserDetailsService类来实现SpringSecurity的UserDetailsService接口(这里进行用户登录和授权的逻辑处理)
UserDetailsService:此接口中定义了登录服务方法,用来实现登录逻辑。方法的返回值是UserDetails,也是spring security框架定义中的一个接口,用来存储用户信息,我们可以自定义一个类用来实现这个接口,将来返回的时候就返回我们自定义的用户实体类。
(1)实现UserDetailsService接口
@Component
public class MyUserDetailsService implements UserDetailsService {/** UserDetailsService:提供查询用户功能,如根据用户名查询用户,并返回UserDetails*UserDetails,SpringSecurity定义的类, 记录用户信息,如用户名、密码、权限等* */@Autowiredprivate SysUserMapper sysUserMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名从数据库中查询用户SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(username != null, SysUser::getUsername, username));if (sysUser==null){throw new UsernameNotFoundException("用户不存在");}// 封装查询到的用户信息MySysUserDetails mySysUserDetails=new MySysUserDetails(sysUser);return mySysUserDetails;}
}
(2)实现UserDetails接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MySysUserDetails implements UserDetails {private Integer id;private String username;private String password;// 用户拥有的权限集合,我这里先设置为null,将来会再更改的@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}public MySysUserDetails(SysUser sysUser) {this.id = sysUser.getId();this.username = sysUser.getUsername();this.password = sysUser.getPassword();}// 后面四个方法都是用户是否可用、是否过期之类的。我都设置为true@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
(5)通过配置类对AuthenticationManager与自定义的UserDetails和PasswordEncoder进行关联
Spring Security是通过AuthenticationManager实现的认证,会借此来判断用户名和密码的正确性
密码解析器spring security框架定义的接口:PasswordEncoder
spring security框架强制要求,必须在spring容器中存在PasswordEncoder类型对象,且对象唯一
@Configuration
@EnableWebSecurity //开启webSecurity服务
public class SecurityConfig {@Autowiredprivate MyUserDetailsService myUserDetailsService;@Beanpublic AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder){DaoAuthenticationProvider provider=new DaoAuthenticationProvider();//将编写的UserDetailsService注入进来provider.setUserDetailsService(myUserDetailsService);//将使用的密码编译器加入进来provider.setPasswordEncoder(passwordEncoder);//将provider放置到AuthenticationManager 中ProviderManager providerManager=new ProviderManager(provider);return providerManager;}/** 在security安全框架中,提供了若干密码解析器实现类型。* 其中BCryptPasswordEncoder 叫强散列加密。可以保证相同的明文,多次加密后,* 密码有相同的散列数据,而不是相同的结果。* 匹配时,是基于相同的散列数据做的匹配。* Spring Security 推荐使用 BCryptPasswordEncoder 作为密码加密和解析器。* */
@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}
(6)在登录方法所在的类中注入AuthenticationManager
调用authenticate实现认证逻辑,并且在认证之后返回认证过的用户信息:
(1)controller层
// 用户登录
@PostMapping("/login")
public String login(@RequestBody LoginDto loginDto){String token= sysUserService.login(loginDto);return token;
}
(2)对应的service层的方法
编写具体的登录方法,创建一个UsernamePasswordAuthenticationToken对象,并传入相应的用户名和密码;注入一个AuthenticationManager的bean,这个bean是spring security封装的用来进行认证的类,调用这个类的authenticate方法并传入UsernamePasswordAuthenticationToken对象;
@Autowiredprivate AuthenticationManager authenticationManager;// 登录接口的具体实现@Overridepublic String login(LoginDto loginDto) {// 传入用户名和密码UsernamePasswordAuthenticationToken usernamePassword =new UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword());//是实现登录逻辑,此时就会去调用LoadUserByUsername方法Authentication authenticate = authenticationManager.authenticate(usernamePassword);//获取返回的用户信息Object principal = authenticate.getPrincipal();//强转为MySysUserDetails类型MySysUserDetails mySysUserDetails = (MySysUserDetails) principal;//输出用户信息System.err.println(mySysUserDetails);//返回tokenString token= UUID.randomUUID().toString();return token;}
(7)介绍UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken是Spring Security中用于表示基于用户名和密码的身份验证令牌的类。它主要有以下两个构造方法:
(1)UsernamePasswordAuthenticationToken(Object principal, Object credentials)
1-principal参数表示认证主体,通常是用户名或用户对象。在身份验证过程中,这通常是用来标识用户的信息,可以是用户名、邮箱等。
2-credentials参数表示凭据,通常是用户的密码或其他凭证信息。在身份验证过程中,这用于验证用户的身份。
(2)UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)
1-除了上述两个参数外,这个构造方法还接受一个授权权限集合(authorities参数)。这个集合表示用户所拥有的权限,通常是一个包含用户权限信息的集合。
2-GrantedAuthority接口代表了用户的权限信息,可以通过该接口的实现类来表示用户具体的权限。
这两个构造方法的作用是创建一个包含用户身份信息、凭据信息和权限信息的身份验证令牌,以便在Spring Security中进行身份验证和授权操作。通过这些构造方法,可以将用户的相关信息封装成一个完整的身份验证对象,方便在安全框架中进行处理和验证。
总之,UsernamePasswordAuthenticationToken是在Spring Security中用于表示用户名密码身份验证信息的重要类,通过不同的构造方法可以满足不同场景下的需求。
(8)测试登录
造一些用户数据,并进行测试
访问:访问http://localhost:8080/test,自动跳转到了Spring Security提供的默认的登录页面;这是因为Spring Security默认所有的请求都要先登录才行,我们在这里登录之后就可以继续访问test页面了;
这里的用户名和密码就是我们在数据库中存储的用户名和密码
既然这个test请求要先进行拦截认证才能访问,那么,我们刚才编写的登录接口sys-user/login岂不是也要先进行拦截认证才能访问,这就与我们编写登录接口的初衷违背了,我们这个接口就是用来登陆的,现在还要先登录认证,之后再访问这个登录接口。那么有没有一种方法,不使用SpringSecurity默认的登录页面呢,使我们编写的登录接口所有人都可以直接访问呢?
(9)SecurityFilterChain 过滤器
配置用户登录的接口可以暴露出来,被所有人都正常的访问,不会被拦截转跳到默认登录页面,而是跳到自定义的登录页面。
在第二步设置的SecurityConfig类中设置过滤器:
(1)在spring security6.x版本之后,原先经常用的and()方法被废除了,现在spring官方推荐使用Lambda表达式的写法。
(2)因为我们接下来要进行测试,所以禁用CSRF保护
/** 配置权限相关的配置* 安全框架本质上是一堆的过滤器,称之为过滤器链,每一个过滤器链的功能都不同* 设置一些链接不要拦截* */@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {//关闭csrfhttpSecurity.csrf(it->it.disable());
httpSecurity.authorizeHttpRequests(it->it.requestMatchers("/login","/sys-user/login").permitAll() //设置登录路径所有人都可以访问.anyRequest().authenticated() //其他路径都要进行拦截);//表单httpSecurity.formLogin(from->from.loginPage("/login") //跳转到自定义的登录页面.loginProcessingUrl("/sys-user/login") //处理前端的请求,与from表单的action一致即可.defaultSuccessUrl("/index") //默认的请求成功之后的跳转页面,直接访问登录页面);return httpSecurity.build();}
配置对应的controller
@Controller
public class Login {@GetMapping("/login")public String login(){System.out.println("用户进入登录页面");return "login"; //没使用json返回,直接映射到自定义登录的页面}@GetMapping("/index")@ResponseBodypublic String index(){return "用户登录成功";}
}
(10)测试登录
(1)访问test请求:遇到拦截,说明我们的配置生效了
(2)访问login请求,并用账号密码登陆成功
登录之后,会跳转到/test请求地址
现在我们直接访问/login登录页面:可以看到返回了/index页面的内容(这个是我们设置的默认登录成功之后返回的页面)
(11)测试退出登录
需要注意的是在Spring Security中,没有专门用于处理退出失败的接口。退出(注销)操作通常是由浏览器发起的,Spring Security会拦截注销请求并执行相应的注销逻辑。
退出操作通常是通过调用SecurityContextLogoutHandler来完成的,它会清除用户的安全上下文,包括认证信息和会话信息。
在security框架中,默认提供了退出登陆的功能。请求地址是 /lohout 此为默认值,可以通过配置进行修该。直接请求 /logout ,会实现自动退出登录逻辑(默认的/logout接收get、和post请求)
退出登陆时,会清楚内存中的登录用户主体信息,销毁会话对象等等。
自定义退出接口:
httpSecurity.logout(logout->{logout.logoutUrl("/user/login") //自定义退出接口.logoutSuccessHandler(logoutSuccess); //退出成功之后的逻辑});
编写退出成功之后的逻辑,我们可以在这里删除掉redis中的数据,清除登录的上下文,设置返回的信息等等…(如果是前后端分离状态下的spring security,这些工作都可以在自定义的退出接口中进行实现。如果是前后端不分离的表单式登录,还是使用传统的Cookie和Session来进行用户信息的保存,我们自需要调用ogout.logoutUrl(“/user/login”) 方法来指定退出路径即可,退出的逻辑不需要我们来实现。)
@Component
public class LogoutSuccess implements LogoutSuccessHandler {@Resourceprivate RedisTemplate<String,String> redisTemplate;/*
* 登录成功之后的逻辑
* */@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {String token = request.getHeader("token");// 删除redis中的数据redisTemplate.delete(token);Map<String,Object> map=new HashMap<>();map.put("msg","退出成功");map.put("code",200);response.getWriter().write(JSON.toJSONString(map));response.setContentType("application/json;charset=utf-8");}}
【2】权限校验
(1)角色与权限
角色与权限在SpringSecurity中的作用:
(1)角色(Role)
角色是一组权限的集合,通常代表着用户的身份或职责。在Spring Security中,可以通过配置将角色分配给用户或者用户组,以此来控制用户对系统资源的访问。例如,管理员拥有添加、删除和修改用户的权限,而普通用户只能查看自己的信息。
(2)权限(Permission)
权限是指对某一特定资源的访问控制,例如读写文件、访问数据库等。在Spring Security中,通常使用“资源-操作”命名方式来定义权限,例如“/admin/* - GET”表示允许访问以/admin/开头的所有URL的GET请求。可以将权限分配给角色,也可以将其分配给单独的用户。
(2)角色表与权限表
角色与权限之间的关系是多对多的,建立两张简单的表;一张用来存放角色、一张用来存放权限
(1)角色表
(2)权限表
其他代码自动生成,不赘述
(3)权限认证流程
SpringSecurity要求将身份认证信息存到GrantedAuthority对象列表中。代表了当前用户的权限。 GrantedAuthority对象由AuthenticationManager插入到Authentication对象中,然后在做出授权决策 时由AccessDecisionManager实例读取。 GrantedAuthority 接口只有一个方法
String getAuthority();
AuthorizationManager实例通过该方法来获得GrantedAuthority。通过字符串的形式表示, GrantedAuthority可以很容易地被大多数AuthorizationManager实现读取。如果GrantedAuthority不 能精确地表示为String,则GrantedAuthorization被认为是复杂的,getAuthority()必须返回null
直接在登录时查询用户的权限,并放在我们自定义的实现了UserDetail的接口类中,用来表示登录用户的全部信息;
(4)在MyUserDetailsService中实现用户权限的赋值
在MySysUserDetails类中加入两个属性,记录从数据库中查处的角色和权限信息
这里就简单一点,不在做多表关联查询了。直接把zhangsan用户设置为超级管理员,拥有所有权限;lisi用户设置为普通管理员,拥有基本权限。
在MyUserDetailsService中实现用户权限的赋值:
@Component
public class MyUserDetailsService implements UserDetailsService {/** UserDetailsService:提供查询用户功能,如根据用户名查询用户,并返回UserDetails*UserDetails,SpringSecurity定义的类, 记录用户信息,如用户名、密码、权限等* */@Autowiredprivate SysUserMapper sysUserMapper;@Autowiredprivate SysRoleMapper sysRoleMapper;@Autowiredprivate SysPermissionsMapper sysPermissionsMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名从数据库中查询用户SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(username != null, SysUser::getUsername, username));if (sysUser==null){throw new UsernameNotFoundException("用户不存在");}MySysUserDetails mySysUserDetails=new MySysUserDetails(sysUser);if ("zhangsan".equals(username)){//zhangsan用户是超级管理员,拥有一切权限SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "超级管理员"));Set<SysRole> roles=new HashSet<>();roles.add(sysRole);mySysUserDetails.setRoles(roles);SysPermissions sysPermissions = sysPermissionsMapper.selectById(1);Set<String> permissions=new HashSet<>();permissions.add(sysPermissions.getPermissionsName());mySysUserDetails.setPermissions(permissions);}if ("lisi".equals(username)){
//lisi用户是普通管理员,拥有基本权限SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "普通管理员"));Set<SysRole> roles=new HashSet<>();roles.add(sysRole);mySysUserDetails.setRoles(roles);SysPermissions sysPermissions = sysPermissionsMapper.selectById(2);Set<String> permissions=new HashSet<>();permissions.add(sysPermissions.getPermissionsName());mySysUserDetails.setPermissions(permissions);}return mySysUserDetails;}
}
(5)MySysUserDetails中完成角色和权限的赋值
private Set<SysRole> roles;// 权限信息private Set<String> permissions;// 用户拥有的权限集合,我这里先设置为null,将来会再更改的@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {System.err.println("进入权限的获取方法");List<GrantedAuthority> authorities = new ArrayList<>(); // 授权信息列表// 将角色名称添加到授权信息列表中roles.forEach(role->authorities.add(new SimpleGrantedAuthority(role.getRoleName())));// 将权限名称添加到授权信息列表中permissions.forEach(permission->authorities.add(new SimpleGrantedAuthority(permission)));return authorities; // 返回授权信息列表}
用户认证之后,会去存储用户对应的权限,并且给资源设置对应的权限,SpringSecurity支持两种粒度 的权限:
1、基于请求的:在配置文件中配置路径,可以使用**的通配符
2、基于方法的:在方法上使用注解实现
角色配置:在UserDetails接口中存在相关的权限和角色管理,只不过我们在实现这个接口的时候,将这些都设置为了null。现在我们只需要将这些信息实现即可
(6)基于请求
还是在SecurityFilter过滤器中实现请求地址的权限校验
httpSecurity.authorizeHttpRequests(it->
//hello地址只有超级管理员角色才能访问
it.requestMatchers("/hello").hasRole("超级管理员")
//hello2地址只有"拥有所有权限"的权限才能访问
.requestMatchers("hello2").hasAuthority("拥有所有权限").requestMatchers("/login","sys-user/login").permitAll() //设置登录路径所有人都可以访问.anyRequest().authenticated() //其他路径都要进行拦截);
使用sili进行登录时,访问hello2接口显示权限不够:
使用zhangsan进行登录时,访问hello2接口可以访问到:
(7)基于方法
基于方法的权限认证要在SecurityConfig类上加上@EnableMethodSecurity注解,表示开启了方法权限的使用;
常用的有四个注解:
@PreAuthorize
@PostAuthorize
@PreFilter
@PostFilter
/*测试@PreAuthorize注解
* 作用:使用在类或方法上,拥有指定的权限才能访问(在方法运行前进行校验)
* String类型的参数:语法是Spring的EL表达式
* 有权限:test3权限
* hasRole:会去匹配authorities,但是会在hasRole的参数前加上一个ROLE_前缀,
* 所以在定义权限的时候需要加上ROLE_前缀
* role和authorities的关系是:role是一种复杂的写法,有ROLE_前缀,authorities是role的简化写法
* 如果使用
* hasAnyRole:则匹配的权限是在authorities加上前缀ROLE_
* 推荐使用
* hasAnyAuthority:匹配authorities,但是不用在authorities的参数前加上ROLE_前缀
* */
@PreAuthorize("hasAnyAuthority('拥有所有权限')")
@ResponseBody
@GetMapping("/test3")
public String test3(){System.out.println("一个请求");return "一个test3请求";
}/*@PostAuthorize:在方法返回时进行校验。可以还是校验权限、或者校验一些其他的东西(接下来我们校验返回值的长度)
*返回结果的长度大于3、则认为是合法的
returnObject:固定写法,代指返回对象
* */
@ResponseBody
@PostAuthorize("returnObject.length()>4")
@GetMapping("/test4")
public String test4(){System.out.println("一个test4请求");return "小张自傲张最终";
}/*
* @PreFilter:过滤符合条件的数据进入到接口
* */@PostFilter("filterObject.length()>3")@ResponseBody@GetMapping("/test5")public String test5(){System.out.println("一个test4请求");List<String> list = new ArrayList<>();list.add("张三");list.add("王麻子");list.add("狗叫什么");return "一个test5请求";}/*
* @PreFilter:过滤符合条件的数据返回,数据必须是Collection、map、Array【数组】
* */
@PreFilter("filterObject.length()>5")
@ResponseBody
@PostMapping("/test6")
public List<String> test6(@RequestBody List<String> list){return list;
}
需要注意的是这些方法不仅仅局限在权限的校验,还能对返回的结果做一定的操作;
最需要注意的就是@PreFilter注解,它要求前端传递的参数一定是数组或集合;
基于方法鉴权 在SpringSecurity6版本中@EnableGlobalMethodSecurity被弃用,取而代之的是 @EnableMethodSecurity。默认情况下,会激活pre-post注解,并在内部使用 AuthorizationManager。
新老API区别 此@EnableMethodSecurity替代了@EnableGlobalMethodSecurity。提供了以下改进:
- 使用简化的AuthorizationManager。
- 支持直接基于bean的配置,而不需要扩展GlobalMethodSecurityConfiguration
- 使用Spring AOP构建,删除抽象并允许您使用Spring AOP构建块进行自定义
- 检查是否存在冲突的注释,以确保明确的安全配置
- 符合JSR-250
- 默认情况下启用@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter
【3】总结
(1)登录校验(Authentication):
1-用户提交用户名和密码进行登录。
2-Spring Security会拦截登录请求,并将用户名和密码与存储在系统中的凭据(如数据库或LDAP)进行比对。
3-如果用户名和密码匹配,则认为用户通过了身份验证,可以继续访问受限资源。
4-认证成功后,Spring Security会创建一个包含用户信息和权限的安全上下文(Security Context)。
(2)权限认证(Authorization):
1-一旦用户通过了身份验证,Spring Security就会开始进行权限认证。
2-针对每个受限资源或操作,可以配置相应的权限要求,例如需要哪些角色或权限才能访问。
3-Spring Security会根据配置的权限要求,检查当前用户所拥有的角色和权限,判断是否满足访问条件。
4-如果用户拥有足够的角色或权限,就被允许访问资源;否则将被拒绝访问,并可能重定向到登录页面或返回相应的错误信息。
Spring Security通过身份验证(Authentication)来确认用户的身份,并通过授权(Authorization)来控制用户对受保护资源的访问。这种分离的设计使得安全配置更加灵活,并且可以轻松地对不同的用户和角色进行管理和控制。
【五】Springboot整合SpringSecurity入门
【1】pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.guor</groupId><artifactId>securityProject</artifactId><version>0.0.1-SNAPSHOT</version><name>securityProject</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--lombok用来简化实体类--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
【2】application.properties
server.port=8111
#spring.security.user.name=root
#spring.security.user.password=root#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
【3】SecurityConfig
SecurityConfig
package com.guor.security.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();String password = passwordEncoder.encode("123");auth.inMemoryAuthentication().withUser("zs").password(password).roles("admin");}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}
}
UserSecurityConfig
package com.guor.security.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;import javax.sql.DataSource;@Configuration
public class UserSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;//注入数据源@Autowiredprivate DataSource dataSource;//配置对象@Beanpublic PersistentTokenRepository persistentTokenRepository() {JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);//jdbcTokenRepository.setCreateTableOnStartup(true);return jdbcTokenRepository;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userService(userService).passwordEncoder(password());}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {//退出http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();//配置没有权限访问跳转自定义页面http.exceptionHandling().accessDeniedPage("/unauth.html");http.formLogin() //自定义自己编写的登录页面.loginPage("/on.html") //登录页面设置.loginProcessingUrl("/user/login") //登录访问路径.defaultSuccessUrl("/success.html").permitAll() //登录成功之后,跳转路径.failureUrl("/unauth.html").and().authorizeRequests().antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证//当前登录用户,只有具有admins权限才可以访问这个路径//1 hasAuthority方法// .antMatchers("/test/index").hasAuthority("admins")//2 hasAnyAuthority方法// .antMatchers("/test/index").hasAnyAuthority("admins,manager")//3 hasRole方法 ROLE_sale.antMatchers("/test/index").hasRole("sale").anyRequest().authenticated().and().rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(60)//设置有效时长,单位秒.userDetailsService(userService);// .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());// .and().csrf().disable(); //关闭csrf防护}
}
【4】启动类
【5】User实体类
package com.guor.security.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
public class User {private Integer id;private String username;private String password;
}
【6】UserService服务层
package com.guor.security.service;import com.guor.security.entity.User;
import com.guor.security.mapper.UsersMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//调用usersMapper方法,根据用户名查询数据库QueryWrapper<Users> wrapper = new QueryWrapper();// where username=?wrapper.eq("username",username);User user = userMapper.selectOne(wrapper);//判断if(user == null) {//数据库没有用户名,认证失败throw new UsernameNotFoundException("用户名不存在!");}List<GrantedAuthority> auths =AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");//从查询数据库返回users对象,得到用户名和密码,返回return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auths);}
}
【7】UserMapper
package com.guor.security.mapper;import com.guor.security.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;@Repository
public interface UserMapper extends BaseMapper<User> {
}
【8】UserController控制层
package com.guor.security.controller;import com.guor.security.entity.User;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.List;@RestController
@RequestMapping("/test")
public class UserController {@GetMapping("hello")public String hello() {return "hello security";}@GetMapping("index")public String index() {return "hello index";}@GetMapping("update")//@Secured({"ROLE_sale","ROLE_manager"})//@PreAuthorize("hasAnyAuthority('admins')")@PostAuthorize("hasAnyAuthority('admins')")public String update() {System.out.println("update......");return "hello update";}@GetMapping("getAll")@PostAuthorize("hasAnyAuthority('admins')")@PostFilter("filterObject.username == 'admin1'")public List<Users> getAllUser(){ArrayList<Users> list = new ArrayList<>();list.add(new Users(11,"admin1","6666"));list.add(new Users(21,"admin2","888"));System.out.println(list);return list;}
}
【六】微服务认证与授权实现思路
(1)如果是基于Session,那么SpringSecurity会对cookie里的SessionID进行解析,找到服务器存储的Session信息,然后判断当前用户是否复合请求的要求
(2)如果是token,则是解析出token,然后将当前请求加入到SpringSecurity管理的权限信息中去
如果系统的模块众多,每个模块都需要进行授权与认证,所以我们选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,SpringSecurity解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样SpringSecurity就能够判断当前请求是否有权限访问。
【七】微服务代码实例
【1】父工程pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><modules><module>common</module><module>infrastructure</module><module>service</module></modules><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.atguigu</groupId><artifactId>acl_parent</artifactId><packaging>pom</packaging><version>0.0.1-SNAPSHOT</version><name>acl_parent</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><mybatis-plus.version>3.0.5</mybatis-plus.version><velocity.version>2.0</velocity.version><swagger.version>2.7.0</swagger.version><jwt.version>0.7.0</jwt.version><fastjson.version>1.2.28</fastjson.version><gson.version>2.8.2</gson.version><json.version>20170516</json.version><cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version></properties><dependencyManagement><dependencies><!--Spring Cloud--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>Hoxton.RELEASE</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${cloud-alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!--mybatis-plus 持久层--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>${velocity.version}</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>${gson.version}</version></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>${swagger.version}</version></dependency><!--swagger ui--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>${swagger.version}</version></dependency><!-- JWT --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jwt.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId><version>${json.version}</version></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
【2】common模块
common模块pom.xml
<?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"><parent><artifactId>acl_parent</artifactId><groupId>com.atguigu</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>common</artifactId><packaging>pom</packaging><modules><module>service_base</module><module>spring_security</module></modules><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided </scope></dependency><!--mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><scope>provided </scope></dependency><!--lombok用来简化实体类:需要安装lombok插件--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided </scope></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><scope>provided </scope></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><scope>provided </scope></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version></dependency></dependencies>
</project>
【3】common模块->SpringSecurity子模块
(1)核心配置类
SpringSecurity的核心配置就是继承WebSecurityConfigurerAdapter并注解@EnableWebSecurity的配置。这个配置指明了用户名密码的处理方式、请求路径、登录登出控制等和安全相关的配置。
package com.atguigu.security.config;import com.atguigu.security.filter.TokenAuthFilter;
import com.atguigu.security.filter.TokenLoginFilter;
import com.atguigu.security.security.DefaultPasswordEncoder;
import com.atguigu.security.security.TokenLogoutHandler;
import com.atguigu.security.security.TokenManager;
import com.atguigu.security.security.UnauthEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {private TokenManager tokenManager;private RedisTemplate redisTemplate;private DefaultPasswordEncoder defaultPasswordEncoder;private UserDetailsService userDetailsService;@Autowiredpublic TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,TokenManager tokenManager, RedisTemplate redisTemplate) {this.userDetailsService = userDetailsService;this.defaultPasswordEncoder = defaultPasswordEncoder;this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}/*** 配置设置* @param http* @throws Exception*///设置退出的地址和token,redis操作地址@Overrideprotected void configure(HttpSecurity http) throws Exception {http.exceptionHandling().authenticationEntryPoint(new UnauthEntryPoint())//没有权限访问.and().csrf().disable().authorizeRequests().anyRequest().authenticated().and().logout().logoutUrl("/admin/acl/index/logout")//退出路径.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)).addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();}//调用userDetailsService和密码处理@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);}//不进行认证的路径,可以直接访问@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/api/**");}
}
(2)实体类
package com.atguigu.security.entity;import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;@Data
public class SecurityUser implements UserDetails {//当前登录用户private transient User currentUserInfo;//当前权限private List<String> permissionValueList;public SecurityUser() {}public SecurityUser(User user) {if (user != null) {this.currentUserInfo = user;}}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {Collection<GrantedAuthority> authorities = new ArrayList<>();for(String permissionValue : permissionValueList) {if(StringUtils.isEmpty(permissionValue)) continue;SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);authorities.add(authority);}return authorities;}
}
package com.atguigu.security.entity;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.io.Serializable;@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "微信openid")private String username;@ApiModelProperty(value = "密码")private String password;@ApiModelProperty(value = "昵称")private String nickName;@ApiModelProperty(value = "用户头像")private String salt;@ApiModelProperty(value = "用户签名")private String token;}
(3)过滤器
package com.atguigu.security.filter;import com.atguigu.security.security.TokenManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;public class TokenAuthFilter extends BasicAuthenticationFilter {private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenAuthFilter(AuthenticationManager authenticationManager,TokenManager tokenManager,RedisTemplate redisTemplate) {super(authenticationManager);this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {//获取当前认证成功用户权限信息UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);//判断如果有权限信息,放到权限上下文中if(authRequest != null) {SecurityContextHolder.getContext().setAuthentication(authRequest);}chain.doFilter(request,response);}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {//从header获取tokenString token = request.getHeader("token");if(token != null) {//从token获取用户名String username = tokenManager.getUserInfoFromToken(token);//从redis获取对应权限列表List<String> permissionValueList = (List<String>)redisTemplate.opsForValue().get(username);Collection<GrantedAuthority> authority = new ArrayList<>();for(String permissionValue : permissionValueList) {SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);authority.add(auth);}return new UsernamePasswordAuthenticationToken(username,token,authority);}return null;}}
package com.atguigu.security.filter;import com.atguigu.security.entity.SecurityUser;
import com.atguigu.security.entity.User;
import com.atguigu.security.security.TokenManager;
import com.atguigu.utils.utils.R;
import com.atguigu.utils.utils.ResponseUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {private TokenManager tokenManager;private RedisTemplate redisTemplate;private AuthenticationManager authenticationManager;public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {this.authenticationManager = authenticationManager;this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;this.setPostOnly(false);this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));}//1 获取表单提交用户名和密码@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {//获取表单提交数据try {User user = new ObjectMapper().readValue(request.getInputStream(), User.class);return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>()));} catch (IOException e) {e.printStackTrace();throw new RuntimeException();}}//2 认证成功调用的方法@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)throws IOException, ServletException {//认证成功,得到认证成功之后用户信息SecurityUser user = (SecurityUser)authResult.getPrincipal();//根据用户名生成tokenString token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());//把用户名称和用户权限列表放到redisredisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());//返回tokenResponseUtil.out(response, R.ok().data("token",token));}//3 认证失败调用的方法protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)throws IOException, ServletException {ResponseUtil.out(response, R.error());}
}
(4)security
package com.atguigu.security.security;import com.atguigu.utils.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;@Component
public class DefaultPasswordEncoder implements PasswordEncoder {public DefaultPasswordEncoder() {this(-1);}public DefaultPasswordEncoder(int strength) {}//进行MD5加密@Overridepublic String encode(CharSequence charSequence) {return MD5.encrypt(charSequence.toString());}//进行密码比对@Overridepublic boolean matches(CharSequence charSequence, String encodedPassword) {return encodedPassword.equals(MD5.encrypt(charSequence.toString()));}
}
package com.atguigu.security.security;import com.atguigu.utils.utils.R;
import com.atguigu.utils.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//退出处理器
public class TokenLogoutHandler implements LogoutHandler {private TokenManager tokenManager;private RedisTemplate redisTemplate;public TokenLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate) {this.tokenManager = tokenManager;this.redisTemplate = redisTemplate;}@Overridepublic void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {//1 从header里面获取token//2 token不为空,移除token,从redis删除tokenString token = request.getHeader("token");if(token != null) {//移除tokenManager.removeToken(token);//从token获取用户名String username = tokenManager.getUserInfoFromToken(token);redisTemplate.delete(username);}ResponseUtil.out(response, R.ok());}
}
package com.atguigu.security.security;import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;import java.util.Date;@Component
public class TokenManager {//token有效时长private long tokenEcpiration = 24*60*60*1000;//编码秘钥private String tokenSignKey = "123456";//1 使用jwt根据用户名生成tokenpublic String createToken(String username) {String token = Jwts.builder().setSubject(username).setExpiration(new Date(System.currentTimeMillis()+tokenEcpiration)).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}//2 根据token字符串得到用户信息public String getUserInfoFromToken(String token) {String userinfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();return userinfo;}//3 删除tokenpublic void removeToken(String token) { }
}
package com.atguigu.security.security;import com.atguigu.utils.utils.R;
import com.atguigu.utils.utils.ResponseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class UnauthEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {ResponseUtil.out(httpServletResponse, R.error());}
}
【4】common模块->service_base
(1)RedisConfig
package com.atguigu.utils;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;@EnableCaching //开启缓存
@Configuration //配置类
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();RedisSerializer<String> redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);template.setConnectionFactory(factory);//key序列化方式template.setKeySerializer(redisSerializer);//value序列化template.setValueSerializer(jackson2JsonRedisSerializer);//value hashmap序列化template.setHashValueSerializer(jackson2JsonRedisSerializer);return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置序列化(解决乱码的问题),过期时间600秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}
}
(2)SwaggerConfig
package com.atguigu.utils;import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration//配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {@Beanpublic Docket webApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select()//.paths(Predicates.not(PathSelectors.regex("/admin/.*"))).paths(Predicates.not(PathSelectors.regex("/error.*"))).build();}private ApiInfo webApiInfo(){return new ApiInfoBuilder().title("网站-课程中心API文档").description("本文档描述了课程中心微服务接口定义").version("1.0").contact(new Contact("java", "http://atguigu.com", "1123@qq.com")).build();}
}
(3)工具类
package com.atguigu.utils.utils;import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public final class MD5 {public static String encrypt(String strSrc) {try {char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f' };byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();int j = bytes.length;char[] chars = new char[j * 2];int k = 0;for (int i = 0; i < bytes.length; i++) {byte b = bytes[i];chars[k++] = hexChars[b >>> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出错!!+" + e);}}public static void main(String[] args) {System.out.println(MD5.encrypt("111111"));}
}
package com.atguigu.utils.utils;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class ResponseUtil {public static void out(HttpServletResponse response, R r) {ObjectMapper mapper = new ObjectMapper();response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);try {mapper.writeValue(response.getWriter(), r);} catch (IOException e) {e.printStackTrace();}}
}
package com.atguigu.utils.utils;import lombok.Data;
import java.util.HashMap;
import java.util.Map;//统一返回结果的类
@Data
public class R {private Boolean success;private Integer code;private String message;private Map<String, Object> data = new HashMap<String, Object>();//把构造方法私有private R() {}//成功静态方法public static R ok() {R r = new R();r.setSuccess(true);r.setCode(20000);r.setMessage("成功");return r;}//失败静态方法public static R error() {R r = new R();r.setSuccess(false);r.setCode(20001);r.setMessage("失败");return r;}public R success(Boolean success){this.setSuccess(success);return this;}public R message(String message){this.setMessage(message);return this;}public R code(Integer code){this.setCode(code);return this;}public R data(String key, Object value){this.data.put(key, value);return this;}public R data(Map<String, Object> map){this.setData(map);return this;}
}
【5】gateway模块
(1)pom.xml
<?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"><parent><artifactId>infrastructure</artifactId><groupId>com.atguigu</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>api_gateway</artifactId><dependencies><dependency><groupId>com.atguigu</groupId><artifactId>service_base</artifactId><version>0.0.1-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--gson--><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency><!--服务调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies>
</project>
(2)application.properties
# 端口号
server.port=8222
# 服务名
spring.application.name=service-gateway
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 使用服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true# 配置路由规则
spring.cloud.gateway.routes[0].id=service-acl
# 设置路由uri lb://注册服务名称
spring.cloud.gateway.routes[0].uri=lb://service-acl
# 具体路径规则
spring.cloud.gateway.routes[0].predicates= Path=/*/acl/**
(3)解决跨域
package com.atguigu.gateway.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;@Configuration
public class CorsConfig {//解决跨域@Beanpublic CorsWebFilter corsWebFilter() {CorsConfiguration config = new CorsConfiguration();config.addAllowedMethod("*");config.addAllowedOrigin("*");config.addAllowedHeader("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());source.registerCorsConfiguration("/**",config);return new CorsWebFilter(source);}
}
【八】ruoyi平台整合SpringSecurity案例【待完成】
相关文章:

【SpringSecurity】springboot整合SpringSecurity实现登录校验与权限认证
【SpringSecurity】springboot整合SpringSecurity实现登录校验与权限认证 【一】SpringSecurity框架简介【二】SpringSecurity与shiro【1】SpringSecurity特点【2】shiro特点【3】SpringSecurity和shiro总结 【三】SpringSecurity过滤器【1】SpringSecurity中常见的过滤器【2】…...

【HarmonyOS Next】鸿蒙应用公钥和证书MD5指纹的获取
【HarmonyOS Next】鸿蒙应用公钥和证书MD5指纹的获取 一、问题背景 政府的icp备案时,或者某些三方SDK以来的管理后台,都需要配置鸿蒙应用的公钥和证书MD5指纹 二、解决方案 专有名词解释: 华为AppGallery Connect简称 AGC平台࿰…...
父组件用的是原生监听,子组件用的是onClick,子组件添加了stopPropagation还是没有阻止传播
父组件用事件监听,子组件用onClick,即使子组件加了stopPropagation还是没有阻止冒泡。父组件可能使用原生的addEventListener来绑定事件,而子组件用的是React的onClick事件。这时候,虽然子组件调用了e.stopPropagation()ÿ…...

ui设计公司兰亭妙微分享:科研单位UI界面设计
科研单位的UI界面设计是一项至关重要的任务,它不仅关乎科研工作的效率,还直接影响到科研人员的用户体验。以下是对科研单位UI界面设计的详细分析: 一、设计目标 科研单位的UI界面设计旨在提升科研工作的效率与便捷性,同时确保科…...

python绘制年平均海表温度、盐度、ph分布图
python绘制年平均海表温度、盐度、ph图 文章目录 python绘制年平均海表温度、盐度、ph分布图前言一、数据准备二、代码编写2.1. python绘制年平均海表温度(主要)2.2. python绘制年平均海表盐度(选看)2.3. python绘制年平均海表ph&…...

windows中kafka集群部署示例
注意 kafka包路径不要太长,不然启动时候 这里再单独下个zookeeper做为三个kafka实例broker的注册中心 修改Zookeeper配置文件 脚本内容 call bin/zkServer.cmd 不然的话就进bin目录双击zkServer.cmd 配置Zookeeper的另外一种方式 用Kafka自带的zookeeper 例如我复制一份 …...
获取GitHub的OAuth2的ClientId和ClientSecrets
获取 GitHub OAuth2 登录所需的 client-id 和 client-secret 登录 GitHub:使用你的 GitHub 账号登录到 GitHub。访问开发者设置:点击右上角的头像,选择 Settings,然后在左侧导航栏中选择 Developer settings。创建新的 OAuth 应用…...
self-attention部分代码注释
多头注意力机制(Multi-Head Attention, MHA),是 Transformer 模型的核心组件之一。以下是对代码的逐行解析和详细说明: attention-is-all-you-need-pytorch-master\transformer\SubLayers.py class MultiHeadAttention(nn.Mo…...
idea里的插件spring boot helper 如何使用,有哪些强大的功能,该如何去习惯性的运用这些功能
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
常用的配置文件格式对比(ini,toml,yaml,json,env,settings.py)及应用程序修改自身配置并保留注释
代码与环境配置解耦 git分支的代码应做到“环境无关”:代码本身不硬编码任何环境特定的配置(如数据库连接、密钥、API地址),而是通过外部机制动态注入。 配置与代码分离:将配置信息存储在代码库之外(如环…...
Java IO 和 NIO 的基本概念和 API
一、 Java IO (Blocking IO) 基本概念: Java IO 是 Java 平台提供的用于进行输入和输出操作的 API。Java IO 基于 流 (Stream) 的模型,数据像水流一样从一个地方流向另一个地方。Java IO 主要是 阻塞式 I/O (Blocking I/O),即线程在执行 I/O …...

小智AI桌宠机器狗
本文主要介绍如何利用开源小智AI制作桌宠机器狗 1 源码下载 首先下载小智源码,下载地址, 下载源码后,使用vsCode打开,需要在vscode上安装esp-idf,安装方式请自己解决 2 源码修改 2.1添加机器狗控制代码 在目录main/iot/things下添加dog.cc文件,内容如下; #include…...
MySQL 入门“鸡”础
一、Win10 与Ubuntu安装 以下是一篇针对 Ubuntu 安装 MySQL 的过程中写的示例: --- # Ubuntu 安装 MySQL 详细指南 在本教程中,我们将向您展示如何在 Ubuntu 上安装 MySQL,并完成基本的安全配置。以下是具体步骤: # 1. 安装 …...
Redis 中有序集合(Sorted Set)的使用方法
文章目录 前言1. 有序集合的特点2. 常用命令2.1 添加元素(ZADD)2.2 获取元素分数(ZSCORE)2.3 获取元素排名(ZRANK / ZREVRANK)2.4 获取范围内的元素(ZRANGE / ZREVRANGE)2.5 获取分数…...

WIn32 笔记:本专栏课件
专栏导航 上一篇:在VS2019里面,调整代码字体大小 回到目录 下一篇:无 本节前言 在之前的讲解里面,我讲解了 Visual Studio 软件的一些个基础操作步骤。从本节开始,我们进入预备章。 本节内容,属于是 …...
Unity git 获取当前修改或者新增的文件列表
直接上代码 using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text.RegularExpressions; using UnityEngine;public class GitFileStatusCheckerTools : MonoBehaviour {// 获取Git变更文件列表(新增/修…...
结构型模式 - 桥接模式 (Bridge)
结构型模式 - 桥接模式 (Bridge) 桥接模式是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立地变化。 // 软件接口,作为实现部分 interface Software {void run(); }// 游戏软件类,实现 Software 接口 class Game…...

如何让传统制造企业从0到1实现数字化突破?
随着全球制造业不断向智能化、数字化转型,传统制造企业面临着前所未有的机遇与挑战。数字化转型不仅是技术的革新,更是管理、文化、业务流程等全方位的变革。从零开始,如何带领一家传统制造企业走向数字化突破,是许多企业领导者面…...
【Elasticsearch】script_fields 和 runtime_fields的区别
script_fields和runtime_fields都是 Elasticsearch 中用于动态计算字段值的功能,但它们在实现方式、应用场景和性能表现上存在显著区别。以下是两者的详细对比: 1.定义和应用场景 • script_fields: • 定义:通过 Painless 脚本…...

城电科技|会追日的智能花,光伏太阳花开启绿色能源新篇章
当艺术与科技相遇,会碰撞出怎样的火花?城电科技推出的光伏太阳花,以其独特的设计与智能化的功能,给出了答案。这款产品不仅具备太阳能发电的实用功能,更是一件充满科技属性的艺术性光伏产品,吸引了广泛关注…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...

向量几何的二元性:叉乘模长与内积投影的深层联系
在数学与物理的空间世界中,向量运算构成了理解几何结构的基石。叉乘(外积)与点积(内积)作为向量代数的两大支柱,表面上呈现出截然不同的几何意义与代数形式,却在深层次上揭示了向量间相互作用的…...

ZYNQ学习记录FPGA(二)Verilog语言
一、Verilog简介 1.1 HDL(Hardware Description language) 在解释HDL之前,先来了解一下数字系统设计的流程:逻辑设计 -> 电路实现 -> 系统验证。 逻辑设计又称前端,在这个过程中就需要用到HDL,正文…...

Linux【5】-----编译和烧写Linux系统镜像(RK3568)
参考:讯为 1、文件系统 不同的文件系统组成了:debian、ubuntu、buildroot、qt等系统 每个文件系统的uboot和kernel是一样的 2、源码目录介绍 目录 3、正式编译 编译脚本build.sh 帮助内容如下: Available options: uboot …...

运动控制--BLDC电机
一、电机的分类 按照供电电源 1.直流电机 1.1 有刷直流电机(BDC) 通过电刷与换向器实现电流方向切换,典型应用于电动工具、玩具等 1.2 无刷直流电机(BLDC) 电子换向替代机械电刷,具有高可靠性,常用于无人机、高端家电…...

[KCTF]CORE CrackMe v2.0
这个Reverse比较古老,已经有20多年了,但难度确实不小。 先查壳 upx压缩壳,0.72,废弃版本,工具无法解压。 反正不用IDA进行调试,直接x32dbg中,dump内存,保存后拖入IDA。 这里说一下…...