Spring Boot 2.x实战79 - Spring Security 3 - Spring Security的授权(Authorization)之Web路径安全

1.4 Authorization

一旦认证成功后,我们下一步要做的就是授权(访问控制)。授权或访问控制意味着用户能或不能访问受保护的资源。

1.4.1 Web路径安全

我们首先学习对Web路径的安全控制,接上面认证的过程,我们从FilterSecurityInterceptor开始:

  • FilterSecurityInterceptor:它是AbstractSecurityInterceptor的子类,当认证成功后,再使用AccessDecisionManager对Web路径资源(web URI)进行授权操作。

  • FilterInvocationSecurityMetadataSource:存储Web路径安全元数据并提供查询功能,用来存储系统中所有的Web路径安全配置,包括请求路径与对应的ConfigAttributerequestMap,当请求web路径/everyCanAccess时,会从requestMap中获取它的ConfigAttributepermitAll()FilterInvocationSecurityMetadataSource的功能由它的子类ExpressionBasedFilterInvocationSecurityMetadataSource实现。

  • AccessDecisionManager:类似于AuthenticationManagerProviderManager)包含一组AuthenticationProviderAccessDecisionManager(默认实现为AffirmativeBased)也包含一组AccessDecisionVoter

    public interface AccessDecisionManager {
       void decide(Authentication authentication, Object object,
             Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
             InsufficientAuthenticationException; //1
       boolean supports(ConfigAttribute attribute); 
       boolean supports(Class<?> clazz); 
    }
    
    1. decide通过三个参数决定是否通过授权:
      • Authentication authenticationAuthentication对象里包含了用户的权限信息,可通过getAuthorities获取;
      • Object object:被保护的资源对象,在Web路径中被保护资源对象的类型为FilterInvocation;
      • Collection<ConfigAttribute> configAttributes:和被保护资源对象一起的配置属性;
    2. AffirmativeBasedsupports(ConfigAttribute attribute)
    3. AffirmativeBasedsupports(Class<?> clazz)
  • AffirmativeBased:包含一组AccessDecisionVoter,只要有一个AccessDecisionVoter授权访问,整个访问立即被授权,无论其它的是否拒绝。

    • decide:让一组AccessDecisionVoter每一个都进行投票;
    • supports(ConfigAttribute attribute):当前的AccessDecisionManager包含的一组AccessDecisionVoter中若有支持该配置属性的则为支持;
    • supports(Class<?> clazz):确认每一个AccessDecisionVoter都支持被保护资源对象的类型处理。
  • AccessDecisionVoter:路径安全控制的主要实现为WebExpressionVoter,通过计算表达式的结果来进行访问控制。

我们重载WebSecurityConfigurerAdapter的方法configure(HttpSecurity http),通过HttpSecurity来配置web路径的安全访问。

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests() //1
                .antMatchers("/everyCanAccess").permitAll() //2
                .antMatchers("/authenticatedCanAccess").authenticated() //3
                .antMatchers("/adminCanAccess").hasRole("ADMIN") //4
                .antMatchers("/userCanAccess").access("hasRole('USER') or hasRole('ADMIN')") //5
               .antMatchers("/threeCanAccess").access("@webSecurity.checkUsernameLenEq3(authentication)")//6
                .anyRequest().authenticated()//7
                .and()
                .httpBasic().authenticationEntryPoint(authenticationEntryPoint());
    }

    @Bean
    AuthenticationEntryPoint authenticationEntryPoint(){
      //...
    }

    @Bean
    UserDetailsService userDetailsService(SysUserRepository sysUserRepository){
        return new CusotmUserDetailsService(sysUserRepository);
    }

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

}
  1. authorizeRequests方法对所有的请求进行授权,它由底下的多个部分组成;

  2. antMatchers方法中路径/everyCanAccess允许任何人访问;

    • antMatchers支持HTTP方法来缩小匹配:antMatchers(HttpMethod.GET,"/everyCanAccess")
    • 若需要匹配路径下的所有路径可使用/**antMatchers("/secure/**")
  3. antMatchers方法中路径/authenticatedCanAccess需要认证后才能访问;

  4. antMatchers方法中路径/adminCanAccess需要用户有ROLE_ADMIN角色;

  5. antMatchers方法中路径/userCanAccess需要用户有ROLE_ADMINROLE_USER角色,access可使用其它表达式;

  6. antMatchers方法中路径/threeCanAccess,通过access中的@通过表达式调用Bean的方法进行访问控制;

    WebSecurity

    @Service
    public class WebSecurity {
        public boolean checkUsernameLenEq3(Authentication authentication){
           if ( authentication.getName().length() == 3)
               return true;
           return false;
        }
    }
    
  7. 其余的路径都需通过认证。

上面的访问控制方法都是配置属性(ConfigAttribute):可作为配置属性的表达式:

表达式 描述
hasRole([role]) 当前用户包含指定角色可访问web路径,需省略ROLE_前缀。
hasAnyRole([role1,role2]) 当前用户包含任意角色可访问web路径,角色之间用“,”隔开,需省略ROLE_前缀。
hasAuthority([authority]) 当前用户包含指定的权限可访问web路径。
hasAnyAuthority([authority1,authority2]) 当前用户包含任意权限可访问web路径,角色之间用“,”隔开。
principal 用户信息。
authentication 允许从SecurityContext中直接访问Authentication对象。
permitAll() 允许所有访问
denyAll() 总是不可访问指定的web路径。
anonymous() 匿名用户可访问指定的web路径。
rememberMe() remember-me 用户可访问指定的web路径。
authenticated() 当前用户不是匿名用户可访问指定的web路径。
fullyAuthenticated() 当前用户不是匿名用户又不是remember-me用户可访问指定的web路径。
hasIpAddress 只有来自指定ip(或子网)的用户可访问web路径
access 可接受上面的任意的表达式,可调用Bean的方法。

受保护的控制器定义:

@RestController
public class IndexController {
    @GetMapping("/everyCanAccess")
    public String everyCanAccess(){
        return "任何用户可访问";
    }

    @GetMapping("/authenticatedCanAccess")
    public String authenticatedCanAccess(){
        return "任何登录用户可访问";
    }

    @GetMapping("/userCanAccess")
    public String userCanAccess(){
        return "角色为ROLE_USER或ROLE_ADMIN的用户都可访问";
    }
  
    @GetMapping("/adminCanAccess")
    public String adminCanAccess(){
        return "角色为ROLE_ADMIN用户可访问";
    }

    @GetMapping("/threeCanAccess")
    public String threeCanAccess(){
        return "只有用户名字符串长度为3的用户可以访问";
    }
}

权限的获取是通过SysUser重载UserDetailsgetAuthorities方法获得,我们定义新的role字段作为权限。

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class SysUser implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String realName;

    @Column(unique = true)
    private String username;

    private String password;

    private String role;

    public SysUser(String realName, String username, String password, String role) {
        this.realName = realName;
        this.username = username;
        this.password = password;
        this.role = role;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.role);
        authorities.add(authority);
        return authorities;
    }
	// ...
}

用户权限用GrantedAuthority接口代表,SimpleGrantedAuthority是它的实现类,可接受字符串的权限值。我们删除数据库的wyf用户数据,并重新初始化2条数据。

@Bean
CommandLineRunner createUser(SysUserRepository sysUserRepository, PasswordEncoder passwordEncoder){
   return args -> {
      SysUser user = new SysUser("wangyunfei", "wyf", passwordEncoder.encode("111111"), "ROLE_USER");
      SysUser admin = new SysUser("administrator", "admin", passwordEncoder.encode("admin"), "ROLE_ADMIN");
      sysUserRepository.save(user);
      sysUserRepository.save(admin);
   };
}

现在我们的两个用户

  • admin:角色为ROLE_ADMIN,密码为111111
  • wyf:觉得为ROLE_USER,密码为admin

验证http://localhost:8080/everyCanAccess,任何人可访问无需登录:
在这里插入图片描述

验证http://localhost:8080/authenticatedCanAccess,任何认证用户可访问:
在这里插入图片描述

验证http://localhost:8080/adminCanAccess,只有角色为ROLE_ADMIN的用户可访问:
在这里插入图片描述

验证http://localhost:8080/userCanAccess,角色为ROLE_ADMINROLE_USER的用户可访问:
在这里插入图片描述

验证http://localhost:8080/threeCanAccess,只有用户名字符串长度为3的用户才能访问:
在这里插入图片描述

新书推荐:

我的新书《从企业级开发到云原生微服务:Spring Boot 实战》已出版,内容涵盖了丰富Spring Boot开发的相关知识
购买地址:https://item.jd.com/12760084.html
在这里插入图片描述
主要包含目录有:

第一章 初识Spring Boot(快速领略Spring Boot的美丽)
第二章 开发必备工具(对常用开发工具进行介绍:包含IntelliJ IDEA、Gradle、Lombok、Docker等)
第三章 函数式编程
第四章 Spring 5.x基础(以Spring 5.2.x为基础)
第五章 深入Spring Boot(以Spring Boot 2.2.x为基础)
第六章 Spring Web MVC
第七章 数据访问(包含Spring Data JPA、Spring Data Elasticsearch和数据缓存)
第八章 安全控制(包含Spring Security和OAuth2)
第九章 响应式编程(包含Project Reactor、Spring WebFlux、Reactive NoSQL、R2DBC、Reactive Spring Security)
第十章 事件驱动(包含JMS、RabbitMQ、Kafka、Websocket、RSocket)
第11章 系统集成和批处理(包含Spring Integration和Spring Batch)
第12章 Spring Cloud与微服务
第13章 Kubernetes与微服务(包含Kubernetes、Helm、Jenkins、Istio)
多谢大家支持。

猜你喜欢

转载自blog.csdn.net/wiselyman/article/details/106853287