Spring Security基于数据库配置权限(角色,路径)
Spring Security基于数据库配置权限(角色,路径)传统的后台管理系统,在权限处理上通常5个表:用户表,角色表,资源表,用户角色关联表,角色资源关联表。现在为了避免重复造轮子,自己写拦截处理,我们可以使用Spring Security来做权限控制。Spring Security官方推荐通过配置来实现角色和资源的对应,这样的问题是假如需要线上配置角色与资源对应就不行了,所以下面
Spring Security基于数据库配置权限(角色,路径)
传统的后台管理系统,在权限处理上通常5个表:用户表,角色表,资源表,用户角色关联表,角色资源关联表。现在为了避免重复造轮子,自己写拦截处理,我们可以使用Spring Security来做权限控制。
Spring Security官方推荐通过配置来实现角色和资源的对应,这样的问题是假如需要线上配置角色与资源对应就不行了,所以下面讲讲如何基于数据库中存储角色,资源,用户 来使用Spring Security做权限控制。
1.首先是数据准备
创建权限相关的五张表和对应的实体类。以及相关的查询实现等。
2.配置类SecurityConfiguration
只要使用spring security都要有这样一个配置类(如果是类配置的话),继承WebSecurityConfigurerAdapter
主要实现两个方法configure(AuthenticationManagerBuilder auth)
和
configure(HttpSecurity http)
AuthenticationManagerBuilder
主要配置身份认证来源,也就是用户及其角色。HttpSecurity
主要配置路径,也就是资源的访问权限(是否需要认证,需要什么角色等)。
2.1 configure(AuthenticationManagerBuilder auth)
相关
该方法本身只需要设置一个身份认证来源,所以我们需要先写个方法来创建该来源
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); //创建密码加密对象
}
/**
* @return 封装身份认证提供者
*/
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(customUserService); //自定义的用户和角色数据提供者
authenticationProvider.setPasswordEncoder(passwordEncoder()); //设置密码加密对象
return authenticationProvider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider()); //设置身份认证提供者
}
自定义的类需要实现UserDetailsService
实现接口中的方法
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
UserDetails中包含用户的用户名,密码,角色等信息。所以需要自己创建一个类实现UserDetails
,这样方便我们赋值我们查到的角色集合
比如创建一个UserPrincipal
public class UserPrincipal implements UserDetails {
private User user;
private List<String> roleCodes;
UserPrincipal(User user,List<String> roleCodes){
this.user = user;
this.roleCodes = roleCodes;
}
/**
* 设置用户角色集合
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roleCodes.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
......
}
CustomUserService
提供用户和对应的角色
public class CustomUserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username); //根据用户名查询用户
if (user == null) {
throw new UsernameNotFoundException("can not found username " + username);
}
return new UserPrincipal(user,getRoleCodes(user));
}
private List<String> getRoleCodes(User user){
return ....//根据用户查询该用户拥有的角色编号集合
}
}
这样就完成了对用户和角色的查询,接下来就是配置路径(资源)和角色映射了
2.2 configure(HttpSecurity http)
相关
主要就配置登陆路径,错误页面路径,后置处理程序等。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginPage("/login").defaultSuccessUrl("/",true)
.and()
.exceptionHandling().accessDeniedPage("/error")
.and()
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.withObjectPostProcessor(postProcessor);//设置后置处理程序对象
}
加上前面的代码,配置类基本就完成了,记得加上@Configuration注解。接下来就是利用角色对路径进行判断了,这部分处理都封装在postProcessor
中的 FilterSecurityInterceptor
中。
3.权限处理配置 postProcessor
现在需要定义一个类来实现 ObjectPostProcessor<FilterSecurityInterceptor>
public class CustomPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
@Override
public <T extends FilterSecurityInterceptor> T postProcess(T fsi) {
fsi.setAccessDecisionManager(accessDecisionManager); //权限决策处理类
fsi.setSecurityMetadataSource(filterSecurityMetadataSource); //路径(资源)拦截处理
return fsi;
}
}
3.1 路径拦截处理类
filterSecurityMetadataSource
需要实现 FilterInvocationSecurityMetadataSource
接口,Collection<ConfigAttribute> getAttributes(Object object)
会返回改路径所需的角色集合到权限决策处理类中供其使用.
public class CustomFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object; //当前请求对象
if (isMatcherAllowedRequest(fi)) return null ; //return null 表示允许访问,不做拦截
List<ConfigAttribute> configAttributes = getMatcherConfigAttribute(fi.getRequestUrl());
return configAttributes.size() > 0 ? configAttributes : deniedRequest(); //返回当前路径所需角色,如果没有则拒绝访问
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class< ? > aClass) {
return FilterInvocation.class.isAssignableFrom(aClass);
}
/**
* 获取当前路径所需要的角色
* @param url 当前路径
* @return 所需角色集合
*/
private List<ConfigAttribute> getMatcherConfigAttribute(String url){
return roleResourceRepository.findByResource_ResUrl(url).stream()
.map(roles -> new SecurityConfig(roles.getRole().getRoleCode()))
.collect(Collectors.toList());
}
/**
* 判断当前请求是否在允许请求的范围内
* @param fi 当前请求
* @return 是否在范围中
*/
private boolean isMatcherAllowedRequest(FilterInvocation fi){
return allowedRequest().stream().map(AntPathRequestMatcher::new)
.filter(requestMatcher -> requestMatcher.matches(fi.getHttpRequest()))
.toArray().length > 0;
}
/**
* @return 定义允许请求的列表
*/
private List<String> allowedRequest(){
return Arrays.asList("/login","/css/**","/fonts/**","/js/**","/scss/**","/img/**");
}
/**
* @return 默认拒绝访问配置
*/
private List<ConfigAttribute> deniedRequest(){
return Collections.singletonList(new SecurityConfig("ROLE_DENIED"));
}
}
3.2 权限决策处理
accessDecisionManager
需要实现 AccessDecisionManager
接口。实现方法decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
来做权限决策
authentication
可获取当前用户拥有的角色集合configAttributes
路径拦截处理中查询到的,能访问当前路径的角色集合
public class CustomAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
if(authentication == null){
throw new AccessDeniedException("permission denied");
}
//当前用户拥有的角色集合
List<String> roleCodes = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
//访问路径所需要的角色集合
List<String> configRoleCodes = configAttributes.stream().map(ConfigAttribute::getAttribute).collect(Collectors.toList());
for (String roleCode : roleCodes){
if(configRoleCodes.contains(roleCode)){
return;
}
}
throw new AccessDeniedException("permission denied");
}
}
这样就完成了基于数据库存储的Spring Security权限控制实现。
更多推荐
所有评论(0)