irpas技术客

spring secuity(三更草堂)_小白的天下

大大的周 2225

一、介绍

spring security是spring家族的一个安全管理框架,适用于大中型项目。 shiro也是一个安全管理框架,但他适用于小型项目。

安全框架主要做两件事: 认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

授权:判断登录后的用户是否有权限进行某个操作

二、快速入门

首先添加依赖,启动项目即可:

<!--安全框架springsecurity依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>

启动后,控制台会有登录密码,用户名为user

访问:

四、jwt(b站-编程不良人) 1.什么是jwt?

官网地址: https://jwt.io/introduction/

JWT是Json Web Token,也就是通过JSON的形式作为web应用中的令牌,用于在各方之间安全的将信息作为JSON对象传输,在传输过程中还可以完成数据加密、签名等相关处理。

2.JWT能做什么? 1.)授权

一旦用户登录以后,每个后续请求将包含JWT。单点登录是当今广泛使用jwt’的一项功能,因为他的开销很小,并且可以在不同的域中轻松使用。

2.)信息交换

在各方之间安全的传输信息,通过对jwt进行签名(使用公钥/私钥对),所以可以确保发件人是他们所说的人,此外由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改.

3.)session的缺点

我们知道,http协议是无状态的协议(不会保持用户的登录状态),为了防止每一次请求跳转的时候都要重新登录,前期我们是通过session来保持用户的登录状态。

但是通过session保持用户的登录状态有以下几个缺点: ①session是保存在服务器端的内存当中,随着登录用户的不断增多,服务器端需要的内存会比较大,造成成本增加。

②如果是分布式项目还要涉及到分布式session方案的设计。

③由于session是通过cookie传输sessionid来进行工作的,如果cookie被截取,用户很容易遭受到跨站请求伪造的攻击。

④在前后端分离的情况下,前端的请求会经过很多的中间件,每次请求转发都会到服务器验证,造成服务器压力增大

4.)基于jwt的认证流程和jwt认证的优势

认证流程:

①前端通过Web表单将自己的用户名和密码发送到后端的接口。 这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加 密的传输(https协议),从而避免敏感信息被嗅探。

②后端核对用户名和密码成功后,将用户的id等其他信息作为 JWT Payload(负载),将其与头部分别进行Base64编码拼接后 签名,形成一个JWT(Token)。形成的JWT就是一个形同lll.zzz.xxx 的字符串。 token head.payload.singurater ③ 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可 以将返回的结果保存在localStorage或sessionStorage上,退出登录 时前端删除保存的JWT即可。

④前端在每次请求时将JWT放入HTTP Header中的Authorization位。 (解决XSS和XSRF问题) HEADER

⑤后端检查是否存在,如存在验证JWT的有效性。例如,检查签名 是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。

⑥验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作, 返回相应结果。

优势: ①简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送, 因为数据量小,传输速度也很快

②自包含(Self-contained):负载中包含了所有用户所需要的信息, 避免了多次查询数据库

③因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语 言的,原则上任何web形式都支持。

④不需要在服务端保存会话信息,特别适用于分布式微服务。

5.)jwt的结构

jwt是由标头、有效载荷、签名组成。

①标头

标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。它会使用 Base64 编码组成 JWT 结构的第一部分。

注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

举例:

{ "alg": "HS256", "typ": "JWT" } ②有效载荷(不要放用户的敏感信息) 令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用 Base64 编码组成 JWT 结构的第二部分 { "sub": "1234567890", "name": "John Doe", "admin": true } ③签名

前面两部分都是使用 Base64 进行编码的,即前端可以解开知 道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法 (HS256)进行签名。签名的作用是保证 JWT 没有被篡改过

最后一步签名的过程,实际上是对头部以及负载内容进行签名, 防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修 改,再进行编码,最后加上之前的签名组合形成新的JWT的话, 那么服务器端会判断出新的头部和负载形成的签名和JWT附带上 的签名是不一样的。如果要对新的头部和负载进行签名,在不知 道服务器加密时用的密钥的话,得出来的签名也是不一样的。

- 在这里大家一定会问一个问题:Base64是一种编码, 是可逆的,那么我的信息不就被暴露了吗?

是的。所以,在JWT中,不应该在负载里面加入任何敏感的 数据。在上面的例子中,我们传输的是用户的User ID。这个值 实际上不是什么敏 感内容,一般情况下被知道也是安全的。但 是像密码这样的内容就不能被放在JWT中了。如果将用户的密 码放在了JWT中,那么怀有恶意的第 三方通过Base64解码就能 很快地知道你的密码了。因此JWT适合用于向Web应用传递一些 非敏感信息。JWT还经常用于设计用户认证和授权系 统,甚至 实现Web应用的单点登录。

以上显示的是未编码的信息,编码后的jwt是:xxxxx.yyyyy.zzzzz

6.)JWT的简单使用

引入依赖:

