Spring Security 笔记

news2024/11/24 22:48:15

Spring Security 5.7.0-M2,我们弃用了 WebSecurityConfigurerAdapter ,因为我们鼓励用户转向使用基于组件的安全配置。

为了帮助大家熟悉这种新的配置风格,我们编制了一份常见用例表和推荐的新写法。

配置HttpSecurity

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
    }
}

往后,我们建议注册一个SecurityFilterChain bean来做这件事:

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
        return http.build();
    }
}

配置WebSecurity

在Spring Security 5.4中,我们还引入了WebSecurityCustomizer。

WebSecurityCustomizer是一个回调接口,可以用来定制WebSecurity。

下面是一个使用WebSecurityConfigurerAdapter忽略匹配/ignore1或/ignore2的请求的示例配置:

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/ignore1", "/ignore2");
    }
}

往后,我们建议注册一个WebSecurityCustomizer bean来做这件事:

@Configuration
public class SecurityConfiguration {
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
    }
}

警告:如果你正在配置WebSecurity来忽略请求,建议你改为在HttpSecurity#authorizeHttpRequests内使用permitAll。想了解更多请参考configure Javadoc)。

01 认证原理与实战

自定义登录页面注意的细节

form表单提交和成功页面跳转必须是post请求

自定义的login.html中,form表单的method必须是post

<form action="/login" method="post">
    用户名:<input type="text" name="username"/></br>
    密码:<input type="password" name="password"/>
    <input type="submit" value="登录"/>
</form>

关闭csrf

http.csrf().disable();

快速开始

使用Springboot工程搭建Spring Security项目。

1.引入依赖

在pom中新增了Spring Security的依赖

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

2.创建测试访问接口

用于访问接口时触发Spring Security登陆页面

package com.qf.my.ss.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * web controller
 * @author Thor
 * @公众号 Java架构栈
 */
@RestController
public class SecurityController {

    @RequestMapping("/hello")
    public String hello(){
        return "hello security";
    }
}

3.访问接口,自动跳转至Security登陆页面

访问add接口,讲自动跳转至Security的登陆页面
在这里插入图片描述

默认账号是: user

默认密码是:启动项目的控制台中输出的密码
在这里插入图片描述

自定义认证页面

自定义登陆页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- Bootstrap 的 CSS 文件 -->
  <link rel="stylesheet" href="css/bootstrap/css/bootstrap.min.css">
  <title>登录</title>

</head>
<body class="bg-dark bg-opacity-75">
<div class="container vh-100">
  <div class="row vh-100">
    <div class="col-4 m-auto p-5 justify-content-center bg-white rounded">
      <form class="" role="form" action="/login"  method="post">
        <div class="mb-3">
          <label class="form-label mb-1 text-black-50">用户名:</label>
          <input type="text" class="form-control" name="username" value="user" >
        </div>
        <div class="mb-4">
          <label class="form-label mb-1 text-black-50">密码:</label>
          <input type="password" class="form-control" name="password">
        </div>
        <div class="mb-1">
          <button type="submit" class="form-control btn btn-primary">登录</button>
        </div>
      </form>
    </div>
  </div>
</div>
</body>
</html>

自定义错误页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录失败
</body>
</html>

自定义配置项

@Configuration
public class SecurityConfig {

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring().antMatchers("/css/**");
    }


    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.formLogin() // 开启认证
                .loginPage("/login.html")//登录页面设置
                .defaultSuccessUrl("/index.html").permitAll()//登陆成功之后,跳转路径
                .loginProcessingUrl("/login")// 登录处理Url,只需要前后台内容一致内部逻辑不需要完成。写什么都可以
                .usernameParameter("username").passwordParameter("password") //修改自定义表单name值.
                //登陆失败,用户名或密码错误
                .failureUrl("/error.html").permitAll()
                .and().authorizeRequests().anyRequest().authenticated();//所有请求都需要认证之后访问*/
        http.csrf().disable();
        return http.build();
    }
}

配置登录用户

基于内存方式 配置文件

application.properties

spring.security.user.name=test
spring.security.user.password=test

基于内存方式 配置类

@Configuration
public class SecurityConfig {
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
        UserDetails userDetails1 = User.withUsername("memory1").password("{noop}memory1").roles("memory1").build();
        UserDetails userDetails2 = User.withUsername("memory2").password("{noop}memory2").roles("memory2").build();
        return new InMemoryUserDetailsManager(userDetails1,userDetails2);
    }

}

