登陆认证&权限控制(2)—— 基于Spring security 安全框架的权限管理 & 注解式权限控制 & RABC模型_security权限控制注解

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、前端页面

在这里插入图片描述

{{ menu.name }} {{ cmenu.name }}
2、MySQL数据库语句

/*
Navicat Premium Data Transfer

Source Server : 127.0.0.1
Source Server Type : MySQL
Source Server Version : 80022

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数软件测试工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年软件测试全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上软件测试开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注软件测试)
img

一个人可以走的很快,但一群人才能走的更远。不论你是正从事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行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值