SpringSecurity(十三)【授权】

news2024/11/19 21:23:41

十三、授权


  • 什么是授权
  • 权限管理核心概念
  • Spring Security 权限管理策略
  • 基于 URL 地址的权限管理
  • 基于方法的权限管理
  • 实战

权限管理

身份认证,就是判断一个用户是否为合法用户的处理过程。SpringSecurity中支持多种不同方式的认证,但是无论开发者使用那种方式认证,都不会影响授权功能使用。因为Spring Security很好做到了认证和授权解耦

授权

授权,即访问控制,控制谁能访问哪些资源。简单的理解授权就是根据系统提前设置好的规则,给用户分配可以访问某一个资源的权限,用户根据自己所具有权限,去执行相应操作

授权核心概念

在前面学习认证过程中,我们得知认证成功之后会将当前登录用户信息保存到 Authentication 对象中,Authentication 对象中有一个 getAuthorities()方法,用来返回当前登录用户具备的权限信息,也就是当前用户具有权限信息。该方法的返回值为 Collection<? extends GrantedAuthority>,当需要进行权限判断时,就回根据集合返回权限信息调用相应方法进行判断

在这里插入图片描述

那么问题来了,针对于这个返回值 GrantedAuthority 应该如何理解呢?是角色还是权限?
我们针对于授权可以是基于角色权限管理基于资源权限管理,从设计层面上来说,角色和权限是两个完全不同的东西:权限是一些具体操作,角色则是某些权限集合。如:
READ_BOOK 和 ROLE_ADMIN 是完全不同的。因此至于返回值是什么取决于你的业务设计情况:

  • 基于角色权限设计就是:用户<=>角色<=>资源三者关系返回就是用户的角色
  • 基于资源权限设计就是:用户<=>权限<=>资源三者关系返回就是用户的权限
  • 基于角色和资源权限设计就是:用户<=>角色<=>权限<=>资源返回统称为用户的权限

为什么可以统称为权限,因为从代码层面角色和权限没有太大不同,都是权限,特别是在 Spring Security 中,角色和权限处理方式基本上都是一样的。唯一区别 Spring Security 在很多时候会自动给角色添加一个ROLE_前缀,而权限则不会自动添加

13.1 权限管理策略

Spring Security 中提供的权限管理策略主要有两种类型

  • 基于过滤器的权限管理(FilterSecurityInterceptor)
    • 基于过滤器(URL)的权限管理主要是用来拦截 HTTP 请求,拦截下来之后,根据 HTTP 请求地址进行权限校验
  • 基于 AOP 的权限管理(MethodSecruityInterceptor)
    • 基于 AOP(方法)权限管理主要是用来处理方法级别的权限问题。当需要调用某一个方法时,通过 AOP 将操作拦截下来,然后判断用户是否具备相关的权限

基于 URL 权限管理

  • controller