基于JDBC 方式

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

设置配置文件

spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url = jdbc:mysql://localhost:3306/spring-auth-db?useUnicode=true&characterEncoding=utf-8&useSSL=false

获取数据库执行脚本

在这个路径下:org/springframework/security/core/userdetails/jdbc/users.ddl 得到脚本后,将_ignorecase 去掉
在这里插入图片描述

配置JDBC Manager

@Autowired
    private DataSource dataSource;
    @Bean
    public JdbcUserDetailsManager jdbcUserDetailsManager(){
        JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);
        if(!jdbcUserDetailsManager.userExists("lglbc-jdbc")){
            jdbcUserDetailsManager.createUser(User.withUsername("lglbc-jdbc").username("lglbc-jdbc").password("{noop}lglbc-jdbc").roles("admin").build());
        }
        if(!jdbcUserDetailsManager.userExists("lglbc-jdbc2")){
            jdbcUserDetailsManager.createUser(User.withUsername("lglbc-jdbc2").username("lglbc-jdbc2").password("{noop}lglbc-jdbc2").roles("admin").build());
        }
        return jdbcUserDetailsManager;
    }

基于自定义数据库

类似于JDBC

创建需要的表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kln5XlYj-1685353708659)(pringSecurityNew.assets/image-20230526170421830.png)]

定义UserDetails

public class LoginUser  implements UserDetails {
    private  User user;
    public  LoginUser(User user)
    {
        this.user=user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : user.getRoles()) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

定义 UserDetailService

@Service
public class MyUserDetailService implements UserDetailsService {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        String ssql = "SELECT * FROM SECURITY_USERS WHERE USERNAME='" + username + "'";
        User user = jdbcTemplate.queryForObject(ssql, new BeanPropertyRowMapper<>(User.class));
        if (user.getUser_id() > 0) {
            String ssql2 = "SELECT A.*,B.NAME ROLENAME FROM SECURITY_AUTH A LEFT JOIN SECURITY_ROLES B ON A.ROLE_ID=B.ROLE_ID WHERE USER_ID='" + user.getUser_id() + "'";
            List<AuthSite> list = jdbcTemplate.query(ssql2, new BeanPropertyRowMapper<>(AuthSite.class));
            for (AuthSite auth : list) {
                user.getRoles().add(new Role(auth.getRole_id(),auth.getRoleName()));
            }
        }
        LoginUser loginUser=new LoginUser(user);
        return loginUser;
    }
}

设置配置文件

    @Autowired
    MyUserDetailService userServiceImpl;

	@Bean
    public AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception {
        AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userServiceImpl)
//                .passwordEncoder(passwordEncoder())
                .and()
                .build();
        return authenticationManager;
    }

自定义认证

实现 AuthenticationProvider

@Service
public class MyAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    MyUserDetailService userService;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String username=authentication.getName();
        String password=authentication.getCredentials().toString();
        UserDetails user=userService.loadUserByUsername(username);


//        PasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
//        if (passwordEncoder.matches(password,user.getPassword()))
//        {
//        {noop}123
        if (password.equals(user.getPassword().substring(6)))
        {
            return new  UsernamePasswordAuthenticationToken(username,password,user.getAuthorities());
        }
        else
            throw  new BadCredentialsException("用户名和密码错误");
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

配置文件

 @Autowired
    MyAuthenticationProvider myAuthenticationProvider;

@Bean
    public AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception {
        AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
                .authenticationProvider(myAuthenticationProvider)
                .build();
        return authenticationManager;
    }

异常

自定义 AuthenticationEntryPoint

public class UnAuthEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        //ResultModel:状态码,信息,数据
        ResponseUtil.out(response, LResult.Error("没有权限"));
       // response.sendRedirect("/unauth.html");
    }
}

配置文件

	http.exceptionHandling().authenticationEntryPoint(new UnAuthEntryPoint());

自定义登录成功和失败处理

成功 实现AuthenticationSuccessHandler

失败 实现AuthenticationFailureHandler


public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
//    private RedirectStrategy redirectStrategy = new
//            DefaultRedirectStrategy();

    private String url;

    public MyAuthenticationSuccessHandler() {

    }
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("登录成功后续处理....");
        //重定向到index页
        // response.sendRedirect("/index.html");
        //redirectStrategy.sendRedirect(request, response, "/index2.html");
         //ajax 请求
        ResponseUtil.out(response, LResult.Success("","登录成功!!!"));
    }
}
public class MyAuthenticationFailureHandler  implements AuthenticationFailureHandler {
    private String url;

