当前位置: 首页 > news >正文

Springboot与SpringSecurity使用(1):介绍、登录验证

一、介绍

        Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 SpringSecurity 重要核心功能。

        Spring Security进行认证和鉴权的时候,就是利用的一系列的Filter来进行拦截的。

        如图所示,一个请求想要访问到API就会从左到右经过虚线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。这里面重点关注两个过滤器:UsernamePasswordAuthenticationFilter负责登录认证,FilterSecurityInterceptor负责权限授权。

二、SpringSecurity入门

1、在项目中添加依赖

<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>

2、启动项目测试

        在浏览器访问:http://localhost:8080/doc.html,自动跳转到了登录页面。

        默认的用户名:user,密码在项目启动的时候在控制台会打印,注意每次启动的时候密码都会发生变化。

        当输入错误的帐号密码时,页面会出现提示。

        如果正确的话,则能正常访问:

        说明Spring Security默认安全保护生效。在实际开发中,这些默认的配置是不能满足我们需要的,我们需要扩展Spring Security组件,完成自定义配置,实现我们的项目需求。

三、用户认证

        用户认证流程:

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

1、用户认证核心组件

        我们系统中会有许多用户,确认当前是哪个用户正在使用我们系统就是登录认证的最终目的。在Spring Security中的体现就是 Authentication,它存储了认证信息,代表当前登录用户。我们在程序中如何获取并使用它呢?我们需要通过 SecurityContext 来获取Authentication,SecurityContext就是我们的上下文对象。这个上下文对象则是交由 SecurityContextHolder 进行管理,你可以在程序任何地方使用它:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

   Authentication中的信息:

  • Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象
  • Credentials:用户凭证,一般是密码
  • Authorities:用户权限

2、用户认证

        AuthenticationManager 就是Spring Security用于执行身份验证的组件,只需要调用它的authenticate方法即可完成认证。Spring Security默认的认证方式就是在UsernamePasswordAuthenticationFilter这个过滤器中进行认证的,该过滤器负责认证逻辑。Spring Security用户认证关键代码如下:

// 生成一个包含账号密码的认证信息
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, passwrod);
// AuthenticationManager校验这个认证信息,返回一个已认证的Authentication
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 将返回的Authentication存到上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);

        AuthenticationManager的校验逻辑非常简单:根据用户名先查询出用户对象(没有查到则抛出异常)将用户对象的密码和传递过来的密码进行校验,密码不匹配则抛出异常。其中使用了三个组件:

  • UserDetialsService接口只有一个方法loadUserByUsername(String username),通过用户名查询用户对象,默认实现是在内存中查询。
  • Spring Security中的用户数据则是由UserDetails来体现,该接口中提供了账号、密码等通用属性。
  • PasswordEncoder负责密码加密与校验。

        UserDetialsService、UserDetails、PasswordEncoder,这三个组件Spring Security都有默认实现,这一般是满足不了我们的实际需求的,所以这里我们自己来实现这些组件。

加密器PasswordEncoder

package com.ywz.security;import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.DigestUtils;import java.util.Arrays;/*** 类描述 -> 自定义md5加密** @Author: ywz* @Date: 2024/07/28*/
public class CustomMd5PasswordEncoder implements PasswordEncoder {/*** 方法描述 -> 对密码进行md5加密** @param rawPassword 未加密密码* @Return: @return {@link String }* @Author: ywz* @Date: 2024/07/28*/@Overridepublic String encode(CharSequence rawPassword) {return Arrays.toString(DigestUtils.md5Digest(rawPassword.toString().getBytes()));}/*** 方法描述 -> 判断密码是否匹配** @param rawPassword 未加密密码* @param encodedPassword 加密后的密码* @Return: @return boolean* @Author: ywz* @Date: 2024/07/28*/@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {return encodedPassword.equals(Arrays.toString(DigestUtils.md5Digest(rawPassword.toString().getBytes())));}}

用户对象UserDetails

该接口是用户对象,它提供了用户的一些通用属性,实际开发中我们的用户属性各种各样,这些默认属性可能是满足不了,所以我们一般会自己实现该接口,然后设置好我们实际的用户实体对象。实现此接口要重写很多方法比较麻烦,我们可以继承Spring Security提供的org.springframework.security.core.userdetails.User类,该类实现了UserDetails接口帮我们省去了重写方法的工作:

