Java从坚持到精通-SpringSecurity

news/2024/5/18 14:33:37/文章来源:https://blog.csdn.net/weixin_44466906/article/details/137188537

1.安全框架是什么

安全框架的本质就是一堆过滤器的组成,目的在于保护系统资源,所以在到达资源之前会做一系列的验证工作,这些验证工作通过一系列的过滤器完成。安全框架通常的功能有认证、授权、防止常见的网络攻击,以此为核心拓展其他功能。比如session管理,密码加密,权限管理等功能。

2.常见的安全框架比较

Shiro

shiro是Apache下的一个开源安全框架,提供了身份验证、授权、密码学和会话管理等关于安全的核心功能。

SpringSecurity

SpringSecurity底层主要是基于Spring AOP和Servlet过滤器来实现安全控制,它提供了全面的安全解决方案,同时授权粒度可以在web请求级和方法调用级来处理身份确认和授权。

SpringSecurity的核心功能主要包括以下几个:

  • 认证:解决“你是谁”的问题->解决的是系统中是否有这个“用户”(用户/设备/系统)的问题,也就是我们常说的“登录”
  • 授权:权限控制/鉴别,解决的是系统中某个用户能够访问哪些资源,即“你能干什么”的问题。Spring Security支持基于URL的请求授权、方法访问授权、对象访问授权。
  • 防护攻击:防止身份伪造等各种攻击手段
  • 加密功能:对密码进行加密、匹配等
  • 会话功能:对Session进行管理
  • RememberMe功能:实现“记住我”功能,并可以实现token令牌持久化。

SpringSecurity与Shiro两者区别

  • SpringSecurity基于Spring开发,与SpringBoot、SpringCloud更容易集成
  • SpringSecurity拥有更多功能,如安全防护,对OAuth授权登录的支持
  • SpringSecurity拥有良好的扩展性,更容易自定义实现一些定制需求
  • SpringSecurity的社区资源比Shiro更丰富
  • Shiro相较于SpringSecurity更轻便,简单,使用流程更清晰,上手容易,反观SpringSecurity属于重量级,学习难度比Shiro高
  • Shiro不依赖其他框架可独立运行,而SpringSecurity需要已离开与Spring容器运行

Sa-Token

是一款国产安全框架,使用简单,轻便。文档清晰详细,内置多重功能。

3.使用SpringSecurity

1.导入坐标

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>

2.启动项目

启动项目之后,我们会发现控制台会输出一串密码,然后后面有很多过滤器的加载。

3.访问接口

访问接口会默认跳转到登录页,默认的用户名是user,然后密码就是之前在控制台输出的密码。

4.SecurityProperties类分析

如果我们没有指定用户名和密码,则默认会使用SecurityProperties里的User类生成默认用户名和密码,user和uuid生成的字符串。

然后类上使用了@ConfigurationProperties(prefix = "spring.security")注解说明了配置的对应关系,如果我们需要修改默认配置,则按照spring.security开始修改即可。

spring:security:user:name: adminpassword: 123456

如果对用户名和密码做了修改,控制台就不会输出密码信息了。

4.基于内存用户分析认证流程

我们需要定义一个配置类,并在类上加上@EnableWebSecurity注解,声明这是一个Security的配置类。我们需要new一个UserDetails对象,设置好用户名和密码,然后将这个对象放入到内存级的用户详情管理器中,启动项目即可。

@Configuration
// 标记为一个Security类,启用SpringSecurity的自定义配置
@EnableWebSecurity
public class SecurityConfig {// 自定义用户名和密码@Beanpublic UserDetailsService userDetailsService(){// 定义用户信息UserDetails adminUser = User.withUsername("zhangsan").password("{noop}111111").roles("admin", "user").build();UserDetails vipUser = User.withUsername("lisi").password("{noop}111111").roles("admin", "user").build();// 将用户存储到SpringSecurity中InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();// 创建两个用户userDetailsManager.createUser(adminUser);userDetailsManager.createUser(vipUser);return userDetailsManager;}}

 具体他会执行到loadUserByUsername这个方法中,查看用户名和密码,有的话就new出一个。

5.密码加密处理

我们可以在刚刚定义的配置类里面加上加密的配置

