Spring Security 基于URL的权限判断

1. FilterSecurityInterceptor 源码阅读

org.springframework.security.web.access.intercept.FilterSecurityInterceptor
在这里插入图片描述
通过过滤器实现对HTTP资源进行安全处理。

该安全拦截器所需的 SecurityMetadataSource 类型为 FilterInvocationSecurityMetadataSource。
在这里插入图片描述

doFilter方法中直接调用invoke方法
在这里插入图片描述
基本都是调用父类的方法,那下面就重点看下父类 AbstractSecurityInterceptor 中相关方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面具体来看
在这里插入图片描述
从这里我们可以知道返回null和空集合是一样的
在这里插入图片描述
接下来看授权
在这里插入图片描述
这是我们要重点关注的,可以看到,授权靠的是 accessDecisionManager.decide(authenticated, object, attributes)

因此,我们想要实现自己的基于请求Url的授权只需自定义一个 AccessDecisionManager 即可

接下来,我们来具体实现一下

2. 自定义基于url的授权

在这里插入图片描述
自定义FilterInvocationSecurityMetadataSource

package com.example.security.core;

import com.example.security.service.SysPermissionService;
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.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/2
 */
@Component
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private SysPermissionService sysPermissionService;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        FilterInvocation fi = (FilterInvocation) object;
        String url = fi.getRequestUrl();
        String httpMethod = fi.getRequest().getMethod();

        List<ConfigAttribute> attributes = new ArrayList<>();

        Map<String, String> urlRoleMap = sysPermissionService.getAllUrlRole();

        for (Map.Entry<String, String> entry : urlRoleMap.entrySet()) {
            if (antPathMatcher.match(entry.getKey(), url)) {
                return SecurityConfig.createList(entry.getValue());
            }
        }

        // 返回null和空列表是一样的,都表示当前访问的资源不需要权限,所有人都可以访问
        return attributes;
//        return null;
    }

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

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

这里需要说明一下,其实Spring Security里面说的role不一定表示的是我们自己建的那个角色表,我们可以这样理解,就是它这里的所谓role只是一个权限标识。我们在建表的时候,通常最基本的是5张表(用户表、角色表、权限表、用户角色关系表、角色权限关系表),我们可以把受保护的资源(通常是一个url)与角色关联起来,建立哪些角色可以访问哪些资源,也可以直接判断资源的权限(通常是权限编码/标识)
在这里插入图片描述
只要有这个关系,剩下的就是写法不同而已。如果你把role理解成资源的权限标识的话,那么返回的Collection中就最多有一个元素,如果理解成角色的话,那么可能有多个元素。就这么点儿东西,写法不同而已,本质是一样的。

自定义AccessDecisionManager

package com.example.security.core;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/2
 */
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        System.out.println(authorities);
        System.out.println(configAttributes);
        //  查看当前用户是否有对应的权限访问该保护资源
        for (ConfigAttribute attribute : configAttributes) {
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(attribute.getAttribute())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("Access is denied");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

decide方法的三个参数,依次表示:

调用者(非空)
被调用的安全对象
与被调用的安全对象关联的配置属性

配置WebSecurityConfig

package com.example.security.config;

import com.example.security.core.MyAccessDecisionManager;
import com.example.security.core.MyFilterSecurityMetadataSource;
import com.example.security.core.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;
    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;
    @Autowired
    private MyFilterSecurityMetadataSource myFilterSecurityMetadataSource;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .and()
                .authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(myFilterSecurityMetadataSource);
                        object.setAccessDecisionManager(myAccessDecisionManager);
                        return object;
                    }
                })
                .anyRequest().authenticated();
    }



    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

其它不重要的就直接贴出来了

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>security-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>security-demo</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo126?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  jpa:
    database: mysql
    show-sql: true

SysPermissionEntity.java

package com.example.security.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.io.Serializable;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Getter
@Setter
@Entity
@Table(name = "sys_permission")
public class SysPermissionEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    /** 权限编码(标识) */
    private String code;

    /** 权限名称 */
    private String name;

    /** 权限URL */
    private String url;

}

SysRoleEntity.java

package com.example.security.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Set;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Getter
@Setter
@Entity
@Table(name = "sys_role")
public class SysRoleEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    /** 角色编码 */
    private String code;

    /** 角色名称 */
    private String name;

    @ManyToMany
    @JoinTable(name = "sys_role_permission", joinColumns = {@JoinColumn(name = "role_id")}, inverseJoinColumns = {@JoinColumn(name = "permission_id")})
    private Set<SysPermissionEntity> permissions;
}

SysUserEntity.java