package com.ywz.security;import com.ywz.pojo.SysUser;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;import java.util.Collection;/*** 类描述 -> 自定义用户** @Author: ywz* @Date: 2024/07/28*/
@Setter
@Getter
public class CustomUser extends User {private SysUser sysUser;public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {super(sysUser.getUsername(), sysUser.getPassword(), authorities);this.sysUser = sysUser;}}

业务对象UserDetailsService

        该接口很简单只有一个方法,我们实现该接口,就完成了自己的业务:

package com.ywz.security.service;import com.ywz.pojo.SysUser;
import com.ywz.security.CustomUser;
import com.ywz.service.SysUserService;
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 javax.annotation.Resource;
import java.util.Collections;
import java.util.Objects;/*** 类描述 -> 实现UserDetailsService接口,重写方法** @Author: ywz* @Date: 2024/07/28*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService{@Resourceprivate SysUserService sysUserService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser sysUser = sysUserService.queryByUsername(username);if (Objects.isNull(sysUser)){throw new UsernameNotFoundException("用户名不存在!");}if(sysUser.getStatus() == 0) {throw new RuntimeException("账号已停用");}return new CustomUser(sysUser, Collections.emptyList());}
}

登录接口

        接下需要自定义登陆接口,然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。​ 认证成功的话要生成一个jwt,放入响应中返回。

package com.ywz.controller;import com.ywz.pojo.LoginVo;
import com.ywz.pojo.Result;
import com.ywz.service.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.Map;/*** 类描述 -> 登录控制器** @Author: ywz* @Date: 2024/07/28*/
@Api(tags = "系统管理-登录管理")
@RequestMapping("/admin/system/index")
@RestController
public class LoginController {@Resourceprivate SysUserService sysUserService;/*** 方法描述 -> 登录接口** @param loginVo -> 登录对象* @Return: @return {@link Result }<{@link Map }<{@link String },{@link Object }>>* @Author: ywz* @Date: 2024/07/28*/@ApiOperation("登录接口")@PostMapping("/login")public Result<Map<String,Object>> login(@RequestBody LoginVo loginVo){return sysUserService.login(loginVo);}
}

SecurityConfig配置

package com.ywz.security;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import java.util.Collections;/*** 类描述 -> SpringSecurity配置类** @Author: ywz* @Date: 2024/07/28*/
@Configuration
@EnableWebSecurity // 是开启SpringSecurity的默认行为
public class SecurityConfig {/*** 方法描述 -> 密码明文加密方式配置** @Return: @return {@link PasswordEncoder }* @Author: ywz* @Date: 2024/07/28*/@Beanpublic PasswordEncoder passwordEncoder(){return new CustomMd5PasswordEncoder();}/*** 方法描述 -> 获取AuthenticationManager(认证管理器),登录时认证使用** @param authenticationConfiguration 认证配置* @Return: @return {@link AuthenticationManager }* @Author: ywz* @Date: 2024/07/28*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}/*** 方法描述 -> 获取SecurityFilterChain(过滤器链),配置过滤器** @param http httpSecurity* @Return: @return {@link SecurityFilterChain }* @Author: ywz* @Date: 2024/07/28*/@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {return  http// 基于 token,不需要 csrf.csrf().disable()// 开启跨域以便前端调用接口.cors().and().authorizeRequests()// 指定某些接口不需要通过验证即可访问。登录接口肯定是不需要认证的.antMatchers("/admin/system/index/login").permitAll()// 静态资源,可匿名访问.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll().antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**","/doc.html").permitAll()// 这里意思是其它所有接口需要认证才能访问.anyRequest().authenticated().and()// 基于 token,不需要 session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// cors security 解决方案.cors().configurationSource(corsConfigurationSource()).and().build();}/*** 方法描述 -> 配置跨源访问(CORS)** @Return: @return {@link CorsConfigurationSource }* @Author: ywz* @Date: 2024/07/28*/@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.setAllowedHeaders(Collections.singletonList("*"));configuration.setAllowedMethods(Collections.singletonList("*"));configuration.setAllowedOrigins(Collections.singletonList("*"));configuration.setMaxAge(3600L);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration);return source;}}

执行登录

controller通过login方法调用实际业务

@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {@Resourceprivate SysMenuService sysMenuService;//通过AuthenticationManager的authenticate方法来进行用户认证,@Resourceprivate AuthenticationManager authenticationManager;@Overridepublic Result<Map<String, Object>> login(LoginVo loginVo) {// 将表单数据封装到 UsernamePasswordAuthenticationTokenUsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());// authenticate方法会调用loadUserByUsernameAuthentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);if(Objects.isNull(authenticate)){throw new RuntimeException("用户名或密码错误");}// 校验成功,强转对象CustomUser customUser = (CustomUser) authenticate.getPrincipal();SysUser sysUser = customUser.getSysUser();// 校验通过返回tokenString token = JwtUtil.createToken(sysUser.getId(), sysUser.getUsername());Map<String, Object> map = new HashMap<>();map.put("token",token);return Result.ok(map);}
}

认证过滤器

