【Spring Security框架解析】

embedded/2024/12/5 3:33:21/

文章目录

  • Spring-security介绍
  • Spring-security认证授权流程
    • 认证流程
    • Security流程
    • 认证过滤器实现
    • 获取UserDetail信息
  • 配置Security

Spring-security介绍

Spring Security是一个功能强大且高度可定制的Java安全框架,用于保护基于Spring的应用程序。它提供了全面的安全服务,包括认证(Authentication)、授权(Authorization)、防止CSRF等。

  1. 认证(Authentication)
    认证是确认用户身份的过程。Spring Security支持多种认证机制,如表单登录、HTTP基本认证、OAuth2、LDAP等。
  2. 授权(Authorization)
    授权是确定用户是否有权限访问特定资源的过程。Spring Security提供了基于角色的访问控制(RBAC)和基于属性的访问控制(ABAC)。
  3. 防止CSRF攻击
    Spring Security自动保护你的应用程序免受跨站请求伪造(CSRF)攻击。
  4. 会话管理
    提供会话固定攻击的保护,并支持会话超时和并发会话控制。
  5. 输入验证
    防止常见的安全漏洞,如SQL注入和跨站脚本(XSS)。
  6. 方法级安全性
    使用Spring AOP,你可以在方法级别上实现安全性控制,例如,只有具有特定角色的用户才能访问特定的方法。
  7. 异常处理
    提供了一个异常处理机制,允许你自定义安全相关的异常处理。
  8. 集成Spring Boot
    Spring Security与Spring Boot集成良好,提供了自动配置和简化的配置选项。
  9. 支持多种认证提供者
    可以与各种认证提供者(如数据库、LDAP、OAuth2提供者等)集成。
  10. 自定义认证和授权
    Spring Security允许你自定义认证和授权逻辑,以满足特定需求。
  11. 单点登录(SSO)
    支持单点登录解决方案,允许用户使用一组凭据访问多个应用程序。
  12. 安全通信
    支持HTTPS和其他安全通信协议,以保护数据传输。

Spring-security认证授权流程

认证流程

在这里插入图片描述
Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。
AuthenticationManager接口:定义了认证authenticate()的方法
UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

Security流程

实现步骤:
1.构建一个自定义的service接口,实现SpringSecurity的UserDetailService接口。
2.建一个service实现类,实现此loadUserByUsername方法。
3.调用登录的login接口,会经过authenticationManager.authenticate(authenticationToken)方法。此方法会调用loadUserByUsername方法。
4.方法内部做用户信息的查询,判断用户名和密码是否正确,这是第一道认证。
5.如果没有查到信息就抛出异常。
6.如果查到信息了再接着查用户的权限信息,返回权限信息到SysUser实体。
7.此实体实现了SpringSecurity自带的userDetail接口。实现了getAuthorities方法。
8.每次查询权限都会调用此方法。
9.查询到的权限,会被返回到login接口。进行后续操作。
10.如果认证通过,通过身份信息中的userid生产一个jwt。
11.把完整的用户信息作为value,token作为key存入redis。

认证过滤器实现

代码入口 通过继承UsernamePasswordAuthenticationFilter过滤器来实现认证。

