zoukankan      html  css  js  c++  java
  • SpringSecurity授权详解

      项目开发离不开认证授权,简单来说,认证解决你是谁的问题,授权解决你能干什么的问题。下面讲讲SpringSecurity的授权。

      一、授权基本知识

      1、授权因项目而异

      一些业务系统,如电商网站,只需区分是否登录,或者是普通用户还是VIP用户等基本角色,它们的权限基本不会改变,这种情况可以直接在代码中写死。还有一些内管系统,如运营人员管理系统 ,角色众多,权限复杂,权限规则随着公司和业务的发展不断变化,这种情况必须配置权限。

      2、什么是授权

      授权不是说在页面上隐藏某个连接或者按钮就完事儿,而是要判断当前用户有没有访问该连接的权限。授权模型中要明确两个要素:系统配置信息和用户权限信息。系统配置信息中记录了url连接和每一个连接需要的权限,比如www.a.com/user需要A权限;用户权限信息则记录了某用户具有的权限,比如用户张三具有A、B、C权限。当一个用户发了一个连接请求,系统会拿这两份信息比对,如果这个请求需要A权限,发请求的这个用户也有A权限,那么就可以访问。

      二、SpringSecurity中的授权

      1、授权之是否需要登录

      有一些链接需要登录才能访问,也有一些链接无需登录就能访问,怎么允许链接不登录就能访问呢?SpringSecurity配置如下:

    @EnableWebSecurity
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
        
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin()
                .loginPage("/authentication/require")
                .loginProcessingUrl("/authentication/form")
            http.authorizeRequests()
                .antMatchers(
                        "/login.html",
                        "/authentication/require",
                        "/code/image",
                        "/session/invalid",
                        "/logout.html"
                        ).permitAll()//不需要身份认证
                .anyRequest()
                .authenticated();
            http.csrf().disable();
        }
    }

      2、授权之区分简单角色

      链接"/user"需要"ADMIN"权限才能访问,配置如下:

    @EnableWebSecurity
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
        
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin()
                .loginPage("/authentication/require")
                .loginProcessingUrl("/authentication/form")
            http.authorizeRequests()
                .antMatchers(
                        "/login.html",
                        "/authentication/require",
                        "/code/image",
                        "/session/invalid",
                        "/logout.html"
                        ).permitAll()
                .antMatchers("/user").hasRole("ADMIN")
                .anyRequest()
                .authenticated();
            http.csrf().disable();
        }
    }
    @Component
    public class MyUserDetailsService implements UserDetailsService{
    
        @Autowired
        private PasswordEncoder passwordEncoder;
        
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            return new User(username, passwordEncoder.encode("123456"), 
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        }
    }

      说明:

      a)如上配置,浏览器访问http://localhost/user,报403无权访问;访问http://localhost/user/1,可以获取到数据。

      b)将MyUserDetailsService中“admin”改为“ROLE_ADMIN”,http://localhost/user或者http://localhost/user/1都可以获取到数据。

      c)注意,hasRole("ADMIN")对应的是“ROLE_ADMIN”,大小写敏感。必须有“ROLE_”。

      d)antMatchers中支持通配符,如下配置,则http://localhost/user/或者http://localhost/user/1都无权访问。

      

      

      3、授权之权限表达式

      上面讲了permitAll和hasRole,SpringSecurity还有一些其他的表达式如下:

      

      说明:

      a)anonymous是匿名,即不登录时可以访问。

      b).antMatchers("/user").access("hasRole('ADMIN') and hasIpAddress('192.168.1.0/24')") ;ADMIN权限和IP地址同时满足。

      c)判断hasRole中的权限时,在设置权限时添加前缀ROLE_,其它表达式时不需要添加。

      4、将授权提取到AuthorizeConfigProvider中统一管理

      从上面的示例中看到授权代码写在WebSecurityConfigurerAdapter中,耦合度高,下面解决这个问题

      1)AuthorizeConfigProvider.java

    public interface AuthorizeConfigProvider {
        void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
    }

      2)MyAuthorizeConfigProvider.java,配置通用url

    @Component
    public class MyAuthorizeConfigProvider implements AuthorizeConfigProvider {
    
        @Override
        public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
            config.antMatchers(
                    "/login.html",
                    "/authentication/require",
                    "/code/image",
                    "/session/invalid",
                    "/logout.html"
            ).permitAll();
        }
    }

      3)DempAuthorizeConfigProvider.java,配置自定义url

    @Component
    public class DempAuthorizeConfigProvider implements AuthorizeConfigProvider {
    
        @Override
        public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
            config.antMatchers("/user",
                               "/demo.html"
                    ).hasRole("ADMIN");
        }
    }

      4)AuthorizeConfigManager.java

    public interface AuthorizeConfigManager {
        void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
    }

      5)MyAuthorizeConfigManager.java

    @Component
    public class MyAuthorizeConfigManager implements AuthorizeConfigManager {
    
        //Spring启动的时候,所有AuthorizeConfigProvider的接口的实现都(自动)放在AuthorizeConfigProviders中
        @Autowired
        private Set<AuthorizeConfigProvider> AuthorizeConfigProviders;
        
        @Override
        public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
            for(AuthorizeConfigProvider authorizeConfigProvider:AuthorizeConfigProviders) {
                authorizeConfigProvider.config(config);
            }
            config.anyRequest().authenticated();//所有请求需要身份认证
        }
    }

      上面的代码实现了权限代码的解耦,现在看一下WebSecurityConfigurerAdapter中的配置:

    @EnableWebSecurity
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
        ... ...
        @Autowired
        private AuthorizeConfigManager authorizeConfigManager;
        
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            ... ...
            /**http.authorizeRequests()
                .antMatchers(
                        "/login.html",
                        "/authentication/require",
                        "/code/image",
                        "/session/invalid",
                        "/logout.html"
                        ).permitAll()//该路径不需要身份认证
                .antMatchers("/user").hasRole("ADMIN")
                //.antMatchers(HttpMethod.GET,"/user/*").hasRole("ADMIN")//GET请求需要这个权限
                //.antMatchers("/user").access("hasRole('ADMIN') and hasIpAddress('192.168.1.0/24')")  
                .anyRequest()
                .authenticated();*/
            http.csrf().disable();//先禁止掉跨站请求伪造防护功能
            authorizeConfigManager.config(http.authorizeRequests());//将授权代码提取到AuthorizeConfigManager中
        }
    }

      测试,访问http://localhost/userhttp://localhost/demo.html报403,上述配置生效。

      5、通用RBAC(Role-Based Access Control)数据模型

      上面的授权都是静态的,即都是写死在代码中的,遇到复杂的权限管理,都是配置在数据库中的,一般由五张表组成,即RBAC。

      1)RBAC数据模型的五张表

      用户表,存储用户信息,由业务人员维护;

      角色表,存储角色信息,由业务人员维护 ;

      资源表,存储资源信息(菜单、按钮及其URL),由开发人员维护;

      用户-角色关系表,存储用户和角色的对应关系,多对多,由业务人员维护;

      角色-资源关系表,存储角色和资源的对应关系 ,多对多,由业务人员维护;

      2)代码实现

      RbacService.java

    public interface RbacService {
        boolean hasPermission(HttpServletRequest request,Authentication authentication);
    }

      RbacServiceImpl.java

    @Component("rbacService")
    public class RbacServiceImpl implements RbacService{
    private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public boolean hasPermission(HttpServletRequest request, Authentication authentication) { System.out.println("---------进入hasPermission方法:url"+request.getRequestURI()); Object principal = authentication.getPrincipal(); boolean hasPermission = false; if(principal instanceof UserDetails) { String username = ((UserDetails)principal).getUsername(); //根据username,读取用户所拥有权限的所有url Set<String> urls = new HashSet<>(); urls.add("/user"); urls.add("/index.html"); for(String url:urls) { if(antPathMatcher.match(url, request.getRequestURI())) { hasPermission = true; break; } } } return hasPermission; } }

      修改DempAuthorizeConfigProvider.java

    @Component
    @Order(Integer.MAX_VALUE)//指定顺序,最后读取
    public class DempAuthorizeConfigProvider implements AuthorizeConfigProvider {
    @Override
    public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) { /**config.antMatchers("/user", "/demo.html" ).hasRole("ADMIN"); */ config.anyRequest().access("@rbacService.hasPermission(request,authentication)"); //anyRequest放到最后读,注释掉MyAuthorizeConfigManager中的anyRequest,否则会覆盖此处的anyRequest } }

      修改MyAuthorizeConfigProvider.java

    @Component
    @Order(Integer.MIN_VALUE)//指定顺序,先读取
    public class MyAuthorizeConfigProvider implements AuthorizeConfigProvider {
    
        @Override
        public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
            config.antMatchers(
                    "/login.html",
                    "/authentication/require",
                    "/code/image",
                    "/session/invalid",
                    "/logout.html"
            ).permitAll();
        }
    }

      修改MyAuthorizeConfigManager.java

    @Component
    public class MyAuthorizeConfigManager implements AuthorizeConfigManager {
    
        //Spring启动的时候,所有AuthorizeConfigProvider的接口的实现都(自动)放在AuthorizeConfigProviders中
        @Autowired
        //private Set<AuthorizeConfigProvider> AuthorizeConfigProviders;
        private List<AuthorizeConfigProvider> AuthorizeConfigProviders;//改为有序集合
        
        @Override
        public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
            for(AuthorizeConfigProvider authorizeConfigProvider:AuthorizeConfigProviders) {
                authorizeConfigProvider.config(config);
            }
            //config.anyRequest().authenticated();//所有请求需要身份认证,注销掉,因为DempAuthorizeConfigProvider中有anyRequest了
        }
    }

      说明:

      a)通过设置@Order注解,使通用url先读取,首选执行permitAll;自定义url后读取,执行rbacService.hasPermission方法。

      b)DempAuthorizeConfigProvider中指定了anyRequest访问hasPermission方法,MyAuthorizeConfigManager中去掉最后一句。

      c)MyAuthorizeConfigManager中改为有序集合List,先实现AuthorizeConfigProvider的bean先被读取,与@Order对应。

      测试:

      http://localhost/login.html可以访问,输入用户名、密码登录。

      登录后访问http://localhost/user或者http://localhost/index.html,有权限,因为hasPermission方法中设置了。

      登录后访问http://localhost/user/1或者http://localhost/demo.html,报403无权限。

      

      

      

  • 相关阅读:
    Code Forces 650 C Table Compression(并查集)
    Code Forces 645B Mischievous Mess Makers
    POJ 3735 Training little cats(矩阵快速幂)
    POJ 3233 Matrix Power Series(矩阵快速幂)
    PAT 1026 Table Tennis (30)
    ZOJ 3609 Modular Inverse
    Java实现 LeetCode 746 使用最小花费爬楼梯(递推)
    Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)
    Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)
    Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)
  • 原文地址:https://www.cnblogs.com/javasl/p/13215687.html
Copyright © 2011-2022 走看看