        我们需要自定义一个过滤器,这个过滤器会去获取请求头中的token,对token进行解析取出其中的信息,获取对应的LoginUser对象。然后封装Authentication对象存入SecurityContextHolder。

/*** 类描述 -> Jwt认证过滤器** @Author: ywz* @Date: 2024/07/28*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisCache redisCache;/*** 方法描述 -> 在请求之前进行过滤** @param request 请求* @param response 响应* @param filterChain 过滤器链* @Return:* @Author: ywz* @Date: 2024/07/28*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {//放行filterChain.doFilter(request, response);return;}//解析tokenString userid;try {Claims claims = JwtUtil.parseJWT(token);userid = claims.getSubject();} catch (Exception e) {e.printStackTrace();throw new RuntimeException("token非法");}//从redis中获取用户信息String redisKey = "login:" + userid;LoginUser loginUser = redisCache.getCacheObject(redisKey);if(Objects.isNull(loginUser)){throw new RuntimeException("用户未登录");}//存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser,null,null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【C#】 使用GDI+获取两个多边形区域相交、非相交区域
  • [数据集][目标检测]船上翻越栏杆危险行为检测数据集VOC+YOLO格式3678张1类别
  • 第13周 简历职位功能开发与Zookeeper实战
  • 4000元投影仪性价比之王:爱普生TW5750极米RS10还是当贝X5S?
  • 前端Long类型精度丢失:后端处理策略
  • 大数据学习之Flink基础
  • ChatGPT:如何在 linux 上运行 springboot 项目,不使用 jar 包的方式,直接编译运行源代码
  • Spring Cloud全解析:服务注册中心的多维度产品对比与优选指南
  • 网站如何实现HTTPS访问
  • 【Github】Github 上commit后 contribution 绿格子不显示 | Github绿格子 | Github贡献度不显示
  • ref函数
  • 英文文献翻译方法哪个好?高效率的翻译方法分享
  • Python中导入不同文件夹中的函数
  • 我们的前端开发逆天了!1 小时搞定了新网站,还跟我说 “不要钱”
  • Java 单元测试
  • [case10]使用RSQL实现端到端的动态查询
  • 【Amaple教程】5. 插件
  • Angular 响应式表单之下拉框
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • github指令
  • JavaScript学习总结——原型
  • Mybatis初体验
  • ReactNativeweexDeviceOne对比
  • webpack入门学习手记(二)
  • 程序员最讨厌的9句话,你可有补充?
  • 对象引论
  • 来,膜拜下android roadmap,强大的执行力
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 白色的风信子
  • mysql面试题分组并合并列
  • 积累各种好的链接
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • (2024,LoRA,全量微调,低秩,强正则化,缓解遗忘,多样性)LoRA 学习更少,遗忘更少
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (delphi11最新学习资料) Object Pascal 学习笔记---第13章第6节 (嵌套的Finally代码块)
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (三)Honghu Cloud云架构一定时调度平台
  • (四)JPA - JQPL 实现增删改查
  • (转)Google的Objective-C编码规范
  • (转)Mysql的优化设置
  • (转)创业的注意事项
  • .babyk勒索病毒解析:恶意更新如何威胁您的数据安全
  • .bat批处理(七):PC端从手机内复制文件到本地
  • .gitignore
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)
  • .NET大文件上传知识整理
  • .Net下使用 Geb.Video.FFMPEG 操作视频文件
  • /run/containerd/containerd.sock connect: connection refused
  • /ThinkPHP/Library/Think/Storage/Driver/File.class.php  LINE: 48
  • @RequestMapping用法详解
  • [3D游戏开发实践] Cocos Cyberpunk 源码解读-高中低端机性能适配策略
  • [AutoSAR 存储] 汽车智能座舱的存储需求
  • [C#]C#学习笔记-CIL和动态程序集