java">@Slf4j
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {private final AuthenticationManager authenticationManager;public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {this.authenticationManager = authenticationManager;super.setFilterProcessesUrl("/user/login");}/*** 设置登录方式的认证实体*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {log.info("<==============登录认证开始===============>");SysUser user;Authentication authenticate;try {//获取用户传送的信息user = new ObjectMapper().readValue(request.getInputStream(), SysUser.class);//参数校验if (user == null) {throw new BusinessException("登录信息不能为空");}if(!StringUtils.hasText(user.getLoginTabs())){throw new BusinessException("登录类型不能为空");}LoginEnum value = LoginEnum.getValue(user.getLoginTabs());if(value == null){throw new BusinessException("登录类型未匹配");}//登录认证不需要抽离 只依赖Security 鉴权 目前只有手机号和账密登录后续自行叠加switch (value){case SYS_LOG_TYPE_1:authenticate = authenticationManager.authenticate(new UserDetailAuthenticationToken(user));break;case SYS_LOG_TYPE_2:authenticate = authenticationManager.authenticate(new MobileCodeAuthenticationToken(user.getPhone(), user.getPhoneCode()));break;default:throw new BusinessException("登录类型错误");}return authenticate;} catch (Exception e) {log.error("登录认证失败:",e);throw new BadCredentialsException(e.getMessage());}}/**认证成功生成token并返回*/@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {log.info("<==============登录认证成功===============>");JWTResponseUtils.successfulAuthentication(request, response,chain, authResult);}/**认证失败,返回失败原因*/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {log.info("<==============登录认证失败:{}===============>", failed.getMessage());JWTResponseUtils.unsuccessfulAuthentication(request, response, failed);}

获取UserDetail信息

java">*** 账号认证器处理器只监听UserDetailAuthenticationToken*/
@Slf4j
@SuppressWarnings("all")
public class UserDetailAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {private final UserDetailsService userDetailsService;private final RedisKeyUtil redisKeyUtil;private final PasswordEncoder passwordEncoder;public UserDetailAuthenticationProvider(UserDetailsService userDetailsService, RedisKeyUtil redisKeyUtil, PasswordEncoder passwordEncoder){this.userDetailsService = userDetailsService;this.redisKeyUtil = redisKeyUtil;this.passwordEncoder = passwordEncoder;}@Overrideprotected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {UserDetailAuthenticationToken tokenReq = (UserDetailAuthenticationToken) authentication;String loginElement = Constants.STATE_INVALID;try{//获取是否开启登录要素校验loginElement = redisKeyUtil.get(RedisEnums.REDIS_LOGIN_ELEMENT.getCode());log.info("查看登录要素入参: key = "+RedisEnums.REDIS_LOGIN_ELEMENT.getCode()+"; value = "+loginElement);}catch (Exception exception){log.error("查看登录要素出现异常,入参: key = "+RedisEnums.REDIS_LOGIN_ELEMENT.getCode()+"; exception: ",exception);}log.info("<==============账号认证处理器处理开始===============>");try {// 根据账号,获取登录人员信息UserDetails userDetails = userDetailsService.loadUserByUsername(tokenReq.getUsername());//储存用户信息必须是SecurityUtils类SecurityUtils securityUtils = (SecurityUtils)userDetails;if(Constants.STATE_EFFECTIVE.equals(loginElement)){if(!StringUtils.hasText(tokenReq.getPhone())){throw new BusinessException("手机号不能为空");}if(!StringUtils.hasText(tokenReq.getPhoneCode())){throw new BusinessException("验证码不能为空");}String redisPhoneCode = redisKeyUtil.get(RedisEnums.REDIS_LOGIN_KEY.getCode() + tokenReq.getPhone());if(!StringUtils.hasText(redisPhoneCode)){throw new BusinessException("验证码已过期,请重新获取验证码");}if(!tokenReq.getPhoneCode().equals(redisPhoneCode)){throw new BusinessException("验证码不正确,请重新填写");}if(!tokenReq.getPhone().equals(securityUtils.getPhone())){throw new BusinessException("用户手机号绑定不正确,请重新填写");}}boolean matches = passwordEncoder.matches(tokenReq.getPassword(), securityUtils.getPassword());if(!matches){throw new BusinessException("密码不正确,请重新填写");}UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());log.info("<==============账号认证处理器处理结束===============>");return usernamePasswordAuthenticationToken;} catch (BusinessException e) {log.error("账号登录出现可控异常:"+e.getMessage());throw new BadCredentialsException(e.getMessage());} catch (Exception e) {log.error("账号登录出现不可控异常:",e);throw new BadCredentialsException("账号登录认证异常");}}@Overrideprotected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {return null;}@Overridepublic boolean supports(Class<?> authentication) {return (UserDetailAuthenticationToken.class.isAssignableFrom(authentication));}}

配置Security

java">/*** Security配置*/
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 业务逻辑bean*/@Autowiredprivate SysUserService sysUserService;/*** redis操作工具类*/@Autowired(required = false)private RedisKeyUtil redisKeyUtil;/*** 密码方式为加密设置*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 跨域设置*/@BeanCorsConfigurationSource corsConfigurationSource() {final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());return source;}/*** 账号密码认证设置*/@Bean(name = "userDetailsService")public UserDetailsService userDetailsService() {return new UserDetailsServiceImpl(sysUserService);}/*** 手机验证码认证设置*/@Bean(name ="mobileUserDetailsServiceImpl")public UserDetailsService mobileUserDetailsServiceImpl() {return new MobileUserDetailsServiceImpl(sysUserService);}/*** 账号验证码认证提供者设置*/@Beanpublic UserDetailAuthenticationProvider userDetailAuthenticationProvider() {return new UserDetailAuthenticationProvider(userDetailsService(),redisKeyUtil,passwordEncoder());}/*** 手机验证码认证提供者设置*/@Beanpublic MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider() {return new MobileCodeAuthenticationProvider(mobileUserDetailsServiceImpl(),redisKeyUtil);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//自定义认证处理器//手机号认证auth.authenticationProvider(mobileCodeAuthenticationProvider());//账密认证auth.authenticationProvider(userDetailAuthenticationProvider());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().authorizeRequests().anyRequest().authenticated().and().addFilter(new JWTAuthenticationFilter(authenticationManager())).addFilter(new JWTAuthorizationFilter(authenticationManager()))// 不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint()).accessDeniedHandler(new JWTAccessDeniedHandler()).and().logout().logoutUrl("/user/logout").logoutSuccessHandler(new JWTLogoutSuccessHandler());}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/auth/**").antMatchers("/api/**").antMatchers("/ws/**").antMatchers("/export/**").antMatchers("/accBalChk/**").antMatchers("/monAcc/**").antMatchers("/doc.html", "/doc.html/**", "/webjars/**", "/v2/**", "/swagger-resources", "/swagger-resources/**", "/swagger-ui.html", "/swagger-ui.html/**");}}

http://www.ppmy.cn/embedded/143072.html

相关文章

基于Matlab实现三维点坐标生成点云(源码+数据)

在MATLAB中生成三维点云是一项常见的任务&#xff0c;特别是在计算机视觉、几何建模和数据分析等领域。点云是由一组三维坐标&#xff08;x, y, z&#xff09;组成的集合&#xff0c;可以用来表示物体的表面或者空间中的采样数据。本篇文章将深入探讨如何使用MATLAB生成并处理点…

面向对象(二)——类和对象(上)

1 类的定义 做了关于对象的很多介绍&#xff0c;终于进入代码编写阶段。 本节中重点介绍类和对象的基本定义&#xff0c;属性和方法的基本使用方式。 【示例】类的定义方式 // 每一个源文件必须有且只有一个public class&#xff0c;并且类名和文件名保持一致&#xff01; …

增删改查文档

列表 : 列表包含 : 模糊查找 分页 列表jsp页面 : 一 :导入外部文件 (举例 : 用户点进来就可以看到菜单,这是预加载属于,使用文档就绪函数实现) 二 : body 上 ① : 文档就绪函数 ${ function() //获取条件查询的字段 //组装对象 //调用文档就绪函数 } ② : 封装ajax方…

两个用来刷新Windows环境变量让会话即时生效的刷新脚本分享

环境变量刷新脚本&#xff1a;RefreshEnv.bat 和 RefreshEnv.ps1 在Windows系统中,环境变量对于程序的正常运行至关重要。当安装新软件或修改系统设置后,环境变量可能会发生变化,但这些变化通常需要重启命令提示符或PowerShell会话才能生效。为了解决这个问题,我们提供了两个脚…

Qt 面试题复习10~12_2024-12-2

Qt 面试题 28、Qt 如果一个信号的处理方法一直未被执行有哪些可能性29、Qt 三大核心机制30、虚函数表31、什么是Qt事件循环 &#xff1f;32、纯虚函数和普通的虚函数有什么区别33、Qt 的样式表是什么&#xff1f;34、描述Qt的TCP通讯流程35、自定义控件流程36、什么是Qt的插件机…

Elasticsearch 进阶

核心概念 索引(Index) 一个索引就是一个拥有几分相似特征的文档的集合。比如说&#xff0c;你可以有一个客户数据的索引&#xff0c;另一个产品目录的索引&#xff0c;还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母)&#xff0c;并且当我们要对这个索…

早鸟票开启:2025年计算机应用、图像处理与视觉算法国际学术会议(CAIPVA2025)

#学术会议早知道##早鸟价优惠# 2025年计算机应用、图像处理与视觉算法国际学术会议&#xff08;CAIPVA2025&#xff09; 2025 International Conference on Computer Applications, Image Processing, and Vision Algorithms 重要信息 会议地点&#xff1a;中国昆明 会议时…

Java 单例模式:深度解析与应用

在软件开发领域&#xff0c;设计模式是解决常见设计问题的有效方案&#xff0c;而单例模式作为创建型设计模式中的一员&#xff0c;其重要性不容小觑。它能够确保一个类仅有一个实例&#xff0c;并提供全局访问点&#xff0c;这一特性在资源管理、配置信息读取、线程池管理以及…