springboot 簡單集成 spring security(二)權(quán)限控制
簡介
上一篇我們實現(xiàn)了springboot簡單的集成了spring security,實現(xiàn)了在用戶想要實現(xiàn)某一操作時需要進(jìn)行登陸認(rèn)證,但是在登陸后用戶能夠進(jìn)行所有想要的操作,這并不是我們想要的,我們需要的是當(dāng)用戶訪問后,用戶能夠操作的是我們能夠控制他能夠操作接口。這里我們就需要進(jìn)行權(quán)限的控制。
1、內(nèi)存中實現(xiàn)權(quán)限控制
在上一篇中我們在實現(xiàn)用戶認(rèn)證后就沒有進(jìn)行相關(guān)的操作,這里我們想要在內(nèi)存中實現(xiàn)權(quán)限認(rèn)證。需要在securityConfig中重寫configure(HttpSecurity http)方法,在接口中進(jìn)行設(shè)置。
configure(HttpSecurity http)
在當(dāng)前方法中能夠?qū)崿F(xiàn)對接口訪問權(quán)限設(shè)置。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
//設(shè)置所有認(rèn)證后的用戶都能夠訪問所有接口,不需要任何權(quán)限
.anyRequest().authenticated()
//設(shè)置不需要登陸認(rèn)證就能夠訪問登陸接口
.and()
.formLogin()
.permitAll()
// 設(shè)置不需要登陸認(rèn)證就能夠訪問登出接口
.and()
.logout()
.permitAll();
}
如果我們想要對接口進(jìn)行權(quán)限控制這里我們就要將
//.anyRequest().authenticated()
注釋掉,然后再添加
.
antMatchers
(
""
)
.
hasAnyAuthority
(
""
)
然后添加想要進(jìn)行權(quán)限控制的接口和設(shè)置訪問當(dāng)前接口需要的權(quán)限我們這里只寫了/selByPhone接口所以我們的代碼如下
.
antMatchers
(
"/selByPhone"
)
//設(shè)置訪問當(dāng)前接口需要的權(quán)限
.
hasAnyAuthority
(
"ROLE_query_user"
)
這里我們設(shè)置當(dāng)前接口需要的權(quán)限是"ROLE_query_user"。完整的securityConfig的configure(HttpSecurity http)方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// .anyRequest().authenticated()
//配置訪問當(dāng)前接口需要的權(quán)限
.antMatchers("/selByPhone")
.hasAnyAuthority("ROLE_query_user")
.and()
.formLogin()
.permitAll()
.and()
.logout()
.permitAll();
}
然后再數(shù)據(jù)庫中將用戶的權(quán)限設(shè)置為query_user
然后訪問selByPhone接口
!](https://img-blog.csdnimg.cn/88043ebba4ad4e3185f343412f8a1bc0.jpeg)
首先登陸沒有訪問權(quán)限的用戶
登陸后我們能夠看到當(dāng)前用戶沒有訪問權(quán)限spring security返回了403頁面。
我們登陸有權(quán)限的用戶,我們看到能夠成功登陸
當(dāng)然這種設(shè)置權(quán)限的方式并不是我們想要的,我們不能在寫完接口后然后再securityConfig中進(jìn)行配置,每次都進(jìn)行這種配置并不是我們需要的,這時我們希望能夠靈活的配置權(quán)限控制。小編這里基于jdbc進(jìn)行動態(tài)的權(quán)限配置。當(dāng)然并不只這一種方式,比如枚舉類等等。
2、基于jdbc動態(tài)的權(quán)限控制
想要實現(xiàn)基于jdbc動態(tài)的權(quán)限控制,我們需要攔截請求獲取到當(dāng)前要訪問的接口名稱,然后再去查詢想要訪問當(dāng)前接口需要的權(quán)限。
想要實現(xiàn)攔截請求的功能那我們就需要創(chuàng)建一個連接器,這里我們實現(xiàn)Filter完成權(quán)限攔截器。然后實現(xiàn)SecurityMetadataSource(安全元數(shù)據(jù)),完成查詢當(dāng)前需要的權(quán)限的功能,這里面我們使用他的子類FilterInvocationSecurityMetadataSource。然后我們再實現(xiàn)AccessDecisionManager(決策訪問管理器)在這里我們判斷當(dāng)前登陸的用戶是否擁有訪問該接口的權(quán)限。
權(quán)限攔截器
/**
* @title: PermissionInterceptor
* @Author zzm
* @Date: 2022/4/14 19:49
* @Version 1.0
*/
@Service
public class CustomizeAbstractSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(CustomizeAccessDecisionManager accessDecisionManager) {
super.setAccessDecisionManager(accessDecisionManager);
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi里面有一個被攔截的url
//里面調(diào)用MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取fi對應(yīng)的所有權(quán)限
//再調(diào)用MyAccessDecisionManager的decide方法來校驗用戶的權(quán)限是否足夠
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//執(zhí)行下一個攔截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
}
這個攔截器是攔截我們請求的url,將當(dāng)前的url執(zhí)行下一個攔截器
訪問決策管理器
/**
* @title: CustomizeFilterInvocationSecurityMetadataSource
* @Author zzm
* @Date: 2022/4/14 19:56
* @Version 1.0
*/
@Component
public class CustomizeFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Resource
private PermissionsMapper permissionsMapper;
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
//獲取請求地址
String requestUrl = ((FilterInvocation) o).getRequestUrl();
//查詢當(dāng)前接口需要的權(quán)限
List<Role> role = permissionsMapper.findRole(requestUrl);
if(role == null ||role.size() == 0){
return null;
}
String[] attributes = new String[role.size()];
for (int i = 0; i < role.size(); i++) {
attributes[i] = "ROLE_"+role.get(i).getRole();
}
return SecurityConfig.createList(attributes);
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
/**
* 這里一定到改成true要不然啟動會報錯
* @param aClass
* @return
*/
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
這里supports方法中返回的boolean類型的數(shù)據(jù)要返回true,默認(rèn)是false。如果是false就會報錯(圖片)
訪問決策管理器
/**
* @title: CustomizeAccessDecisionManager
* @Author zzm
* @Date: 2022/4/14 20:13
* @Version 1.0
*/
@Component
public class CustomizeAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = collection.iterator();
while (iterator.hasNext()) {
ConfigAttribute ca = iterator.next();
//當(dāng)前請求需要的權(quán)限
String needRole = ca.getAttribute();
//當(dāng)前用戶所具有的權(quán)限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("權(quán)限不足!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
這里通過比對用戶權(quán)限和當(dāng)前url需要的權(quán)限,判斷當(dāng)前用戶的權(quán)限能夠訪問url。
配置文件
寫完攔截器后我們需要在WebSecurityConfig中進(jìn)行配置。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(accessDecisionManager);//決策管理器
o.setSecurityMetadataSource(securityMetadataSource);//安全元數(shù)據(jù)源
return o;
}
})
}
WebSecurityConfig最終
/**
* @title: SecurityConfig
* @Author zzm
* @Date: 2022/4/7 21:28
* @Version 1.0
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
//訪問決策管理器
@Autowired
CustomizeAccessDecisionManager accessDecisionManager;
//實現(xiàn)權(quán)限攔截
@Autowired
CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
private CustomizeAbstractSecurityInterceptor securityInterceptor;
@Bean
public PasswordEncoder passwordEncoder(){
//提供加密方式
return new BCryptPasswordEncoder();
}
//配置登陸驗證方式
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//指定登陸用我們自己的方法
auth.userDetailsService(userService)
.passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(accessDecisionManager);//決策管理器
o.setSecurityMetadataSource(securityMetadataSource);//安全元數(shù)據(jù)源
return o;
}
})
.and()
.formLogin()
.permitAll()
.and()
.logout()
.permitAll();
http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);
}
}
3、結(jié)果
在數(shù)據(jù)庫中設(shè)置訪問/selByPhone接口的權(quán)限為query_user,用戶賬號為123的權(quán)限為query_user,用戶456的訪問權(quán)限為user。
當(dāng)?shù)卿涃~戶為456時
當(dāng)?shù)卿涃~戶為123時