package com.vinjcent.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * 自定义 spring security 配置类
 */
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService() {

        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("ADMIN").build());
        inMemoryUserDetailsManager.createUser(User.withUsername("lisi").password("{noop}123").roles("USER").build());
        inMemoryUserDetailsManager.createUser(User.withUsername("win7").password("{noop}123").authorities("READ_INFO").build());

        return inMemoryUserDetailsManager;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/admin/**").hasRole("ADMIN")  // 访问 admin 角色
                .mvcMatchers("/user/**").hasAnyRole("USER", "ADMIN")    // 访问 user 角色
                .mvcMatchers("/info/**").hasAuthority("READ_INFO")   // 访问 READ_INFO 权限
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .and()
                .csrf()
                .disable();
    }
}
  • 权限表达式

在这里插入图片描述

方法说明
hasAuthority(String authority)当前用户是否具备指定权限
hasAnyAuthority(String… authorities)当前用户是否具备指定权限中任意一个
hasRole(String role)当前用户是否具备指定角色
hasAnyRole(String… roles)当前用户是否具备指定角色中任意一个
permitAll()放行所有请求/调用
denyAll()拒绝所有请求/调用
isAnonymous()当前用户是否是一个匿名用户
isAuthenticated()当前用户是否已经认证成功
isRememberMe()当前用户是否通过 RememberMe 自动登录
isFullyAuthenticated()当前用户是否既不是匿名用户又不是通过 RememberMe 自动登录的
hasPermission(Object target, Object permission)当前用户是否具备指定目标的指定权限信息
hasPermission(Object targetId, String targetType, Object permission)当前用户是否具备指定目标的指定权限信息

基于方法的权限管理

基于方法的权限管理主要是通过 AOP 来实现,Spring Security 中通过 MethodSecurityInterceptor 来提供相关的实现。不同在于 FilterSecurityInterceptor 只是在请求之前进行前置处理,MethodSecurityInterceptor 除了前置处理之外还可以进行后置处理。前置处理就是在请求之前判断是否具备相应的权限,后置处理则是对方法的执行结果进行二次过滤。前置处理和后置处理分别对应了不同的实现类

@EnableGlobalMethodSecurity

@EnableGlobalMethodSecurity 该注解是用来开启权限注解,用法如下

/**
 * 自定义 spring security 配置类
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {}
  • perPostEnabled:开启Spring Security提供的四个权限注解,@PreAuthorize、@PreFilter、@PostAuthorize以及 @PostFilter
  • securedEnabled:开启Spring Security提供的 @Secured 注解支持,该注解不支持权限表达式
  • jsr250Enabled:开启 JSR-250 提供的注解,主要是 @DenyAll、@PermitAll、@RolesAllowed 同样这些注解也不支持权限表达式

以上注解含义如下

  • @PreAuthorize:在目标方法执行之前进行权限校验

  • @PreFiter:在目标方法执行之前对方法参数进行过滤

  • @PostAuthorize :在目标方法执行之后进行权限校验

  • @PostFiter:在目标方法执行之后对方法的返回结果进行过滤

  • @Secured:访问目标方法必须具备相应的角色

  • @DenyAll:拒绝所有访问

  • @PermitAll:允许所有访问

  • @RolesAllowed:访问目标方法必须具备相应的角色

    这些基于方法的权限管理相关的注解,一般来说只要设置prePostEnable=true就够用了

  • 用例测试需要在自定义 Spring Security 配置类中添加@EnableGlobalMethodSecurity注解

package com.vinjcent.controller;

import com.vinjcent.pojo.User;
import org.omg.CORBA.PUBLIC_MEMBER;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.web.bind.annotation.*;

import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import java.util.ArrayList;
import java.util.List;

@RestController
public class DemoController {

    /**
     * 方法执行前
     * 角色必须是 "ADMIN" 并且用户名是 "root" 或有 "READ_INFO" 权限
     * @return 字符串
     */
    @PreAuthorize("hasRole('ADMIN') and authentication.name == 'root' or hasAuthority('READ_INFO')")
    @GetMapping("/hello")
    public String hello() {
        return "admin ok!";
    }

    /**
     * 方法执行前
     * 当前认证的用户名必须与传递的参数username一致
     * @param username  用户名
     * @return  字符串
     */
    @PreAuthorize("authentication.name == #username")
    @GetMapping("/name")
    public String hello(String username) {
        return "hello: " + username;
    }

    /**
     * 方法执行前
     * 当前的过滤对象数组中,id 为 奇数的用户
     * @param users 过滤后的用户
     */
    @PreFilter(value = "filterObject.id % 2 != 0", filterTarget = "users")
    @PostMapping("/users")
    public void addUsers(@RequestBody List<User> users) {   // filterTarget 必须是 数组、集合类型
        System.out.println("users = " + users);
    }

    /**
     * 方法执行后
     * 返回的对象,id只能为"1"
     * @param id    传递的id参数
     * @return  返回一个对象
     */
    @PostAuthorize("returnObject.id == 1")
    @GetMapping("/userId")
    public User getUserById(Integer id) {
        return new User(id, "vinjcent");
    }

    /**
     * 方法执行完后
     * 将集合中用户id为偶数的用户进行过滤
     * @return  用户集合
     */
    @PostFilter("filterObject.id % 2 == 0")
    @GetMapping("/users")
    public List<User> getAllUsers() {
        ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            users.add(new User(i, "user" + i));
        }
        return users;
    }

    /**
     * 当前认证的用户必须有指定角色,才能访问该方法
     * @return  返回一个对象
     */
    @Secured({"ROLE_USER", "ROLE_ADMIN"})   // 可以写多个角色,具有其中一个即可
    @GetMapping("/secured")
    public User getUserByUsername() {
        return new User(99, "secured");
    }

    /**
     * 允许所有访问
     * @return  字符串
     */
    @PermitAll
    @GetMapping("/permitAll")
    public String permitAll() {
        return "permitAll";
    }

    /**
     * 拒绝所有访问
     * @return  字符串
     */
    @DenyAll
    @GetMapping("/denyAll")
    public String denyAll() {
        return "denyAll";
    }

    @RolesAllowed({"ROLE_USER", "ROLE_ADMIN"})   // 可以写多个角色,具有其中一个即可
    @GetMapping("/rolesAllowed")
    public String roleAllowed() {
        return "rolesAllowed";
    }
}