package com.example.security.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Set;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Getter
@Setter
@Entity
@Table(name = "sys_user")
public class SysUserEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    /** 用户名 */
    private String username;

    /** 密码 */
    private String password;

    @ManyToMany
    @JoinTable(name = "sys_user_role",
            joinColumns = {@JoinColumn(name = "user_id")},
            inverseJoinColumns = {@JoinColumn(name = "role_id")})
    private Set<SysRoleEntity> roles;
}

SysUserRepository.java

package com.example.security.repository;

import com.example.security.entity.SysUserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
public interface SysUserRepository extends JpaRepository<SysUserEntity, Integer>, JpaSpecificationExecutor<SysUserEntity> {

    SysUserEntity findByUsername(String username);
}

SysPermissionServiceImpl.java

package com.example.security.service.impl;

import com.example.security.entity.SysPermissionEntity;
import com.example.security.repository.SysPermissionRepository;
import com.example.security.service.SysPermissionService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Service
public class SysPermissionServiceImpl implements SysPermissionService {

    @Resource
    private SysPermissionRepository sysPermissionRepository;

    @Override
    public Map<String, String> getAllUrlRole() {
        List<SysPermissionEntity> list = sysPermissionRepository.findAll();
        return list.stream().collect(Collectors.toMap(SysPermissionEntity::getUrl, SysPermissionEntity::getCode));
    }
}

MyUserDetails.java

package com.example.security.domain;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Set;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@NoArgsConstructor
@AllArgsConstructor
public class MyUserDetails implements UserDetails {

    private String username;
    private String password;
    private boolean enabled;
    private Set<SimpleGrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

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

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

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

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

MyUserDetailsService.java

package com.example.security.core;

import com.example.security.domain.MyUserDetails;
import com.example.security.entity.SysPermissionEntity;
import com.example.security.entity.SysUserEntity;
import com.example.security.repository.SysUserRepository;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Service;

import javax.annotation.Resource;
import javax.transaction.Transactional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@Transactional
@Service
public class MyUserDetailsService implements UserDetailsService {

    @Resource
    private SysUserRepository sysUserRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUserEntity sysUserEntity = sysUserRepository.findByUsername(username);
        if (null == sysUserEntity) {
            throw new UsernameNotFoundException("用户不存在");
        }
        Set<SimpleGrantedAuthority> authorities = sysUserEntity.getRoles().stream()
                .flatMap(roleId->roleId.getPermissions().stream())
                .map(SysPermissionEntity::getCode)
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toSet());

        return new MyUserDetails(sysUserEntity.getUsername(), sysUserEntity.getPassword(), true, authorities);
    }
}

HelloController.java

package com.example.security.controller;

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

/**
 * @Author ChengJianSheng
 * @Date 2021/12/6
 */
@RestController
@RequestMapping("/hello")
public class HelloController {

    @GetMapping("/sayHello")
    public String sayHello() {
        return "Hello";
    }

    @GetMapping("/sayHi")
    public String sayHi() {
        return "Hi";
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

数据库脚本如下

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限编码(标识)',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限名称',
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限URL',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, 'home', '首页', '/home/**');
INSERT INTO `sys_permission` VALUES (2, 'user:add', '添加用户', '/user/add');
INSERT INTO `sys_permission` VALUES (3, 'user:delete', '删除用户', '/user/delete');
INSERT INTO `sys_permission` VALUES (4, 'hello:sayHello', '打招呼', '/hello/sayHello');

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色编码',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'employee', '员工');
INSERT INTO `sys_role` VALUES (2, 'engineer', '工程师');
INSERT INTO `sys_role` VALUES (3, 'leader', '组长');

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) NOT NULL COMMENT '角色ID',
  `permission_id` int(11) NOT NULL COMMENT '权限ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES (1, 1, 1);
INSERT INTO `sys_role_permission` VALUES (2, 2, 1);
INSERT INTO `sys_role_permission` VALUES (3, 2, 2);
INSERT INTO `sys_role_permission` VALUES (4, 3, 1);
INSERT INTO `sys_role_permission` VALUES (5, 3, 2);
INSERT INTO `sys_role_permission` VALUES (6, 3, 3);
INSERT INTO `sys_role_permission` VALUES (7, 3, 4);

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'zhangsan', '$2a$10$e4wFsFHQCNjPe5tTJMPkRuKGwmMGC45pfjMupY9nwbTuoKQ0bKc/u');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '用户ID',
  `role_id` int(11) NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1);
INSERT INTO `sys_user_role` VALUES (2, 1, 2);
INSERT INTO `sys_user_role` VALUES (3, 1, 3);

SET FOREIGN_KEY_CHECKS = 1;

浏览器访问 http://localhost:8080/hello/sayHi 正常返回,不用登录,因为没有在sys_permission表中配置该资源,也就是说它不是一个受保护的资源(公开资源)

访问http://localhost:8080/hello/sayHello则需要先登录,用zhangsan登录成功以后正确返回

在这里插入图片描述
在这里插入图片描述
项目结构如下
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值