<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency> public class JwtTest { /** * 生成token */ @Test public void createTest(){ Calendar instance = Calendar.getInstance(); instance.add(Calendar.SECOND, 90); String token = JWT.create() .withClaim("username", "mcf") .withClaim("admin", true) // 设置过期时间 .withExpiresAt(instance.getTime()) // 设置签名 .sign(Algorithm.HMAC256("token!Q2W#E$RW")); System.out.println(token); } /** * 解析token,输出相关信息 */ @Test public void paraseInfo(){ JWTVerifier build = JWT.require(Algorithm.HMAC256("token!Q2W#E$RW")).build(); DecodedJWT verify = build.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNjUxMTk4Mzg3LCJ1c2VybmFtZSI6Im1jZiJ9.tH5-qe60SUmBC_7cO-QJv-86PURd3QkiGYVhBafZs-I"); System.out.println(verify.getClaim("username").asString()); System.out.println(verify.getClaim("admin").asBoolean()); } }

可能出现的常见日常: SignatureVerificationException: 签名不一致异常 TokenExpiredException: 令牌过期异常 AlgorithmMismatchException: 算法不匹配异常 InvalidClaimException: 失效的payload异常

7.)工具类 public class JWTUtils { private static String TOKEN = "token!Q@W3e4r"; /** * 生成token * @param map //传入payload * @return 返回token */ public static String getToken(Map<String,String> map){ JWTCreator.Builder builder = JWT.create(); map.forEach((k,v)->{ builder.withClaim(k,v); }); Calendar instance = Calendar.getInstance(); instance.add(Calendar.SECOND,7); builder.withExpiresAt(instance.getTime()); return builder.sign(Algorithm.HMAC256(TOKEN)); } /** * 验证token * @param token * @return */ public static void verify(String token){ JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token); // 如果验证通过,则不会把报错,否则会报错 } /** 从DecodedJWT 中拿到所有的信息 * 获取token中payload * @param token * @return */ public static DecodedJWT getToken(String token){ return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token); } } 五、从数据库校验用户

数据库信息自己补充,或到百度网盘中找。 创建一个类实现UserDetailsService接口,重写其中的方法。 因为要返回UserDetails类型的对象,所以我们实现一个:

六、自定义登录接口

spring security会把所有的请求都拦截,实际情况下,用户是可以进入登录页面的,所以我们需要把登录页面放行,让用户在登录页面进行登录操作。

同时,在以前的入门案例中,在springsecurity官方定义的登录界面进行用户认证,我们重写了官方定义的登录界面就要重写用户认证方面的问题:可以通过通过AuthenticationManager的authenticate方法来进行用户认证

注入AuthenticationManager 在业务逻辑中重新定义认证

七、利用过滤器拦截请求,验证token

在前面的自定义登录接口中,登录后会返回一个token,以后每次请求时请求头都会携带这个token,所以我们需要定义一个拦截器,拦截这些请求,验证token,查看是否已登录

①自定义过滤器: ②配置自定义过滤器,并将其置于授权过滤器之前 ③结果

八、退出登录

如果用户登录会在redis当中存入当前用户的信息 注销登录的方法就是:删除redis当中的用户的信息

逻辑代码: 结果:

九、权限系统(不同的用户拥有不同的权限)

实现授权的基本思路: ①SpringSecurity使用FilterSecurityInterceptor进行权限校验,FilterSecurityInterceptor会从SecurityContextHolder中获取Authentication,从中获取用户的权限信息,所以我们要把用户的权限信息也存入Authentication。 ②在资源(接口)上通过框架或自定义注解设置权限

-----------------------------------实操------------------------------------------------------

1.)限制访问资源所需的权限

2.)用户登录时将权限信息保存到SecurityContextHolder

十、从数据库查询权限信息

RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型。

建表语句:

CREATE DATABASE /*!32312 IF NOT EXISTS*/`sg_security` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; USE `sg_security`; /*Table structure for table `sys_menu` */ DROP TABLE IF EXISTS `sys_menu`; CREATE TABLE `sys_menu` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `menu_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '菜单名', `path` varchar(200) DEFAULT NULL COMMENT '路由地址', `component` varchar(255) DEFAULT NULL COMMENT '组件路径', `visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', `status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', `perms` varchar(100) DEFAULT NULL COMMENT '权限标识', `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标', `create_by` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_by` bigint(20) DEFAULT NULL, `update_time` datetime DEFAULT NULL, `del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='菜单表'; /*Table structure for table `sys_role` */ DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(128) DEFAULT NULL, `role_key` varchar(100) DEFAULT NULL COMMENT '角色权限字符串', `status` char(1) DEFAULT '0' COMMENT '角色状态(0正常 1停用)', `del_flag` int(1) DEFAULT '0' COMMENT 'del_flag', `create_by` bigint(200) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_by` bigint(200) DEFAULT NULL, `update_time` datetime DEFAULT NULL, `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; /*Table structure for table `sys_role_menu` */ DROP TABLE IF EXISTS `sys_role_menu`; CREATE TABLE `sys_role_menu` ( `role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色ID', `menu_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '菜单id', PRIMARY KEY (`role_id`,`menu_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; /*Table structure for table `sys_user` */ DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `user_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名', `nick_name` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '昵称', `password` varchar(64) NOT NULL DEFAULT 'NULL' COMMENT '密码', `status` char(1) DEFAULT '0' COMMENT '账号状态(0正常 1停用)', `email` varchar(64) DEFAULT NULL COMMENT '邮箱', `phonenumber` varchar(32) DEFAULT NULL COMMENT '手机号', `sex` char(1) DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)', `avatar` varchar(128) DEFAULT NULL COMMENT '头像', `user_type` char(1) NOT NULL DEFAULT '1' COMMENT '用户类型(0管理员,1普通用户)', `create_by` bigint(20) DEFAULT NULL COMMENT '创建人的用户id', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` bigint(20) DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `del_flag` int(11) DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; /*Table structure for table `sys_user_role` */ DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '用户id', `role_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '角色id', PRIMARY KEY (`user_id`,`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