    public MyAuthenticationFailureHandler( ) {

    }
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        //ajax 请求
        ResponseUtil.out(response, LResult.Error("密码或用户错误"));
		//response.sendRedirect("/error.html");	
        System.out.println("oooo");
    }


}

配置文件

//   .defaultSuccessUrl("/index.html").permitAll()//登陆成功之后,跳转路径
// .failureUrl("/error.html")//登陆失败,
.successHandler(new MyAuthenticationSuccessHandler())
.failureHandler(new MyAuthenticationFailureHandler())
    
    
.and().authorizeRequests()
.antMatchers("/error.html").permitAll()
.antMatchers("/unauth.html").permitAll()

自动登录

简单的Token生成方法

在这里插入图片描述
Token=MD5(username+分隔符+expiryTime+分隔符+password)

注意 这种方式不推荐使用 , **有严重的安全问题 ** 。就是密码信息在前端浏览器 cookie中存放如果cookie被盗取很容易破解

  1. 前端页面需要增加remember-me的复选框
<!--记住我 name为remember-me value值可选true yes 1 on 都行-->

<input type="checkbox" name="remember-me" value="true"/>记住我

</div>

</div>
  1. 后台代码开启remember-me功能
.and().rememberMe()//开启记住我功能

.tokenValiditySeconds(1209600)// token失效时间默认2周

.rememberMeParameter("remember-me")// 自定义表单name值
  1. 登录成功后前台cookie
    在这里插入图片描述

持久化的Token生成方法

存入数据库Token包含

token: 随机生成策略,每次访问都会重新生成

series: 登录序列号,随机生成策略。用户输入用户名和密码登录时,该值重新生成。使用

remember-me功能,该值保持不变

expiryTime: token过期时间。

CookieValue=encode(series+token)

创建表