13.2 antMatchers()、mvcMatchers()、regexMatchers()

  • antMatchers:映射 AntPathRequestMatcher 实例 List

  • mvcMatchers:此匹配器将使用 Spring MVC 用于匹配的相同规则。例如,路径"/path"的映射通常匹配"/path"、“/path/”、“/path.html”

  • regexMatchers:正则表达式匹配器

在这里插入图片描述

在这里插入图片描述

13.3 授权之原理分析

授权 Debug

  • 首先会经过 FilterSecurityInterceptor 类中的 doFilter() 方法

在这里插入图片描述

  • 然后会经过父类的 super.beforeInvocation() 方法

在这里插入图片描述

  • 接着走到了父类的 this.obtainSecurityMetadataSource().getAttributes(object) 获取对应的元数据(自定义资源的对应权限

在这里插入图片描述

  • 然后类似于认证,去尝试授权 attemptAuthorization()

在这里插入图片描述

  • 授权当中,会进行 AccessDecisionManager 接口中的 decide() 方法进行投票

在这里插入图片描述

  • 循环遍历认证后的用户所具有的权限

在这里插入图片描述

  • 简要流程

在这里插入图片描述

  • ConfigAttribute 在 Spring Security 中,用户请求一个资源(通常是一个接口或者一个 Java 方法)需要的角色会被封装成一个 ConfigAttribute 对象,在 ConfigAttribute 中只有一个 getAttribute 方法,该方法返回一个 String 字符串,就是角色的名称。一般来说,角色名称都带有一个ROLE_前缀,投票器 AccessDecisionVoter 所做的事情,其实就是比较用户所具备的角色和请求某个资源所需的 ConfigAttribute 之间的关系
  • AccessDecisionVoterAccessDecisionManager 都有众多的实现类,在 AccessDecisionManager 中会挨个遍历 AccessDecisionVoter,进而决定是否允许用户访问,因而 AccessDecisionVoter 和 AccessDecisionManager 两者的关系类似于 AuthenticationProviderProviderManager 的关系

13.4 自定义资源权限实战

在前面的案例中,我们配置 URL 拦截规则和请求 URL 所需要的权限都是通过代码来配置的,这样就比较固定。如果想要调整访问某一个 URL 所需要的权限,就需要修改代码

动态管理权限规则就是我们将 URL 拦截规则和访问 URL 所需要的权限都保存在数据库中,这样,在不修改源代码的情况下,只需要修改数据库中的数据即可对权限进行调整

用户<--中间表-->角色<--中间表-->菜单

库表设计

-- menu 表
CREATE TABLE `menu` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '菜单id',
  `pattern` VARCHAR(128) DEFAULT NULL COMMENT '路径映射',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8


-- user 表
CREATE TABLE `user` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `username` VARCHAR(32) DEFAULT NULL COMMENT '用户名',
  `password` VARCHAR(255) DEFAULT NULL COMMENT '密码',
  `enabled` TINYINT(1) DEFAULT NULL COMMENT '是否可用',
  `locked` TINYINT(1) DEFAULT NULL COMMENT '是否锁定',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

-- role 表
CREATE TABLE `role` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '角色id',
  `name` VARCHAR(32) DEFAULT NULL COMMENT '角色英文名称',
  `nameZh` VARCHAR(32) DEFAULT NULL COMMENT '角色中文名称',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8

-- menu_role 表
CREATE TABLE `menu_role` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '菜单角色id',
  `mid` INT(11) DEFAULT NULL COMMENT '菜单id',
  `rid` INT(11) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`),
  KEY `mid` (`mid`),
  KEY `rid` (`rid`),
  CONSTRAINT `menu_role_ibfk_1` FOREIGN KEY (`mid`) REFERENCES `menu` (`id`),
  CONSTRAINT `menu_role_ibfk_2` FOREIGN KEY (`rid`) REFERENCES `role` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

-- user_role 表
CREATE TABLE `user_role` (
  `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '用户角色id',
  `uid` INT(11) DEFAULT NULL COMMENT '用户id',
  `rid` INT(11) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`),
  KEY `uid` (`uid`),
  KEY `rid` (`rid`),
  CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`),
  CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`rid`) REFERENCES `role` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
  • 参考数据

