权限管理之Spring Security(一)

2022-07-31,,,

本文只是我对security的一些简单看法,如有错误,敬请指正。

从最简单的demo说起

一个最简单的springboot+security的demo到底可以简单到什么程度?请看以下代码:

null

 

如果说maven引入jar包不算代码的话,那么最简单的启用security没有任何代码!首先创建一个springboot项目,引入security以及web即可

当我们引入spring-boot-starter-security 之后就已经默认启用了security,在这之后,只有通过验证的用户才可以访问,在web环境下,security会有一个默认的登录界面和默认的用户(用户名为user,密码打印在控制台)

系统的用户肯定是由我们自己管理的而不是由security默认的,所以我们需要给security提供用户支持,这里就是security的一个核心所在,认证!

security支持多种用户认证方式,常用的有ldap,jdbc等等。你只需要根据相应的认证方式进行配置就可以使用该认证方式进行工作。以jdbc为例,我们只需要配置查询用户的sql,DataSource就可以使security从我们的数据来查询用户进行认证了。认证的核心java配置如下

1,新建security包

2,新建SecurityConfig类,继承WebSecurityConfigurerAdapter类(security的默认配置类),重写configureGlobal(AuthenticationManagerBuilder auth)方法

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        
    }
}

当我们需要快速使用security进行用户认证时,只需要在这个方法里进行配置即可,以内存认证为例:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user").password("123456").roles("ADMIN");
    }
}

以上代码我们在内存中定义了一个用户名为user,密码为123456,角色为ADMIN的用户,我们现在启动项目,然后进行登录认证,会发现报错

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

这是因为在新版本中,security必须指定密码编码器,通常情况下我们使用的是BCryptPasswordEncoder,

在这里我们配置为明文密码NoOpPasswordEncoder,详见  官方文档

    @Bean
    public static NoOpPasswordEncoder passwordEncoder() {
        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
    }

再次启动项目,进行认证会认证成功!

auth中包含许多的认证方式,我们需要用到那种就用那种即可。用这些的好处是快,能够快速的对应用进行安全保护。但通常情况下,我们不会选择这些默认的方式,我们需要自己实现自己的认证方式,就需要用到两个关键的对象。1,UserDetailsService,它是security提供的一个接口,唯一的方法是 loadUserByUsername,作用是根据用户名获取用户对象;2,UserDetails,它是security的用户对象接口,security已经默认实现了该接口。但不管怎么样,只要认证,都需要在configureGlobal(AuthenticationManagerBuilder auth) 这个方法里进行配置

当我们需要自定义认证时,首先需要的是实现UserDetailsService接口,代码如下:

class MyUserDetailsService implements UserDetailsService {

        private Map<String, User> userRepository = new HashMap<String, User>();

        public MyUserDetailsService() {
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
            User user1 = new User("user1", "password1", authorities);
            userRepository.put("user1", user1);
            User user2 = new User("user2", "password2", authorities);
            userRepository.put("user2", user2);
        }

        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            User user = userRepository.get(s);
            return user;
        }
    }

在上面的代码中,我们实现了UserDetailsService接口,重写了loadUserByUsername方法。在这里我们构造了两个用户以及他们的角色列表。在实际情况在,这里远远不是这样,我们可以根据自己的需求来获取用户信息。只需要理解一点,这个方法是用来根据用户名获取用户信息的(包含角色列表,账号是否启用等等)。

接下来,我们需要在configureGlobal(AuthenticationManagerBuilder auth)这个方法里设置我们自己的UserDetailsService

@Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(new MyUserDetailsService());
    }

到这里,我们已经完成了基础的认证,总之认证的核心方法就是configureGlobal,而自定义认证的核心是实现UserDetailsService接口。具体请看  官方文档​​​​​​

当我们完成认证之后,接下来就是授权,用户到底能够访问那些链接。在上面的认证中,我们说到loadUserByUsername这个方法主要是根据用户名查询用户信息,包括用户的角色信息,在授权这里,我们就需要用到用户的角色信息。

首先我们需要开启security基于注解授权的功能,只需要在配置类上加 “@EnableGlobalMethodSecurity(prePostEnabled = true) ”注解即可

其次,我们来改造一下启动类,增加几个放法,代码如下:

@SpringBootApplication
@RestController
public class SimpleSecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SimpleSecurityApplication.class, args);
    }

    @GetMapping("test")
    public String test(){
        return "test!";
    }

    @GetMapping("role")
    @PreAuthorize("hasRole('USER')")
    public String role(){
        return "role!";
    }

    @GetMapping("admin")
    @PreAuthorize("hasRole('ADMIN')")
    public String admin(){
        return "admin!";
    }
}

可以看到,test方法是都可以访问的,role需要角色USER才可以访问,admin需要角色ADMIN才可以访问,接下来我们启动项目会得到预期结果。至此,springboot整合security最基础的demo完成,如果要进行实际使用,我们只需要完善MyUserDetailsService 类即可,同时为每个方法分配角色就可以完成对系统权限的管理。

在我看来,security强大的地方是在于方法级别的权限控制,在访问方法的过程中进行权限控制,访问前满足条件允许访问,访问时满足条件允许访问,访问后满足条件允许访问之类的控制,就是aop(官方文档)。所以security貌似没有所谓的权限这一概念,而是通过role来限制用户是否可以访问接口。我们常用的权限管理模型为RBAC模型,我认为,如果用security来实现的话,我们应该将security中ROLE概念认为是权限,每个方法一个ROLE。通过在数据库里配置用户拥有的ROLE(权限,非角色)即可实现权限控制。

本文地址:https://blog.csdn.net/qq_28726483/article/details/85921614

《权限管理之Spring Security(一).doc》

下载本文的Word格式文档,以方便收藏与打印。