    @Beanpublic PasswordEncoder passwordEncoder(){// 构建密码编译器return new BCryptPasswordEncoder();}

只需声明一个加密解析器的实现类,这里使用BCryptPasswordEncoder,比较常用。

然后我们测试的时候,只需加载这个密码解析器类,调用相应的加密方法和匹配方法即可(不可解密)

@SpringBootTest
class SpringsecurityApplicationTests {@Autowiredprivate PasswordEncoder passwordEncoder;@Testvoid contextLoads() {String password = "123456";String encode = passwordEncoder.encode(password);System.out.println("生成的密码为:"+encode);boolean matches = passwordEncoder.matches(password, encode);System.out.println("密码是否匹配:"+matches);}}

6.获取登录用户信息的方法

    @GetMapping("/getLoginUser1")public Authentication getLoginUser1(Authentication authentication){return authentication;}@GetMapping("/getLoginUser2")public Principal getLoginUser2(Principal principal){return principal;}@GetMapping("/getLoginUser3")public Principal getLoginUser3(){// 通过安全上下文持有器获取安全上下文,再获取认证信息return SecurityContextHolder.getContext().getAuthentication();}

 以上三个对象Authentication继承了Principal,先返回Authentication对象后,会将这个对象再放入得到安全上下文对象中,然后从安全上下文持有器中获取认证信息。

7.权限和角色的问题

我们可以在配置类中定义用户的角色(role)和权限(authority),角色和权限在这里是一个意思,设置角色的时候,获取角色会在角色前面拼上“ROLE_”,而且角色和权限,谁在下面谁就会生效(覆盖了前面的)。

8.针对Url进行授权

首先,原来的配置类需要继承WebSecurityConfigurerAdapter抽象类,然后重写里面的configure方法

我们可以定义路径和权限的匹配规则,你访问某个路径时,需要查看你对应的权限,如果没有权限则无法访问,如果访问了这里面没有的配置路径,则不需要权限。

9.针对方法进行授权

首先要在配置类上面加上@EnableGlobalMethodSecurity(prePostEnabled = true)这个注解,说明要开启全局方法安全。

然后在方法上使用注解完成。

    @GetMapping("/getLoginUser1")@PreAuthorize("hasAuthority('admin')")public Authentication getLoginUser1(Authentication authentication){return authentication;}

10.登录成功或者失败返回json

1.登录成功

定义的配置类需要实现AuthenticationSuccessHandler接口,然后重写onAuthenticationSuccess方法,这里需要引入ObjectMapper,将字符串转成json然后输出。

    @Resourceprivate ObjectMapper objectMapper;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {HttpResult httpResult = HttpResult.builder().code(1).msg("登录成功").build();String responseJson = objectMapper.writeValueAsString(httpResult);response.setContentType("application/json;charset=utf-8");PrintWriter writer = response.getWriter();writer.println(responseJson);writer.flush();}

然后在原有设置了放行的方法中添加successHandler方法,将上面那个对象作为参数传进去(我这里因为是写在一个类当中,所以用了this)

 2.登录失败

与上面登录成功一样的配置,只是要实现AuthenticationFailureHandler这个接口。

3.退出登录

实现LogoutSuccessHandler接口

4.访问拒绝(没权限)

实现AccessDeniedHandler接口

然后在原有的配置方法中按如上所示调用。

11.基于数据库的认证

1.我们需要新建基本的5张表

2.我们后端使用mybatis,所以创建好基本的框架

service、dao等,并且做好配置

3.新建配置类,继承WebSecurityConfigurerAdapter,重写里面的configure方法。并在这里设置好密码的加密方式,我这里方便测试,直接配成明文的了,对应数据表中的数据也是明文存储的。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Beanpublic PasswordEncoder passwordEncoder(){// return new BCryptPasswordEncoder();// 测试时先用明文的return NoOpPasswordEncoder.getInstance();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated();http.formLogin().permitAll();}
}

4.创建一个实现类实现UserDetailService

@Service
public class SecurityUserDetailServiceImpl implements UserDetailsService {@Autowiredprivate SysUserService sysUserService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser sysUser = sysUserService.getByUserName(username);if(sysUser == null){throw new UsernameNotFoundException("该用户不存在");}SecurityUser securityUser = new SecurityUser(sysUser);return securityUser;}
}

 解析,注意,这里是springsecurity的核心判断登录的方法,他在登录成功之后,需要返回UserDails对象,所以我们创建了一个SecurityUser对象来实现UserDails,就可以正常返回了。在SecurityUser中我们定义一个属性SysUser,就是数据表中的sys_user对应的对象,将数据表中的数据通过构造方法进行传参并赋值springsecurity里的属性。

public class SecurityUser implements UserDetails {private final SysUser sysUser;public SecurityUser(SysUser sysUser){this.sysUser = sysUser;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return sysUser.getPassword();}@Overridepublic String getUsername() {return sysUser.getUsername();}@Overridepublic boolean isAccountNonExpired() {return sysUser.getAccountNoExpired().equals(1);}@Overridepublic boolean isAccountNonLocked() {return sysUser.getAccountNoLocked().equals(1);}@Overridepublic boolean isCredentialsNonExpired() {return sysUser.getCredentialsNoExpired().equals(1);}@Overridepublic boolean isEnabled() {return sysUser.getEnabled().equals(1);}
}

 这里其实还有一种方法,是SysUser直接实现UserDetails接口,但是这样SysUser会显得很长很臃肿,所以我们就采用构造方法中属性赋值的方法。

12.基于数据库的授权

1.我们根据sys_menu创建对应的实体类,service以及serviceImpl。

2.编写查询的核心sql语句(三表联查)

3.在SecurityUser类中新增权限集合属性,这里我们加上了@Data,所以不需要写权限集合的set方法

4.根据userId查询到用户的所有权限,并且设置到List<SimpleGrantedAuthority>集合中,然后设置权限集合即可

13.自定义登录界面(先不跨域)

1.先引入thymeleaf的依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>

 2.然后在templates目录下新建文件

3.创建控制器,指定跳转路径

@Controller
public class PageController {// 跳转到登录页面@GetMapping("to_login")public String toLogin(){System.out.println("跳转到登录页面");return "login";}}

4.配置表单信息

在http.formLogin()...后面配置登录相关的配置,比如登录的页面,登录的用户名密码,登录请求的接口,登录失败和成功的路径等。

5.配置退出信息

在如上所示图中,可以通过http.logout....配置退出成功的路径。

6.不跨域配置

禁用csrf,使用http.csrf().disabled()来禁用跨域,否则他们校验token,导致登录无法通过。

14.集成图片验证码

1.先加入hutool的依赖,里面可以使用验证码工具类相关方法

        <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.7</version></dependency>

2.创建一个验证码控制器,用于生成验证码,这里使用hutool的验证码工具类来生成验证码,并将生成的验证码放在session中,然后使用ImageIO类来返回验证码图片给前端

@Controller
public class CaptchaController {@GetMapping("/code/image")public void getCaptchaCode(HttpServletRequest request, HttpServletResponse response) throws IOException {CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(200, 100, 2, 20);String code = circleCaptcha.getCode();System.out.println("生成的图片验证码为:" + code);// 将验证码存储到session中request.getSession().setAttribute("CAPTCHA_CODE", code);ImageIO.write(circleCaptcha.getImage(), "jpeg", response.getOutputStream());}}

3.在springsecurity的主配置类中添加上验证码的请求路径,说明请求验证码是需要放行的

 4.前端登录页需要指定验证码的name和请求路径

 5.创建一个过滤器,需要继承OncePerRequestFilter抽象类,然后重写doFilterInternal方法,这里判断请求的路径,只有登录的接口需要验证码,别的接口直接放行。然后如果是登录接口,还需要校验验证码。

@Component
public class ValidateCodeFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 1.判断路径是否是/login/doLoginString requestURI = request.getRequestURI();// 如果不是登录请求,直接放行if(!requestURI.equals("/login/doLogin")){doFilter(request, response, filterChain);return;}validateCode(request, response, filterChain);}private void validateCode(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {// 2.从前端获取验证码String enterCode = request.getParameter("code");// 3.从session中获取验证码String captchaCodeInSession = (String) request.getSession().getAttribute("CAPTCHA_CODE");// 4.判断二者是否相等if(!enterCode.equalsIgnoreCase(captchaCodeInSession)){request.getSession().setAttribute("captcha_code_error", "验证码输入错误");response.sendRedirect("/toLogin");return;}// 删除session中的验证码值request.getSession().removeAttribute("CAPTCHA_CODE");doFilter(request, response, filterChain);}
}

6.最后,注入我们定义好的验证码过滤器,并将这个过滤器加到用户名密码过滤器之前执行。

 15.JWT

1.简介

jwt是Jason Web Token的缩写,用于网络安全传输,是一种好的传输方式。

jwt就是一个加密的带用户信息的字符串。

2.组成

一个jwt由三部分组成,各部分以点分隔:

  • Header(头部):base64Url编码的Json字符串
  • Playload(载荷):base64Url编码的Json字符串
  • Signature(签名):使用指定算法,通过Header和Payload加盐计算的字符串

举例:

3.使用jwt

1.添加jwt的依赖

        <dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.18.3</version></dependency>

2.创建工具类

public class JwtUtils {// 密钥private static final String SECRET = "secret888";public String createJwt(Integer userId, String username, List<String> authList){Map<String ,Object> headerClaims = new HashMap<>();headerClaims.put("alg", "HS256");headerClaims.put("typ", "JWT");return JWT.create().withHeader(headerClaims) // 设置头部.withIssuer("duolaimi") // 设置签发人.withIssuedAt(new Date()) // 设置签发时间.withExpiresAt(new Date(new Date().getTime() + 1000*60*2)) // 设置两个小时过期.withClaim("userId", userId) // 自定义属性.withClaim("userName", username) // 自定义属性.withClaim("userAuth", authList) // 自定义属性.sign(Algorithm.HMAC256(SECRET));// 签名并指定密钥}public boolean verifyToken(String jwtToken){try {JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();DecodedJWT decodedJWT = jwtVerifier.verify(jwtToken);Integer userId = decodedJWT.getClaim("userId").asInt();return true;}catch (Exception e){System.out.println("token验证不正确!");return false;}}}

这个工具类主要有两个方法,一个是创建jwt字符串,一个是验证jwt字符串。

创建jwt字符串时,可以使用JWT.create()方法,然后后面指定头部、签发人、签发时间等必要信息。

验证jwt时,需要使用JWT.require()指定加密方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_1045574.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IP地址中网络号的查看方法

IP地址是互联网中设备的标识&#xff0c;它由网络号和主机号两部分组成。网络号用于标识设备所连接的网络&#xff0c;而主机号则用于标识该网络中的具体设备。了解如何查看IP地址中的网络号对于网络管理员和需要进行网络配置的用户来说至关重要。虎观代理将介绍几种常见的查看…

linux之文件系统、inode和动静态库制作和发布

一、背景 1.没有被打开的文件都在磁盘上 --- 磁盘级文件 2.对磁盘级别的文件&#xff0c;我们的侧重点 单个文件角度 -- 这个文件在哪里&#xff0c;有多大&#xff0c;其他属性是什么&#xff1f; 站在系统角度 -- 一共有多少文件&#xff1f;各自属性在哪里&#xff1f…

Vue3---基础2(component)

主要讲解 component 的创建 以及vue插件的安装 Vue.js Devtools 为谷歌浏览器的Vue插件&#xff0c;可以在调试工具内查看组件的数据等 下载 有两种下载方式 1. 谷歌应用商店 打开Chrome应用商店去下载&#xff0c;这个方法需要魔法 2. 极简插件 极简插件官网_Chrome插件下载_…

react渲染列表信息(简单易学)

1.新建个文件夹&#xff0c;启动终端&#xff0c;使用create-react-app my-react命令创建项目&#xff0c;其中my-react是自定义项目名称。 2.删除根目录src文件夹下多余文件&#xff0c;保留index.js和index.css文件 3.安装scss需要的依赖&#xff0c;使用npm install --sav…

常见性能测试工具对比

在性能测试工作中&#xff0c;我们常常会遇到好几个工具&#xff0c;但是每一个工具都有自己的优势&#xff0c;一时间不知道怎么选择。 今天我们就将性能测试常用的工具进行对比&#xff0c;这样大家在选择工具的时候心里就有底啦&#xff01; 阿里云PTS 性能测试PTS&#xff…

【Linux】shell 脚本基础使用

在终端中输入命令可以完成一些常用的操作&#xff0c;但是我们都是一条一条输入命令&#xff0c;比较麻烦&#xff0c;为了解决这个问题&#xff0c;就会涉及到 shell 脚本&#xff0c;它可以将很多条命令放到一个文件里面&#xff0c;然后直接运行这个文件即可。 shell 脚本类…

【UnityRPG游戏制作】Unity_RPG项目之界面面板分离和搭建

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

【Qt】多线程

目录 一、常用API 二、线程安全 2.1 互斥锁 2.2 条件变量 2.3 信号量 在Qt中&#xff0c;多线程的处理一般是通过QThread类来实现 QThread代表一个在应用程序中可以独立控制的线程&#xff0c;也可以和进程中的其他线程共享数据。QThread对象管理程序中的一个控制线程 …

深入探索力扣第12题:整数转罗马数字的算法之旅

作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任大厂数据部门负责人。 会一些的技术&#xff1a;数据分析、算法、SQL、大数据相关、python 欢迎加入社区&#xff1a;码上找工作http://t.csdnimg.cn/Q59WX作者专栏每日更新&#xff1a; LeetCode解锁1000题: 打…

业务逻辑漏洞(靶场) fiddler

目录 fiddler简介&#xff1a; 业务逻辑漏洞&#xff1a; fiddler下载 靶场&#xff1a; 实验一 ​编辑实验二&#xff08;ps 更改实验url会变&#xff0c;fiddler没抓到东西看看代理改没改&#xff09; 实验三 实验四 fiddler简介&#xff1a; 一款网络抓包工具&#…

ht1622不显示无反应问题解决

如果你正在写ht1622 驱动时&#xff0c;怎么看程序都没问题&#xff0c;抓取波形&#xff0c;示波器分析波形&#xff0c;如果都没有问题&#xff0c;那么很大可能是硬件问题&#xff0c;检测看看 ht1622 RD是不是接地了。 RD 低会进入读取模式&#xff0c;所以不用RD 请将RD悬…

雄安建博会:中矿雄安新区的总部开工建设

中矿落位雄安&#xff1a;助力国家战略与新区发展 雄安新区&#xff0c;作为中国未来发展的重要战略支点&#xff0c;正迎来一系列央企总部的疏解与建设。最近&#xff0c;中国矿产资源集团有限公司&#xff08;简称“中矿”&#xff09;在雄安新区的总部项目正式开工建设&…

Jettison 1.8.7直装版 外部磁盘辅助弹出

Jettison 是一款适用于 macOS 的实用工具&#xff0c;旨在简化外部驱动器的管理。它可以自动卸载和重新挂载外部驱动器&#xff0c;帮助您更方便地使用和保护您的存储设备。 软件下载&#xff1a;Jettison 1.8.7直装版下载 自动卸载和重新挂载&#xff1a;Jettison 可以在您离开…

yolo预标注的txt转换成labelme中segment的json

前言 在yolo预标注的时候&#xff0c;想把保存的txt转换成labelme中segment的json&#xff0c;于是写了下面的脚本。 1.引入库 完整代码&#xff1a; import os import json from tqdm import tqdmdef get_image_size(image_path):from PIL import Imagewith Image.open(ima…

Spring boot如何执行单元测试?

Spring Boot 提供了丰富的测试功能&#xff0c;主要由以下两个模块组成&#xff1a; spring-boot-test&#xff1a;提供测试核心功能。spring-boot-test-autoconfigure&#xff1a;提供对测试的自动配置。 Spring Boot 提供了一个 spring-boot-starter-test一站式启动器&…

unipush+个推实现消息推送

1.注册个推平台的帐号个推&#xff0c;专业的数据智能服务商-为垂直领域提供数据智能解决方案 2.应用列表中选择新增应用/服务 3.填写下应用信息4.创建好应用后在manifest.json中的sdkConfigs配置上写入appid、appkey、appsecret "sdkConfigs" : {"ad" :…

硬件学习件Cadence day16 做个笔记,BOM 位号这个参数输出的两种情况。

1. BOM 中位号有3种情况 1. 一种是位号生成时多行&#xff0c;每行是固定的位数。&#xff08;如下图所示&#xff09; 2. 一种是位号生成时只有一行&#xff0c;但是可以使用表格中自动换行功能&#xff0c;给他换行&#xff0c;但是这个位号本质上只有一行&#xff0c;只是因…

基于深度学习的乳腺癌智能检测分割与诊断系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标分割、人工智能

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

虚拟网络设备的真正使命:实现有控制的通信

在数字化时代&#x1f4f2;&#xff0c;网络安全&#x1f512;成为了企业和个人防御体系中不可或缺的一部分。随着网络攻击的日益复杂和频繁&#x1f525;&#xff0c;传统的物理网络安全措施已经无法满足快速发展的需求。虚拟网络设备&#x1f5a7;&#xff0c;作为网络架构中…

一起学习python——基础篇(5)

今天讲一下python的数据类型。 数据类型主要分为文本类型、数值类型、序列类型、映射类型、集合类型、布尔类型、二进制类型六大类型。 文本类型&#xff1a;str 数值类型&#xff1a; int, float, complex 序列类型&#xff1a; list, tuple, range 映射类型&#xff1a;…