在这里插入图片描述

依赖以及配置文件

  • pom.xml
<dependencies>
    <!--security-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
    <!--druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.8</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
    </dependency>
</dependencies>
  • application.yml
# 端口号
server:
  port: 8080

spring:
  application:
    # 应用名称
    name: SpringSecurity014
  # 数据源
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springsecurity?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
# mybatis
mybatis:
  type-aliases-package: com.vinjcent.pojo
  mapper-locations: classpath:com/vinjcent/mapper/*.xml

# 日志打印
logging:
  level:
    com:
      vinjcent:
        debug

代码设计

  • 对应实体类

在这里插入图片描述

  • Mapper 接口

1)UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vinjcent.mapper.UserMapper">


    <!--根据用户名查询用户信息-->
    <select id="queryUserByUsername" resultType="User">
        select id,
               username,
               password,
               enabled,
               locked
        from user
        where username = #{username}
    </select>

</mapper>

2)RoleMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vinjcent.mapper.RoleMapper">

    <!--基础字段映射-->
    <resultMap id="RoleResultMap" type="Role">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="nameZh" column="nameZh" />
    </resultMap>

    <!--根据用户id查询角色信息-->
    <select id="queryRolesByUid" resultMap="RoleResultMap">
        select r.id id,
               r.name name,
               r.nameZh nameZh
        from role r,
             user_role ur
        where r.id = ur.rid
          and ur.uid = #{uid}
    </select>

</mapper>

3)MenuMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybtais.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vinjcent.mapper.MenuMapper">

    <resultMap id="MenuResultMap" type="Menu">
        <id column="id" property="id" />
        <result column="pattern" property="pattern" />
        <collection property="roles" ofType="Role">
            <id column="rid" property="id" />
            <result column="rname" property="name" />
            <result column="rnameZh" property="nameZh" />
        </collection>
    </resultMap>

    <select id="getAllMenus" resultMap="MenuResultMap">
        select
            m.id id,
            m.pattern pattern,
            r.id rid,
            r.name rname,
            r.nameZh rnameZh
        from menu m
             left join menu_role mr
                       on m.id = mr.mid
             left join role r
                       on mr.rid = r.id
   </select>

</mapper>

service 层代码过于简单不在这里给出,根据 mapper 接口方法实现即可

  • 测试接口 HelloController
package com.vinjcent.controller;

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

@RestController
public class HelloController {

    /**
     * 资源          可访问的角色
     * /admin/**    ROLE_ADMIN
     * /user/**     ROLE_ADMIN ROLE_USER
     * /guest/**    ROLE_ADMIN ROLE_USER ROLE_GUEST
     *
     *
     * 用户          具有的角色信息
     * admin        ADMIN USER GUEST
     * user         USER GUEST
     * vinjcent     GUEST
     *
     */

    @GetMapping("/admin/hello")
    public String admin() {
        return "hello admin";
    }

    @GetMapping("/user/hello")
    public String user() {
        return "hello user";
    }

    @GetMapping("/guest/hello")
    public String guest() {
        return "hello guest";
    }

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}
  • 认证数据源 UserDetailsService
