• 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无权限。

      

      

      

  • 相关阅读:
    Android -- 自定义View小Demo,动态画圆(一)
    Android -- 自定义View小Demo,绘制四位数随机码(一)
    Android设计模式---观察者模式小demo(一)
    Android -- 自定义View小Demo,绘制钟表时间(一)
    Android -- 自定义View小Demo,关于Rect绘制Android机器人(一)
    Android -- 自定义View小Demo,关于Path类的使用(一)
    Android -- 自定义View小Demo(一)
    Android -- 自定义View(一)
    自定义Scrollview--实现仿淘宝Toolbar透明度渐变效果
    究竟什么是技术——非科班程序员两年的内心挣扎
  • 原文地址:https://www.cnblogs.com/javasl/p/13215687.html
Copyright © 2020-2023  润新知