逻辑处理没什么难的,就是将以前写死的地方,用数据库查询代替:

十一、自定义异常处理

当认证失败或授权失败时,应该返回特定的异常信息给前端。 (这里以前也学过springboot的全局异常处理,也可以用全局异常处理来解决,但是这里SpringSecurity有自己的异常处理机制,最好用SpringSecurity)


主要思想: 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。

? 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。

? 所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。

最后配置给SpringSecurity的配置类: 结果:

十二、跨域问题解决

前后端分离项目中,前端和后端部署的服务器一般是不同的,即前端项目和后端项目的 域名、端口、协议 可能不同。在用浏览器访问前端项目向后端项目发送请求时,会因为前端项目和后端项目的 域名、端口、协议 可能不同,导致请求失败。(像apipost等测试工具不会出现此类现象)

springboot开启跨域请求:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { // 设置允许跨域的路径 registry.addMapping("/**") // 设置允许跨域请求的域名 .allowedOriginPatterns("*") // 是否允许cookie .allowCredentials(true) // 设置允许的请求方式 .allowedMethods("GET", "POST", "DELETE", "PUT") // 设置允许的header属性 .allowedHeaders("*") // 跨域允许时间 .maxAge(3600); } }

SpringSecurity开启跨域请求:

@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; @Autowired AuthenticationEntryPoint authenticationEntryPoint; @Autowired AccessDeniedHandler accessDeniedHandler; @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } //ctrl+o可以查看一个类中所有的可被重写的方法 //将AuthenticationManager注入到spring容器中 @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } //配置某个接口可以匿名访问...... @Override protected void configure(HttpSecurity http) throws Exception { http. //关闭csrf 详细介绍:https://blog.csdn.net/weixin_40482816/article/details/114301717 csrf().disable() //不从session中获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() //对于登录接口 允许匿名访问 .antMatchers("/user/login").anonymous() //除了上面的请求全部需要鉴权认证 .anyRequest().authenticated(); //添加我们的自定义过滤器,并将其置于授权过滤器UsernamePasswordAuthenticationFilter之前 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); //配置自定义异常处理器 http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint). accessDeniedHandler(accessDeniedHandler); //开启跨域请求 http.cors(); } } 十三、其他权限校验方法

以上的权限校验方法是SpringSecurity官方定义的,在实际开发中,权限校验方法比较复杂需要自定义权限校验方法: ①设计权限验证逻辑

②在接口上配置对应的权限验证方法

也可以使用配置的方法为接口设置权限

十四、CSRF

CSRF(Cross-site request forgery)跨站请求伪造,是web常见的攻击之一。 如果不是前后端分离的话,并且还使用了SpringSecurity框架,那么SpringSecurity会借助csrf_token来防范CSRF攻击,后端会生成一个csrf_token,每次前端请求的时候都会携带csrf_token,后端会有过滤器拦截看是否有csrf_token或者是否csrf_token是仿造的。

如果是前后端分离的情况下的话,我们一般是使用token来验证用户信息,而CSRF需要借助cookie来完成攻击,前后端分离项目没有cookie,天然的避免了CSRF攻击。但是拦截器是默认会验证csrf_token的,所以在前后端分离的项目中需要关闭csrf防范;

十五、认证成功、认证失败、登出成功处理器

实际上在UsernamePasswordAuthenticationFilter进行登录认证的时候,如果登录成功了是会调用AuthenticationSuccessHandler的方法进行认证成功后的处理的。AuthenticationSuccessHandler就是登录成功处理器。

UsernamePasswordAuthenticationFilter是SpringSecurity官方用于用户认证的过滤器,但是我们前几节学的时候,自定义了登录认证接口,通过我们自定义的登录接口来实现认证,所以在我们自定义登录接口的项目中是没有UsernamePasswordAuthenticationFilter,使用了自定义的登录接口替换了UsernamePasswordAuthenticationFilter,也就不能使用认证成功处理器。

如果我以后的项目中用到了这些知识,可以在百度网盘中找三更草堂的SpringSecurity的课件。


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Spring #secuity三更草堂 #一介绍spring