package com.vinjcent.config.security.service;

import com.vinjcent.pojo.Role;
import com.vinjcent.pojo.User;
import com.vinjcent.service.RoleService;
import com.vinjcent.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.List;

@Component
public class DivUserDetailsService implements UserDetailsService {

    // dao ===> springboot + mybatis
    private final UserService userService;

    private final RoleService roleService;

    @Autowired
    public DivUserDetailsService(UserService userService, RoleService roleService) {
        this.userService = userService;
        this.roleService = roleService;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.查询用户
        User user = userService.queryUserByUsername(username);
        if (ObjectUtils.isEmpty(user)) throw new UsernameNotFoundException("用户名不存在!");
        // 2.查询权限信息
        List<Role> roles = roleService.queryRolesByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}
  • 自定义 url 权限元数据的数据源 FilterInvocationSecurityMetadataSource
package com.vinjcent.config.security.meta;

import com.vinjcent.pojo.Menu;
import com.vinjcent.pojo.Role;
import com.vinjcent.service.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;

/**
 * 自定义 url 权限元数据的数据源
 */
@Component
public class DivSecurityMetaSource implements FilterInvocationSecurityMetadataSource {


    private final MenuService menuService;

    @Autowired
    public DivSecurityMetaSource(MenuService menuService) {
        this.menuService = menuService;
    }

    // 用于路径对比
    AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 自定义动态资源权限数据源信息
     * @param object
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        // 1.获取请求资源路径
        String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
        // 2.获取数据库中所有菜单
        List<Menu> menus = menuService.getAllMenus();
        // 3.循环对比数据库中的路径与当前uri是否匹配
        for (Menu menu : menus) {
            // 如果匹配成功
            if (antPathMatcher.match(menu.getPattern(), requestURI)) {
                // 转链表为数组
                String[] roles = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                // 返回角色集合
                return SecurityConfig.createList(roles);
            }
        }
        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}
  • 自定义 security 配置类 WebSecurityConfigurerAdapter
package com.vinjcent.config.security;

import com.vinjcent.config.security.meta.DivSecurityMetaSource;
import com.vinjcent.config.security.service.DivUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

/**
 * 自定义 spring security 配置类
 */
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    // 注入认证数据源
    private final DivUserDetailsService userDetailsService;

    // 注入url元数据数据源
    private final DivSecurityMetaSource securityMetaSource;

    @Autowired
    public WebSecurityConfiguration(DivUserDetailsService userDetailsService, DivSecurityMetaSource securityMetaSource) {
        this.userDetailsService = userDetailsService;
        this.securityMetaSource = securityMetaSource;
    }

    // 替换认证数据源
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    // 暴露AuthenticationManager,使得这个bean能在组件中进行注入
    @Override
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    // http 拦截
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 1.获取工厂对象
        ApplicationContext context = http.getSharedObject(ApplicationContext.class);
        // 2.设置自定义 url 权限元数据
        http.apply(new UrlAuthorizationConfigurer<>(context))
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        // 配置元数据信息
                        object.setSecurityMetadataSource(securityMetaSource);
                        // 是否拒绝公共资源的访问,设置为true,不允许访问公共资源
                        object.setRejectPublicInvocations(false);
                        return object;
                    }
                });

        // 开启表单验证
        http.formLogin();

        // 关闭csrf
        http.csrf().disable();
    }

}
  • 测试对应接口

总结

用户对应角色具有指定的权限访问,admin 用户具有 ADMIN、USER、GUEST 角色,而 user 用户具有 USER、GUEST 角色,vinjcent 具有 GUEST 角色。发现/hello允许任何角色进行访问,/admin/hello只允许 ADMIN 角色进行访问;/user/hello 既可以是 USER 角色,又可以是 ADMIN 角色;而/guest/hello只允许 ADMIN、USER、GUEST 角色访问

角色可访问的资源

/**
 * 资源          可访问的角色
 * /admin/**    ROLE_ADMIN
 * /user/**     ROLE_ADMIN ROLE_USER
 * /guest/**    ROLE_ADMIN ROLE_USER ROLE_GUEST
 */

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

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

