• Spring Boot Security 保护你的程序


    Spring Boot Security

    本示例要内容

    • 基于角色的权限访问控制
    • 加密、解密
    • 基于Spring Boot Security 权限管理框架保护应用程序

    String Security介绍

    Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

    快速上手

    1.创建表

        CREATE TABLE `user` (
        `id` bigint(11) NOT NULL AUTO_INCREMENT,
        `username` varchar(255) NOT NULL,
        `password` varchar(255) NOT NULL,
        PRIMARY KEY (`id`) 
        );
        CREATE TABLE `role` (
        `id` bigint(11) NOT NULL AUTO_INCREMENT,
        `name` varchar(255) NOT NULL,
        PRIMARY KEY (`id`) 
        );
        CREATE TABLE `user_role` (
        `user_id` bigint(11) NOT NULL,
        `role_id` bigint(11) NOT NULL
        );
        CREATE TABLE `role_permission` (
        `role_id` bigint(11) NOT NULL,
        `permission_id` bigint(11) NOT NULL
        );
        CREATE TABLE `permission` (
        `id` bigint(11) NOT NULL AUTO_INCREMENT,
        `url` varchar(255) NOT NULL,
        `name` varchar(255) NOT NULL,
        `description` varchar(255) NULL,
        `pid` bigint(11) NOT NULL,
        PRIMARY KEY (`id`) 
        );
    

    2.添加maven依赖

        <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-security4</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>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.1.2</version>
            </dependency>
        </dependencies>
    

    3.配置文件

        spring:
          thymeleaf:
            encoding: UTF-8
            cache: false
        
          datasource:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://localhost:3306/easy_web?useSSL=false&serverTimezone=UTC
            username: root
            password: 123456
    

    4.自定义UserDetailsService,实现用户登录功能

    @Service
    public class MyUserDetailsService implements UserDetailsService {
    
        @Autowired
        private UserMapper userMapper;
        @Autowired
        private RoleMapper roleMapper;
    
        @Override
        public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
            //查数据库
            User user = userMapper.loadUserByUsername(userName);
            if (null != user) {
                List<Role> roles = roleMapper.getRolesByUserId(user.getId());
                user.setAuthorities(roles);
            }
    
            return user;
        }
    }
    

    5.资源初始化

    @Component
    @Slf4j
    public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
    
        @Autowired
        private PermissionMapper permissionMapper;
    
        /**
         * 每一个资源所需要的角色 Collection<ConfigAttribute>决策器会用到
         */
        private static HashMap<String, Collection<ConfigAttribute>> map = null;
    
        /**
         * 返回请求的资源需要的角色
         */
        @Override
        public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
            //object 中包含用户请求的request 信息
            HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();
            for (Iterator<String> it = map.keySet().iterator(); it.hasNext(); ) {
                String url = it.next();
                log.info("url==>{},request==>{}", url, request.getRequestURI());
                if (new AntPathRequestMatcher(url).matches(request)) {
                    return map.get(url);
                }
            }
            return new ArrayList<>();
        }
    
        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            //初始化 所有资源 对应的角色
            loadResourceDefine();
            return new ArrayList<>();
        }
    
        @Override
        public boolean supports(Class<?> aClass) {
            return true;
        }
    
        /**
         * 初始化 所有资源 对应的角色
         */
        public void loadResourceDefine() {
            map = new HashMap<>(16);
            //权限资源 和 角色对应的表  也就是 角色权限 中间表
            List<RolePermisson> rolePermissons = permissionMapper.getRolePermissions();
    
            //某个资源 可以被哪些角色访问
            for (RolePermisson rolePermisson : rolePermissons) {
    
                String url = rolePermisson.getUrl();
                String roleName = rolePermisson.getRoleName();
                ConfigAttribute role = new SecurityConfig(roleName);
    
                if (map.containsKey(url)) {
                    map.get(url).add(role);
                } else {
                    List<ConfigAttribute> list = new ArrayList<>();
                    list.add(role);
                    map.put(url, list);
                }
            }
        }
    }
    

    6.决策器(也就是授权代码)

    /**
     * 决策器
     */
    @Component
    public class MyAccessDecisionManager implements AccessDecisionManager {
        /**
         * 通过传递的参数来决定用户是否有访问对应受保护对象的权限
         *
         * @param authentication 包含了当前的用户信息,包括拥有的权限。这里的权限来源就是前面登录时UserDetailsService中设置的authorities。
         * @param object  就是FilterInvocation对象,可以得到request等web资源
         * @param configAttributes configAttributes是本次访问需要的权限
         */
        @Override
        public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
            if (null == configAttributes || 0 >= configAttributes.size()) {
                return;
            } else {
                String needRole;
                for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
                    needRole = iter.next().getAttribute();
    
                    for(GrantedAuthority ga : authentication.getAuthorities()) {
                        if(needRole.trim().equals(ga.getAuthority().trim())) {
                            return;
                        }
                    }
                }
                throw new AccessDeniedException("当前访问没有权限");
            }
        }
    
        /**
         * 表示此AccessDecisionManager是否能够处理传递的ConfigAttribute呈现的授权请求
         */
        @Override
        public boolean supports(ConfigAttribute configAttribute) {
            return true;
        }
    
        /**
         * 表示当前AccessDecisionManager实现是否能够为指定的安全对象(方法调用或Web请求)提供访问控制决策
         */
        @Override
        public boolean supports(Class<?> aClass) {
            return true;
        }
    
    }
    

    7.最后一步Security配置

    @Configuration
    @EnableWebSecurity
    @Slf4j
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private MyUserDetailsService userService;
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    
            //校验用户
            auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {
                //对密码进行加密
                @Override
                public String encode(CharSequence charSequence) {
                    log.info(charSequence.toString());
                    return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                }
    
                //对密码进行判断匹配
                @Override
                public boolean matches(CharSequence charSequence, String s) {
                    String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                    boolean res = s.equals(encode);
                    return res;
                }
            });
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/", "index", "/login", "/login-error", "/401", "/css/**", "/js/**").permitAll()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin().loginPage("/login").failureUrl("/login-error")
                    .and()
                    .exceptionHandling().accessDeniedPage("/401");
            http.logout().logoutSuccessUrl("/");
        }
    }
    

    8.编写个控件器测试user用户和admin用户权限

    @Controller
    public class MainController {
    
        @RequestMapping("/")
        public String root() {
            return "redirect:/index";
        }
    
        @RequestMapping("/index")
        public String index() {
            return "index";
        }
    
        @RequestMapping("/login")
        public String login() {
            return "login";
        }
    
        @RequestMapping("/login-error")
        public String loginError(Model model) {
            model.addAttribute("loginError", true);
            return "login";
        }
    
        @GetMapping("/401")
        public String accessDenied() {
            return "401";
        }
    
        @GetMapping("/user/common")
        public String common() {
            return "user/common";
        }
    
        @GetMapping("/user/admin")
        public String admin() {
            return "user/admin";
        }
    }
    

    资料

  • 相关阅读:
    Roslyn 语法树中的各种语法节点及每个节点的含义
    WPF 使用 WindowChrome,在自定义窗口标题栏的同时最大程度保留原生窗口样式(类似 UWP/Chrome)
    在制作跨平台的 NuGet 工具包时,如何将工具(exe/dll)的所有依赖一并放入包中
    如何在 .NET 库的代码中判断当前程序运行在 Debug 下还是 Release 下
    像黑客一样!Chrome 完全键盘操作指南(原生快捷键 + Vimium 插件)
    .NET 使用 XPath 来读写 XML 文件
    XML 的 XPath 语法
    WPF 中使用附加属性,将任意 UI 元素或控件裁剪成圆形(椭圆)
    Windows 10 四月更新,文件夹名称也能区分大小写?
    C#/.NET 中推荐的 Dispose 模式的实现
  • 原文地址:https://www.cnblogs.com/tqlin/p/11283697.html
Copyright © 2020-2023  润新知