在权限管理之Spring Security(一)中,可以说完成了一个最简单的可以使用的demo,但是在实际情况中,我们通常希望权限是存储在数据库中的而不是在代码中以注解的方式写死。本文来探讨security基于数据库的动态权限。
首先我们来理解一下如何做到权限控制,在我看来,其实就是判断当前用户是否可以访问这个url,如何来判断呢,实际上就是看用户所谓的权限列表中有没有当前这个url。最根本的权限管理如何做,数据库存储用户能访问的url,当访问链接时,判断当前url是否在用户可访问的列表中,是则允许访问,否则无权限。
security实际上已经帮我们完成了判断的过程,我们只需要提供用户的权限即可,但是security是根据所谓的权限标识来进行判断的(即ROLE),所以我们需要获取所有的url,并为每个url指定一个权限标识,配置代码如下(延用之前的demo):
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new MyUserDetailsService());
}
@Data
class Permission{
private String url;
private String ROLE;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//构造权限
Set<Permission> set = new HashSet<>();
Permission permission = new Permission();
permission.setUrl("/admin/**");
permission.setROLE("ADMIN");
set.add(permission);
Permission permission1 = new Permission();
permission1.setUrl("/role");
permission1.setROLE("USER");
set.add(permission1);
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry urlRegistry = http.formLogin().permitAll()
.and()
.authorizeRequests()
.antMatchers("/test/**").permitAll();
set.forEach(permission2 -> {
urlRegistry.antMatchers(permission2.getUrl()).hasRole(permission2.getROLE());
});
urlRegistry.anyRequest().authenticated();
}
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;
}
}
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
}
在上面的代码中,我构造了两个url的权限,在实际开发中,这里应该从数据库查出所有的接口权限,同时此处也可以额外添加其他的配置,如限制ip,以及过滤接口等。详见官方文档
security的授权核心是 AccessDecisionManager以及AccessDecisionVoter,但是我并不推荐去自己实现这两个对象来达到数据库动态权限配置的目的,因为那样很麻烦并且存在一定的问题。我写的这种方式是我认为比较合理的,而且不用过多的配置即可完成一整套的功能。当然,可能是我对它的认识还不够准确,有不对的地方敬请指正。最后附上基于AccessDecisionManager以及AccessDecisionVoter实现动态权限配置的链接。