Security授权实现

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: Security授权实现

授权



重点剖析


AccessDecisionManager


AccessDecisionManager (访问决策管理器),用来决定此次访问是否被允许。

image-20220110110946267.png


AccessDecisionVoter


AccessDecisionVoter (访问决定投票器),投票器会检查用户是否具备应有的角色,进而投出赞成、反对或者弃权票。


image-20220110111011018.png


AccesDecisionVoter 和 AccessDecisionManager 都有众多的实现类,在 AccessDecisionManager 中会换个遍历 AccessDecisionVoter,进而决定是否允许用户访问,因而 AaccesDecisionVoter 和 AccessDecisionManager 两者的关系类似于 AuthenticationProvider 和 ProviderManager 的关系。


ConfigAttribute


ConfigAttribute,用来保存授权时的角色信息


image-20220110111037603.png


在 Spring Security 中,用户请求一个资源(通常是一个接口或者一个 Java 方法)需要的角色会被封装成一个 ConfigAttribute 对象,在 ConfigAttribute 中只有一个 getAttribute方法,该方法返回一个 String 字符串,就是角色的名称。一般来说,角色名称都带有一个 ROLE_ 前缀,投票器 AccessDecisionVoter 所做的事情,其实就是比较用户所具各的角色和请求某个

资源所需的 ConfigAtuibute 之间的关系。


调用步骤


首先. 调用对应接口时,需要的角色会被封装到ConfigAttribute 对象中来保存权限信息 ,只有保存了权限信息才能进行调用 ,在ConfigAttribute中有方法名为getAttribute的,它返回的是一个字符串,就是角色名称。


接下来. 通过AccessDecisionManager (访问决策管理器) 来对不同的权限设置不同的AccessDecisionVoter (访问决定投票器),从而实现决定用户是否能够访问


image-20230214190729821.png


所需的拦截器FilterSecurityInterceptor


实现案例


实战


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


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


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


库表设计


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pattern` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of menu
-- ----------------------------
BEGIN;
INSERT INTO `menu` VALUES (1, '/admin/**');
INSERT INTO `menu` VALUES (2, '/user/**');
INSERT INTO `menu` VALUES (3, '/guest/**');
COMMIT;
-- ----------------------------
-- Table structure for menu_role
-- ----------------------------
DROP TABLE IF EXISTS `menu_role`;
CREATE TABLE `menu_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `mid` int(11) DEFAULT NULL,
  `rid` int(11) DEFAULT NULL,
  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 AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of menu_role
-- ----------------------------
BEGIN;
INSERT INTO `menu_role` VALUES (1, 1, 1);
INSERT INTO `menu_role` VALUES (2, 2, 2);
INSERT INTO `menu_role` VALUES (3, 3, 3);
INSERT INTO `menu_role` VALUES (4, 3, 2);
COMMIT;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL,
  `nameZh` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of role
-- ----------------------------
BEGIN;
INSERT INTO `role` VALUES (1, 'ROLE_ADMIN', '系统管理员');
INSERT INTO `role` VALUES (2, 'ROLE_USER', '普通用户');
INSERT INTO `role` VALUES (3, 'ROLE_GUEST', '游客');
COMMIT;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `enabled` tinyint(1) DEFAULT NULL,
  `locked` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, 'admin', '{noop}123', 1, 0);
INSERT INTO `user` VALUES (2, 'user', '{noop}123', 1, 0);
INSERT INTO `user` VALUES (3, 'blr', '{noop}123', 1, 0);
COMMIT;
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) DEFAULT NULL,
  `rid` int(11) DEFAULT NULL,
  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;
-- ----------------------------
-- Records of user_role
-- ----------------------------
BEGIN;
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 1, 2);
INSERT INTO `user_role` VALUES (3, 2, 2);
INSERT INTO `user_role` VALUES (4, 3, 3);
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;


创建 springboot 应用


引入依赖


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--        <dependency>-->
    <!--            <groupId>org.springframework.boot</groupId>-->
    <!--            <artifactId>spring-boot-starter-thymeleaf</artifactId>-->
    <!--        </dependency>-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    </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>
    <!--数据库相关的-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.38</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>
</dependencies>

配置配置文件