相关文章

【uniapp】uniapp使用高德地图定位打包成安卓app的一些记录,比如打包后定位失效、

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、创建你的uniapp1.打开Dcloud开发者后台2.下载你的证书、获取你的SHA1安全码、证书私钥密码二、打开高德开放平台申请key1.打开官网2.创建一个应用三、在unia…

快速傅里叶变换FFT和逆变换的python编程

0. 预备知识 快速傅里叶变换旨在解决离散傅里叶变换DFT计算量大效率低的问题。当我们想要抑制噪声提取出某段信号中的有效信息时&#xff0c;如系统模型辨识或者是使用高精度力传感器测量人体腕部寸关尺脉搏信号这类应用&#xff0c;应该如何设计采样流程&#xff1f; 首先&a…

《通讯录》思路及代码实现详解

目录 一、通讯录功能实现的详细描述 二、通讯录的代码及思路实现 2、1 定义联系人结构体 2、2 初始化就结构体与释放动态开辟空间的实现 2、3 菜单打印 2、4 添加联系人信息 2、5 删除联系人信息 2、6 查询联系人信息 2、7 修改联系人信息 2、8 打印所有联系人信息 2、9 排序整…

75. 序列模型的代码实现

1. 训练 在了解了上述统计工具后&#xff0c;让我们在实践中尝试一下&#xff01; 首先&#xff0c;我们生成一些数据&#xff1a;(使用正弦函数和一些可加性噪声来生成序列数据&#xff0c; 时间步为 1,2,…,1000 。) %matplotlib inline import torch from torch import nn…

新手nvm npm 卸载不用依赖包,项识别为 cmdlet、函数、脚本文件,等命令集合

nvm安装包&#xff1a;Releases coreybutler/nvm-windows GitHub下载ta就不用单独下载node了注意:vnm安装位置尽量不要动C:\Users\Administrator\AppData\Roaming\nvm\settings.txt增加下面代码node_mirror: https://npm.taobao.org/mirrors/node/ npm_mirror: https://npm.t…

java+Springboot交通事故档案管理系统

系统分为用户和管理员两个角色 用户的主要功能有&#xff1a; 1.用户注册和登陆系统 2.用户查看警察相关信息 3.用户查看我的相关事故信息&#xff0c;可以对交通事故进行交通申诉 4.用户查看交通申诉审核信息 5.退出登陆 管理员的主要功能有&#xff1a; 1.管理员输入账户登陆…

Metasploit渗透框架介绍及永恒之蓝复现

Metasploit渗透框架介绍及永恒之蓝复现一、Metasploit渗透框架介绍1.1 名词解释1.2 MSF简介1.3 MSF框架结构1.4 MSF命令汇总1.4.1 常用命令1.4.2 基本命令1.4.3 Exploits模块1.4.4 漏洞名称规则1.5 MSF模块介绍1.5.1 auxiliary(辅助模块)1.5.2 exploits(漏洞利用模块)1.5.3 pay…

Open3D 泊松盘网格采样(Python版本)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 在图形的许多应用中,特别是在渲染中,从蓝色噪声分布生成样本是很重要的。然而,现有的有效技术不容易推广到二维以外。不过泊松盘采样是个例外,它允许在O(N)时间内生成泊松盘样本,而且该方法很容易在任意维度上…

分布式CAP和BASE理论学习笔记

参考至&#xff1a;https://blog.csdn.net/solihawk/article/details/124442443 1. CAP理论 CAP理论是计算机科学家Eric Brewer在2000年提出的理论猜想&#xff0c;在2002年被证明并成为分布式计算领域公认的定理&#xff0c;其理论的基本观念是&#xff0c;在分布式系统中不…

加密算法 AES和RSA

一&#xff0c;加密&#xff08;一&#xff09;加密基础&#xff1f;通过互联网发送数据&#xff0c;数据可能会被第三者恶意窃听&#xff0c;造成损失。因此需要给重要的数据进行加密&#xff0c;加密后的数据被称为“密文”。接收方通过解除加密或得原本的数据&#xff0c;把…

