import org.springframework.context.annotation.Bean;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;
/**
* 框架中自定义用户名和密码
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,jsr250Enabled = true,prePostEnabled = true) // 开启注解式权限控制
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private MyUserDetail myUserDetail;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
private JwtHandler jwtHandler;
@Value(“${security.isOpen}”)
private Boolean isOpen;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
/**
* security硬性要求密码必须是密文
*/
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 对明文加密
// String encode = passwordEncoder.encode(“123”);
// System.out.println(“加密后为:”+encode);
// auth.inMemoryAuthentication().withUser(“pet”).password(encode).roles(“admin”);
auth.userDetailsService(myUserDetail);
}
/**
* 自定义表单,前端的页面
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception{
if (isOpen){
hasSecurity(http);
}else {
noSecurity(http);
}
}
/**
* 不加安全框架,放行所有请求
* @param http
*/
private void noSecurity(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().permitAll()
.and()
.csrf().disable();
}
private void hasSecurity(HttpSecurity http) throws Exception {
// 自定义页面
http.formLogin() // 需要自定义表单
.loginPage(“/login.html”) // 自己登陆页面
.loginProcessingUrl(“/api/user/login”) // 登陆请求地址
.successHandler(loginSuccessHandler) // 给前端返回json字符串
.failureHandler(new LoginFailHandler()) // 登陆失败给前端的字符串Json
.permitAll() // 对上面两个进行放行
;
http.exceptionHandling()
.accessDeniedHandler(new NoAuthorityHandler()) // 登陆后没有权限的返回字符串Json
.authenticationEntryPoint(new NotLoginHandler()) // 未登录
;
//给接口配置权限
//1:注解的方式
//2:编码的方式
http.authorizeRequests()
// 无需登陆
.antMatchers(“/find”,“/api/img/upload”,“/api/img/hi”,“/api/map/fastMatch”,
“/api/myLogin/username”,“/api/ali/pay”,“/api/ali/notify”).permitAll() // 无需登陆
.anyRequest().authenticated(); // 所有请求都拦截
// 指定目标过滤器,填自己的过滤器
http.addFilterBefore(jwtHandler, UsernamePasswordAuthenticationFilter.class);
// 前后端项目中装禁用掉session(改用jwt)
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// CSRF是指跨站请求伪造(Cross-site request forgery)
// https://www.jianshu.com/p/5ac8deb775b0
http.csrf().disable(); // 关闭csrf过滤器
}
/**
* 加密的类必须放入IOC容器中
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
UserDetailsService接口
1、基本概念
- AuthenticationManager
它是 “表面上” 的做认证和鉴权比对工作的那个人,它是认证和鉴权比对工作的起点。
ProvierderManager 是 AuthenticationManager 接口的具体实现。
- AuthenticationProvider
它是 “实际上” 的做认证和鉴权比对工作的那个人。从命名上很容易看出,Provider 受 ProviderManager 的管理,ProviderManager 调用 Provider 进行认证和鉴权的比对工作。
我们最常用到 DaoAuthenticationProvider 是 AuthenticationProvider 接口的具体实现。
- UserDetailsService
虽然 AuthenticationProvider 负责进行用户名和密码的比对工作,但是它并不清楚用户名和密码的『标准答案』,而标准答案则是由 UserDetailsService 来提供。简单来说,UserDetailsService 负责提供标准答案 ,以供 AuthenticationProvider 使用。
- UserDetails
UserDetails 它是存放用户认证信息和权限信息的标准答案的 “容器” ,它也是 UserDetailService “应该” 返回的内容。
- PasswordEncoder
Spring Security 要求密码不能是明文,必须经过加密器加密。这样,AuthenticationProvider 在做比对时,就必须知道『当初』密码时使用哪种加密器加密的。所以,AuthenticationProvider 除了要向 UserDetailsService 『要』用户名密码的标准答案之外,它还需要知道配套的加密算法(加密器)是什么
2、UserDetailsService获取用户名,密码,权限
3、UserInfo extends User
package com.tianju.config.security.entity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
public class UserInfo extends User {
private Integer userId; // 用户的id
private String realName; // 真实姓名
public UserInfo(String username, String password,
Collection<? extends GrantedAuthority> authorities, Integer userId, String realName) {
super(username, password, authorities);
this.userId = userId;
this.realName = realName;
}
public Integer getUserId() {
return userId;
}
public String getRealName() {
return realName;
}
}
设置拦截器handler
1、登录成功的拦截器
2、请求访问通过OncePerRequestFilter过滤器
- 1:是否携带了jwt
- 2:解密清求头jwt
不能解开:放行(到下一个过滤器》
能解开:走到下一步
- 3:对比redis中的jwt
不一样:放行(到下一个过滤器)
一样:走到下一步
- 4:给jwt续期
- 5:让容器中放入一个凭证(登陆凭证)
package com.tianju.config.security.handler;
import com.tianju.config.security.service.MyUserDetail;
import com.tianju.util.JwtUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
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.Map;
import java.util.concurrent.TimeUnit;
/**
* 1:是否携带了jwt
* 2:解密清求头jwt
* 不能解开:放行(到下一个过滤器》
* 能解开:走到下一步
* 3:对比redis中的jwt
* 不一样:放行(到下一个过滤器)
* 一样:走到下一步
* 4:给jwt续期
* 5:让容器中放入一个凭证(登陆凭证)
*/
@Component
@Slf4j
public class JwtHandler extends OncePerRequestFilter {
@Autowired
private RedisTemplate<String,Object> stringRedisTemplate;
@Autowired
private MyUserDetail myUserDetail;
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
//1.获取请求头中的前端jwt
String jwt = request.getHeader(“jwt”);
//2.判断能否获取到jwt
if (jwt == null) {
filterChain.doFilter(request, response);
log.warn(“未携带Jwt”);
return;
}
//3.判断能否解密jwt
if (!JwtUtil.decode(jwt)) {
filterChain.doFilter(request, response);
log.warn(“未能解密jwt”);
return;
}
//4.通过jwt获取用户信息,然后根据key获取redis中的jwt
Map userInfo = JwtUtil.getUserInfo(jwt);
Long userId = (Long) userInfo.get(“userId”);
String redisJwt = (String) stringRedisTemplate.opsForValue().get(“jwt” + userId.intValue());
//5.核验两个jwt
if (!jwt.equals(redisJwt)) {
filterChain.doFilter(request, response);
log.warn(“redis里的和前端的不一致”);
return;
}
//6.给jwt续期
stringRedisTemplate.opsForValue().set(“jwt” + userId, jwt, 30, TimeUnit.DAYS);
//7.往security容器中放登录凭证
//实现步骤:获取security上下文类,往里面放一个凭证
String username = (String) userInfo.get(“username”);
UserDetails userDetails = myUserDetail.loadUserByUsername(username);
UsernamePasswordAuthenticationToken upa = new UsernamePasswordAuthenticationToken(userDetails.getUsername(),
userDetails.getPassword(),userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(upa);
/**
* 记得放行
*/
filterChain.doFilter(request, response);
}
}
权限相关表设计
RBAC基于角色的访问控制
RBAC(Role-Based Access Control,基于角色的访问控制)模型是一种广泛用于访问控制的安全模型。它基于角色的概念,将权限授权和访问管理组织起来。
在RBAC模型中,有以下几个核心概念:
- 角色(Role):角色是一组权限的集合,代表了用户在系统中所扮演的角色或身份。用户可以被分配一个或多个角色。
- 权限(Permission):权限是对系统资源的操作权限。例如,读取、写入、执行等。权限定义了用户或角色可以进行的操作。
- 用户(User):用户是系统的最终使用者,他们可以被分配一个或多个角色。
- 资源(Resource):资源是受到访问控制保护的对象,可以是系统中的数据、功能、服务等。
数据库表设计
查询语句
SELECT
ut.id,
ut.username,
ut.realname,
ut.password,
art.role_name,
art.role_note,
at.auth_name,
at.auth_url
FROM auth_user_tab ut
LEFT JOIN auth_role_user_tab arut ON arut.user_id=ut.id
LEFT JOIN auth_role_tab art ON art.role_id=arut.role_id
LEFT JOIN auth_role_privs_tab arpt ON arpt.rp_role=arut.role_id
LEFT JOIN auth_tab at ON at.auth_id=arpt.rp_privs
权限的注解式控制
配置打开注解
Spring Security 支持三套注解:
注解类型 | 注解 |
---|---|
jsr250 注解 | @DenyAll、@PermitAll、@RolesAllowed |
secured 注解 | @Secured |
prePost 注解 | @PreAuthorize、@PostAuthorize |
使用什么注解在@EnableGlobalMethodSecurity开启,默认是关闭的,例如
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true,securedEnabled=true) //开启jsr250和secured注解
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
实际开发中最常用的写法 使用 @PreAuthorize(“hasRole(‘admin’)”)
使用注解控制权限
package com.tianju.controller;
import com.baomidou.mybatisplus.extension.api.R;
import com.tianju.entity.GoodType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(“/good/type”)
public class GoodTypeController {
@GetMapping
@PreAuthorize(“hasAnyAuthority(‘/good/query’)”)
public String get(Integer id){
return “查询的方法:”+id;
}
@PostMapping
@PreAuthorize(“hasAnyAuthority(‘/good/add’)”)
public String add(GoodType goodType){
return “新增商品的方法:”+goodType;
}
@DeleteMapping
@PreAuthorize(“hasAnyAuthority(‘/good/del’)”)
public String deleteById(Integer id){
return “删除的方法:”+id;
}
@PutMapping
@PreAuthorize(“hasAnyAuthority(‘/good/update’)”)
public String updateById(GoodType goodType){
return “修改的方法:”+goodType;
}
}
给不同的用户显示不同的页面
数据库表设计
查询语句
SELECT
aut.username,
t_menu.id,
t_menu.name,
t_menu.link,
t_menu.parentid,
t_menu.icon
FROM auth_user_tab AS aut
LEFT JOIN t_employee_menu ON aut.id = t_employee_menu.employeeId
LEFT JOIN t_menu ON t_employee_menu.menuId = t_menu.id
不同用户显示的效果
admin用户登录
peter用户登录
总结
1.Spring Security的使用,结合MySQL,Redis实现基于JWT的注解式的权限认证,并且可以实现给不同的用户显示不同的前端页面;
2.项目中的快速应用和使用,拦截器的设置,安全框架的配置;
3.权限表的设计,基于角色的访问控制RABC模型;
4.启动注解的配置,通过注解实现权限的控制;
5.给不同的用户显示不同的页面,相关的SQL以及前端页面;
附录:
1、前端页面
2、MySQL数据库语句
/*
Navicat Premium Data Transfer
Source Server : 127.0.0.1
Source Server Type : MySQL
Source Server Version : 80022
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数软件测试工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年软件测试全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上软件测试开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注软件测试)
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年软件测试全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-6MkPZSM3-1713067194124)]
[外链图片转存中…(img-BkA1ZkSe-1713067194125)]
[外链图片转存中…(img-jhQ7BdRl-1713067194126)]
[外链图片转存中…(img-2zq85vg3-1713067194126)]
[外链图片转存中…(img-5lojMAgf-1713067194127)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上软件测试开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注软件测试)
[外链图片转存中…(img-BSsplgHN-1713067194127)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!