CREATE TABLE `persistent_logins` (
 `username` varchar(64) NOT NULL,
 `series` varchar(64) NOT NULL,
 `token` varchar(64) NOT NULL,
 `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP,
 PRIMARY KEY (`series`)
)

编写配置类

@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
    JdbcTokenRepositoryImpl jdbcTokenRepository = new 
    JdbcTokenRepositoryImpl();
    // 赋值数据源
    jdbcTokenRepository.setDataSource(dataSource);
    // 自动创建表,第一次执行会创建,以后要执行就要删除掉!
   // jdbcTokenRepository.setCreateTableOnStartup(true);
    return jdbcTokenRepository;
 }


@Autowired
private UsersServiceImpl usersService;
@Autowired
private PersistentTokenRepository tokenRepository;


// 开启记住我功能
http.rememberMe()
 .tokenRepository(tokenRepository)
 .userDetailsService(usersService);

页面添加记住我复选框

记住我:<input type="checkbox"name="remember-me"title="记住密码"/><br/>

设置有效期
在这里插入图片描述

退出登录

org.springframework.security.web.authentication.logout.LogoutFilter

匹配URL为/logout的请求,实现用户退出,清除认证信息。

只需要发送请求,请求路径为/logout即可, 当然这个路径也可以自行在配置类中自行指定, 同时退出

操作也有对应的自定义处理LogoutSuccessHandler,退出登录成功后执行,退出的同时如果有

remember-me的数据,同时一并删除

1.前端页面

<a class="button button-little bg-red" href="/logout">

<span class="icon-power-off"></span>退出登录</a></div>

2.LogoutSuccessHandler

public class MyAuthenticationService implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request,HttpServletResponse response, Authentication authentication) throws
    IOException, ServletException {
            System.out.println("退出成功后续处理....");
            redirectStrategy.sendRedirect(request, response, "/toLoginPage");
    }
}

3.配置文件

.and().logout().logoutUrl("/logout")//设置退出url
.logoutSuccessHandler(myAuthenticationService)//自定义退出处理

02 授权原理与实战

授权简介

安全权限控制问题其实就是控制能否访问url
在这里插入图片描述

Spring Security 授权原理

在这里插入图片描述

在我们应用系统里面,如果想要控制用户权限,需要有2部分数据。

  1. 系统配置信息数据:写着系统里面有哪些URL,每一个url拥有哪些权限才允许被访问。

  2. 另一份数据就是用户权限信息:请求用户拥有权限

系统用户发送一个请求:系统配置信息和用户权限信息作比对,如果比对成功则允许访问。

内置权限表达式

Spring Security 使用Spring EL来支持,主要用于Web访问和方法安全上, 可以通过表达式来判断是否具有访问权限. 下面是Spring Security常用的内置表达式. ExpressionUrlAuthorizationConfifigurer定义了所有的表达式

表达式说明
permitAll指定任何人都允许访问。
denyAll指定任何人都不允许访问
anonymous指定匿名用户允许访问。
rememberMe指定已记住的用户允许访问。
authenticated指定任何经过身份验证的用户都允许访问,不包含 anonymous
fullyAuthenticated指定由经过身份验证的用户允许访问,不包含anonymous和rememberMe
hasRole(role)指定需要特定的角色的用户允许访问,会自动在角色前面插入’ROLE_’
hasAnyRole([role1,role2])指定需要任意一个角色的用户允许访问,会自动在角色前面插入’ROLE_’
hasAuthority(authority)指定需要特定的权限的用户允许访问
hasAnyAuthority([authority,authority])指定需要任意一个权限的用户允许访问
hasIpAddress(ip)hasIpAddress(ip)

url 安全表达式

基于web访问使用表达式保护url请求路径.

  1. 设置url访问权限

    // 设置/user/** 访问需要ADMIN角色
    http.authorizeRequests().antMatchers("/user/**").hasRole("ADMIN");
    // 设置/user/** 访问需要PRODUCT角色和IP地址为127.0.0.1
    .hasAnyRole("PRODUCT,ADMIN")
    http.authorizeRequests().antMatchers("/product/**")
    .access("hasAnyRole('ADMIN,PRODUCT') and
    hasIpAddress('127.0.0.1')");
    // 设置自定义权限不足信息.
    http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
    
  2. MyAccessDeniedHandler自定义权限不足类

    /**
    * 自定义权限不足信息
    */
    @Component
    public class MyAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest httpServletRequest,
        HttpServletResponse resp, AccessDeniedException e) throws IOException,
        ServletException {
        resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("权限不足,请联系管理员!");
        }
    }
    
  3. 设置用户对应的角色权限
    在这里插入图片描述

// 先声明一个权限集合, 因为构造方法里面不能传入null
Collection<GrantedAuthority> authorities = new ArrayList<>();
if ("admin".equalsIgnoreCase(user.getUsername())) {
	authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
} else {
	authorities.add(new SimpleGrantedAuthority("ROLE_PRODUCT"));
}

Method安全表达式

针对方法级别的访问控制比较复杂, spring security 提供了4种注解分别是

@PreAuthorize , @PostAuthorize ,@PreFilter , @PostFilter .

  1. 开启方法级别的注解配置

在security配置类中添加注解

/**
* Security配置类
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启注解支持
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
  1. 在方法上使用注解

@ProAuthorize : 注解适合进入方法前的权限验证

/**
* 查询所有用户
*
* @return
*/
@RequestMapping("/findAll")
@PreAuthorize("hasRole('ADMIN')")//需要ADMIN权限
public String findAll(Model model) {
    List<User> userList = userService.list();
    model.addAttribute("userList", userList);
    return "user_list";
}
/**
* 用户修改页面跳转
*
* @return
*/
@RequestMapping("/update/{id}")
@PreAuthorize("#id<10")//针对参数权限限定 id<10可以访问
    public String update(@PathVariable Integer id, Model model) {
    User user = userService.getById(id);
    model.addAttribute("user", user);
    return "user_update";
}

@PostAuthorize: @PostAuthorize在方法执行后再进行权限验证,适合验证带有返回值的权限, Spring EL 提供返回对象能够在表达式语言中获取到返回对象的 returnObject

/**
* 根据ID查询用户
*
* @return
*/
@GetMapping("/{id}")
@ResponseBody
@PostAuthorize("returnObject.username==authentication.principal.username")//判断查询用户信息是否是当前登录用户信息.否则没有
权限
public User getById(@PathVariable Integer id) {
    User user = userService.getById(id);
    return user;
}

returnObject : 代表return返回的值

@PreFilter: 可以用来对集合类型的参数进行过滤, 将不符合条件的元素剔除集合

/**
* 商品删除-多选删除
*
* @return
*/
@GetMapping("/delByIds")
@PreFilter(filterTarget = "ids", value = "filterObject%2==0")//剔除参数为
基数的值
public String delByIds(@RequestParam(value = "id") List<Integer> ids) {
    for (Integer id : ids) {
    	System.out.println(id);
    }
    return "redirect:/user/findAll";
}

@PostFilter: 可以用来对集合类型的返回值进行过滤, 将不符合条件的元素剔除集合

/**
* 查询所有用户-返回json数据
*
* @return
*/
@RequestMapping("/findAllTOJson")
@ResponseBody
@PostFilter("filterObject.id%2==0")//剔除返回值ID为偶数的值
public List<User> findAllTOJson() {
    List<User> userList = userService.list();
    return userList;
}

自定义Bean授权

  1. 定义自定义授权类
/**
* 自定义授权类
*/
@Component
public class MyAuthorizationService {
/**
* 检查用户是否有对应的访问权限
*
* @param authentication 登录用户
* @param request 请求对象
* @return
*/
public boolean check(Authentication authentication, HttpServletRequest
request) {
    User user = (User) authentication.getPrincipal();
    // 获取用户所有权限
    Collection<GrantedAuthority> authorities = user.getAuthorities();
    // 获取用户名
    String username = user.getUsername();
    // 如果用户名为admin,则不需要认证
    if (username.equalsIgnoreCase("admin")) {
    	return true;
    } else {
    // 循环用户的权限, 判断是否有ROLE_ADMIN权限, 有返回true
        for (GrantedAuthority authority : authorities) {
            String role = authority.getAuthority();
            if ("ROLE_ADMIN".equals(role)) {
            	return true;
        	}
    	}
    }
    return false;
}

  1. 配置类
//使用自定义Bean授权
http.authorizeRequests().antMatchers("/user/**").
access("@myAuthorizationService.check(authentication,request)");
  1. 携带路径变量
/**
* 检查用户是否有对应的访问权限
*
* @param authentication 登录用户
* @param request 请求对象
* @param id 参数ID
* @return
*/
public boolean check(Authentication authentication, HttpServletRequestrequest, Integer id) {
    if (id > 10) {
    	return false;
    }
    return true;
}
//配置类
//使用自定义Bean授权,并携带路径参数
http.authorizeRequests().antMatchers("/user/delete/{id}").
access("@myAuthorizationService.check(authentication,request,#id)");

基于数据库的RBAC数据模型的权限控制

RBAC权限模型简介

RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。模型中有几个关键的术语:

用户:系统接口及访问的操作者

权限:能够访问某接口或者做某操作的授权资格

角色:具有一类相同操作权限的总称

RBAC权限模型核心授权逻辑如下:

某用户是什么角色?

某角色具有什么权限?

通过角色对应的权限推导出用户的权限
在这里插入图片描述
在这里插入图片描述

基于RBAC设计权限表结构

一个用户有一个或多个角色

一个角色包含多个用户

一个角色有多种权限

一个权限属于多个角色
在这里插入图片描述

实现RBAC权限管理

  1. 动态查询数据库中用户对应的权限
public interface PermissionMapper extends BaseMapper<Permission> {
/**
* 根据用户ID查询权限
2. 给登录用户授权
3. 设置访问权限
3.4 基于页面端标签的权限控制
在jsp页面或者thymeleaf模板页面中我们可以使用spring security提供的权限标签来进行权限控制.要
想使用thymeleaf为SpringSecurity提供的标签属性,首先需要引入thymeleaf-extras-springsecurity依
赖支持。
1. 在pom 文件中的引入springsecurity的标签依赖thymeleaf-extras-springsecurity5。
2. 在html文件里面申明使用
*
* @param id
* @return
*/
@Select("SELECT p.* FROM t_permission p,t_role_permission rp,t_role r,t_user_role ur,t_user u " +
"WHERE p.id = rp.PID AND rp.RID = r.id AND r.id = ur.RID AND ur.UID = u.id AND u.id =#{id}")
List<Permission> findByUserId(Integer id);
}
  1. 给登录用户授权
// 先声明一个权限集合, 因为构造方法里面不能传入null
Collection<GrantedAuthority> authorities = new ArrayList<>();
// 查询用户对应所有权限
List<Permission> permissions = permissionService.findByUserId(user.getId());
for (Permission permission : permissions) {
    // 授权
    authorities.add(new SimpleGrantedAuthority(permission.getPermissionTag()));
}
  1. 设置访问权限
// 查询数据库所有权限列表
List<Permission> permissions = permissionService.list();
for (Permission permission : permissions) {
    //添加请求权限
    http.authorizeRequests().antMatchers(permission.getPermissionUrl()).hasAuthority(permission.getPermissionTag());
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重磅发布!面向装备制造业服务化转型白皮书

《面向装备制造业服务化转型白皮书》 关于白皮书 《面向装备制造业服务化转型白皮书》通过调研160余家装备制造企业的服务化路径及模式&#xff0c;研讨支持企业开展服务型制造的系统化方案&#xff0c;希望为装备制造业服务化转型&#xff0c;探索切实有效的路径以供参考。 …

Web 自动化测试案例——关闭某视频网站弹出广告以及打开登录框输入内容

文章目录 &#x1f4cb;前言&#x1f3af;自动化测试&#x1f9e9;环境的搭建 &#x1f3af;案例介绍&#x1f4dd;最后 &#x1f4cb;前言 人生苦短&#xff0c;我用Python。许久没写博客了&#xff0c;今天又是久违的参与话题的讨论&#xff0c;话题的内容是&#xff1a;如何…

4.文件系统

组成 Linux&#xff1a;一切皆文件 索引节点&#xff08;I-node&#xff09; I-node&#xff08;Index Node&#xff09;&#xff1a;文件系统的内部数据结构&#xff0c;用于管理文件的元数据和数据块。 文件的元数据&#xff1a;包括文件的权限、拥有者、大小、时间戳、索引…

VM增加磁盘并挂载到根目录

1、虚拟机增加磁盘 首先要关闭虚拟机&#xff0c;否则增加按钮不可见。 9 vm添加磁盘完毕。 2、登录虚拟机挂盘 1、lsblk查看硬盘挂载情况&#xff0c;sdb为新挂载的磁盘。 [rootlocalhost ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda …

通过python封装接口采集1688店铺所有商品数据接口,1688店铺所有商品接口,1688API接口

采集1688店铺所有商品数据需要进行以下步骤&#xff1a; 获取店铺ID 要获取店铺ID&#xff0c;您可以通过访问店铺首页来获取&#xff0c;例如&#xff1a;https://1688455341.1688.com/ 店铺ID就是链接中的“1688455341”。 获取店铺所有商品列表页 通过向1688店铺的搜索…

关于【SD-WEBUI】的LoRA模型训练:怎样才算训练好了?

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;模型(LoRA)训练&#xff08;1.1&#xff09;数据准备&#xff08;1.1.1&#xff09;筛选照片&#xff08;1.1.2&#xff09;预处理照片&#xff08;1.1.3&#xff09;提示词(tags)处理&#xff08;1.1.4&…

部署微信小程序-shopro

部署微信小程序 开始之前 注意不要运行模式下的代码提交小程序审核&#xff0c;第一包体积太大&#xff0c;第二性能太差请下载 小程序开发工具正式小程序无法正常使用&#xff0c;而开发版正常&#xff0c;请确保域名都添加到小程序后台&#xff0c;并且配置好了 IP 白名单&a…

Openai+Deeplearning.AI: ChatGPT Prompt Engineering(五)

想和大家分享一下最近学习的Deeplearning.AI和openai联合打造ChatGPT Prompt Engineering在线课程.以下是我写的关于该课程的前四篇博客&#xff1a; ChatGPT Prompt Engineering(一)ChatGPT Prompt Engineering(二)ChatGPT Prompt Engineering(三)ChatGPT Prompt Engineering…

微星笔记本618大促至高直降5000元,泰坦GP78 HX爆款配置10999拿下

在万众玩家的期待下&#xff0c;微星笔记本618大促如约而至&#xff01;不仅覆盖今年全新13代酷睿HX RTX40系显卡的高能游戏本&#xff0c;还特别在618同步推出新品&#xff1a;泰坦GP78 HX&#xff0c;承袭“泰坦系列”旗舰的满血基因极致性能体验外&#xff0c;更有i9-13980…

自学web前端能找到工作吗?是否有必要参加前端培训?

是的&#xff0c;自学前端可以帮助您找到工作&#xff0c;参加培训是根据个人学习能力和经济实力来自己决定的。前端开发是一个相对容易入门的领域&#xff0c;并且许多人通过自学成功地找到了前端开发的工作。以下是好程序员的一些建议&#xff0c;可以帮助您在自学前端时提高…

头顶“米链代工厂”标签,德尔玛上市之后怎么走?

截至5月29日上午收盘&#xff0c;德尔玛股价当前为14.10、成交量55272手、成交额为7820.32万&#xff0c;总市值65.08亿元&#xff0c;总股本为4.62亿。 曲折的股价走势背后&#xff0c;德尔玛未来的增长潜力成疑。德尔玛表示&#xff0c;此次上市将有助于公司在创新家电市场保…

诚迈科技携智达诚远出席高通汽车技术与合作峰会

5月25日至26日&#xff0c;诚迈科技及旗下的智能汽车操作系统及中间件产品提供商智达诚远作为高通生态伙伴&#xff0c;亮相首届“高通汽车技术与合作峰会”&#xff0c;通过产品展示和主题演讲呈现了基于高通骁龙数字底盘的最新智能座舱技术成果&#xff0c;共同展望智能网联汽…

Java代码命名规范是真优雅呀!代码如诗

Java 命名规范 一、Java总体命名规范 1、项目名全部小写. 2、包名全部小写. 3、类名首字母大写,其余组成词首字母依次大写. 4、变量名,方法名首字母小写,如果名称由多个单词组成,除首字母外的每个单词首字母都要大写. 5、常量名全部大写. 6、所有命名规则必须遵循以下规则 : …

Java - ThreadLocal数据存储和传递方式的演变之路

Java - ThreadLocal数据存储和传递方式的演变之路 前言一. InheritableThreadLocal - 父子线程数据传递1.1 父子线程知识预热和 InheritableThreadLocal 实现原理1.2 InheritableThreadLocal 的诟病 二. TransmittableThreadLocal (TTL) 横空出世2.1 跨线程变量传递测试案例2.2…

代码随想录二刷 day06 | 哈希表之 242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和

day06 242.有效的字母异位词349. 两个数组的交集202. 快乐数1. 两数之和 哈希表能解决什么问题呢&#xff1f;一般哈希表都是用来快速判断一个元素是否出现集合里。 242.有效的字母异位词 题目链接 解题思路&#xff1a; 题目的意思就是 判断两个字符串是否由相同字母组成。 字…

【Java|基础篇】内部类

文章目录 1.什么是内部类?2.实例内部类3.静态内部类4.局部内部类5.匿名内部类6.结语 1.什么是内部类? 内部类就是在一个类中再定义一个类,内部类也是封装的体现.它可以被声明为 public、protected、private 或默认访问控制符。内部类可以访问外部类的所有成员变量和方法&…

【WebRTC】音视频通信

WebRTC对等体还需要查找并交换本地和远程音频和视频媒体信息&#xff0c;例如分辨率和编解码器功能。 交换媒体配置信息的信令通过使用被称为SDP的会话描述协议格式来交换&#xff0c;被称为提议和应答的元数据块 WebRTC 音视频通信基本流程 一方发起调用 getUserMedia 打开本…

线程池在业务中的实践-美团技术团队分享

原文地址&#xff1a;Java线程池实现原理及其在美团业务中的实践 场景1&#xff1a;快速响应用户请求 描述&#xff1a;用户发起的实时请求&#xff0c;服务追求响应时间。比如说用户要查看一个商品的信息&#xff0c;那么我们需要将商品维度的一系列信息如商品的价格、优惠、…

从小白到大神之路之学习运维第31天

第二阶段基础 时 间&#xff1a;2023年5月29日 参加人&#xff1a;全班人员 内 容&#xff1a; Rsync服务 目录 一、基本信息 二、rsync命令 三、rsyncinotfy实时同步 一、基本信息 &#xff08;一&#xff09;概述 rsync是linux 下一个远程数据同步工具 他可通过…

拼多多获取整站实时商品详情数据|商品标题|商品链接,数据采集,数据分析提取教程

拼多多是一个基于社交电商的购物平台&#xff0c;它通过通过价格和优惠吸引大量用户&#xff0c;使用户形成消费场景和消费共同体&#xff0c;最终实现规模效应。在拼多多运营中&#xff0c;API接口起到了重要的作用&#xff0c;它可以实现不同系统之间的信息共享和数据传递&am…