人工智能卷积算法

文章目录前言数字信号处理与卷积运算卷积公式与计算过程边缘卷积计算与0填充NumPy卷积函数二维矩阵卷积计算图像卷积应用实例总结前言 卷积运算实际上是一种常见的数学方法&#xff0c;与加法&#xff0c;乘法等运算类似&#xff0c;都是由两个输入的到一个输出。不同的是&…

迷宫问题---数据结构实践作业

迷宫问题—数据结构实践作业 ✅作者简介&#xff1a;大家好,我是新小白2022&#xff0c;让我们一起学习&#xff0c;共同进步吧&#x1f3c6; &#x1f4c3;个人主页&#xff1a;新小白2022的CSDN博客 &#x1f525;系列专栏&#xff1a;算法与数据结构 &#x1f496;如果觉得博…

什么是HAL库和标准库,区别在哪里?

参考文章https://blog.csdn.net/u012846795/article/details/122227823 参考文章 https://zhuanlan.zhihu.com/p/581798453 STM32的三种开发方式 通常新手在入门STM32的时候&#xff0c;首先都要先选择一种要用的开发方式&#xff0c;不同的开发方式会导致你编程的架构是完全…

Java 面向对象程序设计 消息、继承与多态实验 课程设计研究报告

代码&#xff1a;Java计算机课程设计面向对象程序设计对战游戏SwingGUI界面-Java文档类资源-CSDN文库 一、课程设计内容 一个游戏中有多种角色(Character)&#xff0c;例如&#xff1a;国王&#xff08;King&#xff09;、皇后&#xff08;Queen&#xff09;、骑士&#xff0…

【Linux多线程】

Linux多线程Linux线程概念什么是线程线程的优点线程的缺点线程异常线程用途Linux进程VS线程进程和线程进程的多个线程共享Linux线程控制POSIX线程库线程创建线程等待线程终止分离线程线程ID及进程地址空间布局Linux线程概念 什么是线程 在一个程序里的一个执行路线就叫做线程…

JavaScript 如何正确的分析报错信息

文章目录前言一、报错类型1.控制台报错2.终端报错二、错误追查总结前言 摸爬滚打了这么长时间…总结了一些排查错误的经验, 总的来说, 这是一篇JavaScript新手向文章. 里面会有些不那么系统性的, 呃, 知识? 一、报错类型 报错信息该怎么看, 怎么根据信息快速的追查错误. 1.…

瑞吉外卖项目

技术选型&#xff1a; 1、JAVA版本&#xff1a;JDK11 2、数据库&#xff1a;mysql5.7 Navicat 3、后端框架&#xff1a;SpringBoot SpringMVC MyBatisPlus 4、工具类&#xff1a;发邮件工具类、生成验证码工具类 5、项目优化&#xff1a;Nginx、Redis、读写分离 项目来…

2022. 12 青少年机器人技术等级考试理论综合试卷(五级)

2022.年12月青少年机器人技术等级考试理论综合试卷&#xff08;五级&#xff09; 分数&#xff1a; 100 题数&#xff1a; 30 一、 单选题(共 20 题&#xff0c; 共 80 分) 1.下列程序执行后,串口监视器显示的相应内容是&#xff1f; &#xff08; &#xff09; A.1 B.2 C.4 D.…

WPF绑定(Binding)下的数据验证IDataErrorInfo

绑定下的数据验证 WPF中Binding数据校验、并捕获异常信息的三种方式讲到了三种方式&#xff0c;其中使用ValidatinRule的方式比较推荐&#xff0c;但是如果一个类中有多个属性&#xff0c;要为每个属性都要声明一个ValidatinRule&#xff0c;这样做非常麻烦。可以让类继承自ID…

【High 翻天】Higer-order Networks with Battiston Federico (8)

目录传播与社会动力学&#xff08;2&#xff09;Opinion and cultural dynamicsVoter modelMajority modelsContinuous models of opinion dynamicsCultural dynamics传播与社会动力学&#xff08;2&#xff09; 在本节将讨论一些观点和文化动力学模型&#xff0c;它们基于物理…