server.port=8080
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
mybatis.mapper-locations=classpath:com/blr/mapper/*.xml
mybatis.type-aliases-package=com.blr.entity


创建实体类

public class User implements UserDetails {
    private Integer id;
    private String password;
    private String username;
    private boolean enabled;
    private boolean locked;
    private List<Role> roles;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream().map(r -> new SimpleGrantedAuthority(r.getName())).collect(Collectors.toList());
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return enabled;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
    public void setLocked(boolean locked) {
        this.locked = locked;
    }
    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
    public Integer getId() {
        return id;
    }
    public List<Role> getRoles() {
        return roles;
    }
}
public class Role {
    private Integer id;
    private String name;
    private String nameZh;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getNameZh() {
        return nameZh;
    }
    public void setNameZh(String nameZh) {
        this.nameZh = nameZh;
    }
}


public class Menu {
    private Integer id;
    private String pattern;
    private List<Role> roles;
    public List<Role> getRoles() {
        return roles;
    }
    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getPattern() {
        return pattern;
    }
    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
}

创建 mapper 接口


@Mapper
public interface UserMapper {
    List<Role> getUserRoleByUid(Integer uid);
    User loadUserByUsername(String username);
}


@Mapper
public interface MenuMapper {
    List<Menu> getAllMenu();
}


创建 mapper 文件


<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blr.mapper.UserMapper">
    <select id="loadUserByUsername" resultType="com.blr.entity.User">
        select *
        from user
        where username = #{username};
    </select>
    <select id="getUserRoleByUid" resultType="com.blr.entity.Role">
        select r.*
        from role r,
             user_role ur
        where ur.uid = #{uid}
          and ur.rid = r.id
    </select>
</mapper>


<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blr.mapper.MenuMapper">
    <resultMap id="MenuResultMap" type="com.blr.entity.Menu">
        <id property="id" column="id"/>
        <result property="pattern" column="pattern"></result>
        <collection property="roles" ofType="com.blr.entity.Role">
            <id column="rid" property="id"/>
            <result column="rname" property="name"/>
            <result column="rnameZh" property="nameZh"/>
        </collection>
    </resultMap>
    <select id="getAllMenu" resultMap="MenuResultMap">
        select m.*, r.id as rid, r.name as rname, r.nameZh as rnameZh
        from menu m
                 left join menu_role mr on m.`id` = mr.`mid`
                 left join role r on r.`id` = mr.`rid`
    </select>
</mapper>


创建 service 接口


@Service
public class UserService implements UserDetailsService {
    private final UserMapper userMapper;
    @Autowired
    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        user.setRoles(userMapper.getUserRoleByUid(user.getId()));
        return user;
    }
}


@Service
public class MenuService {
    private final MenuMapper menuMapper;
    @Autowired
    public MenuService(MenuMapper menuMapper) {
        this.menuMapper = menuMapper;
    }
    public List<Menu> getAllMenu() {
        return menuMapper.getAllMenu();
    }
}


创建测试 controller


@RestController
public class HelloController {
    @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";
    }
}


创建 CustomSecurityMetadataSource


@Component
public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    private final MenuService menuService;
    @Autowired
    public CustomSecurityMetadataSource(MenuService menuService) {
        this.menuService = menuService;
    }
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
        List<Menu> allMenu = menuService.getAllMenu();
        for (Menu menu : allMenu) {
            if (antPathMatcher.match(menu.getPattern(), requestURI)) {
                String[] roles = menu.getRoles().stream().map(r -> r.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 配置


package Authorize.config;
import Authorize.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
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.config.annotation.web.configurers.UrlAuthorizationConfigurer;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 将之前定义的权限数据源 注入
     */
    private final CustomerSecurityMetaSource customerSecurityMetaSource;
    private final UserService userService;
    @Autowired
    public SecurityConfig(CustomerSecurityMetaSource customerSecurityMetaSource, UserService userService) {
        this.customerSecurityMetaSource = customerSecurityMetaSource;
        this.userService = userService;
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //1. 获取工厂对象
        ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);
        //2. 设置自定义的url权限处理
        http.apply(new UrlAuthorizationConfigurer<>(applicationContext))
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(customerSecurityMetaSource);
                        object.setRejectPublicInvocations(false);
                        return object;
                    }
                });
        http.formLogin()
                .and()
                .csrf().disable();
    }
}


启动入口类进行测试


说明



部分的代码是由@编程不良人的学习教程中提供的,仅作为自己的学习参考使用!


作者教程链接 : Spring Security 最新实战教程


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6月前
|
数据库
shiro认证和授权
shiro认证和授权
55 3
|
Java Spring
5-SpringSecurity:RBAC及方法授权
5-SpringSecurity:RBAC及方法授权
369 0
5-SpringSecurity:RBAC及方法授权
|
17天前
|
安全 API 数据安全/隐私保护
基于Keycloak的认证与授权
【10月更文挑战第27天】Keycloak 是一个开源的身份和访问管理解决方案,提供用户认证、授权、单点登录等功能,保护应用程序和服务的安全。其认证流程包括用户登录、凭证验证、身份验证令牌生成、令牌返回给应用、应用验证令牌、用户身份确认。Keycloak 支持资源定义、权限定义、角色创建与分配、用户角色分配、访问请求与授权决策等授权流程。其优势在于集中式管理、高安全性、良好扩展性和社区支持。适用于企业应用集成、微服务架构、移动应用及 API 安全等多种场景。
|
6月前
|
安全 数据安全/隐私保护
security权限管理详解
security权限管理详解
|
JSON 前端开发 数据格式
SpringSecurity基础-认证授权结果处理
在传统的应用中,认证成功后页面需要跳转到认证成功页面或者跳转到个人中心页,但是在前后端分离的项目通常是使用Ajax请求完成认证,这时候我们需要返回一个JSON结果告知前端认证结果,然后前端自行跳转页面。 要做到上述功能,我们需要自定义认证成功处理器实现AuthenticationSuccessHandler接口复写 onAuthenticationSuccess方法,该方法其中一个参数是Authentication ,他里面封装了认证信息,用户信息UserDetails等,我们需要在这个方法中使用Response写出json数据即可
138 0
|
JSON 前端开发 数据格式
六.SpringSecurity基础-认证授权结果处理
SpringSecurity基础-认证授权结果处理
|
存储 开发框架 安全
快速理解 IdentityServer4 中的认证 & 授权
在实际的生产环境中,存在各种各样的应用程序相互访问,当用户访问 `app` 应用的时候,为了安全性考虑,通常都会要求搭配授权码或者安全令牌服务一并访问,这样可有效地对 `Server` 端的 `API` 资源起到一定程度的有效保护...
442 0
快速理解 IdentityServer4 中的认证 & 授权
|
移动开发 安全 前端开发
SpringSecurity认证和授权
目前,我们的测试环境,是谁都可以访问的,我们使用 Spring Security 增加上认证和授权的功能
SpringSecurity认证和授权
|
安全 Java 关系型数据库
|
存储 算法 前端开发
Security+Oauth2权限认证(案例 源码)
Security+Oauth2权限认证(案例 源码)
156 0
Security+Oauth2权限认证(案例 源码)