springsecurity控制授权
# 控制权限与用户体验

# 简单权限控制与rbac权限控制
一般公司应用有给顾客用的业务系统和公司内部管理的系统

# 业务系统简单权限控制
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers(HttpMethod.GET,"/admin/**").hasRole("ADMIN") // 对应角色:ROLE_ADMIN
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated(); // 其它的请求需要认证
2
3
4
5
# 1、spring security 过滤器链
spring security中的除了用户登录校验相关的过滤器,最后还包含了鉴权功能的过滤器,还有匿名资源访问的过滤器链,相关的图解如下:
(opens new window)
FilterSecurityInterceptor
# 2、控制授权的相关类
这里是整个spring security的过滤器链中的授权流程中控制权限的类的相关图示:
这里主要是从AccessDecisionVoter的投票者(译称)把信息传递给投票管理者AccessDecisionManager,最终来判断是过还是不过(也就是有没有权限).有两种可能的类:
1.不管有多少请求投票不过,只要有一个过就可以通过(UnanimousBased);
2.不管有多少请求投票通过,只要有一个不通过就不让通过(AffirmativeBased);
3.比较投通过和不通过的个数,谁多久就按照谁的方式来(Consensusbased).
这里可以可能听起来有点绕,但实际上就是三种控制权限的方式类,我们可以认为Spring security已经帮我们做好了最终的判断,我们只需要当一个旁观者即可.
我们再来关注SecurityContextHolder这个类,他会将我们的权限信息封装到Authentication中,SecurityConfig则是相关的Spring security的配置信息,这个类会将相关的信息传递到ConfigAttribute中.
AbstractSecurityInterceptor 核心鉴权方法(精简版)
protected InterceptorStatusToken beforeInvocation(Object object) {
// 第一步:通过访问请求,找到匹配的权限配置
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
// 第二步:从SecurityContextHolder里面拿用户信息Authentication,至少会有一个AnonymousAuthenticationToken
Authentication authenticated = authenticateIfRequired();
//attemptAuthorization(object, attributes, authenticated);
try {
// 第三步:accessDecisionManager做决策,默认策略:有一个投票器通过,就鉴权通过
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException ex) {
// 发布事件,被ExceptionTranslationFilter去处理
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex));
throw ex;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 对AccessDeniedException进行处理
private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
authentication), exception);
}
// 匿名用户的话,则先保存请求信息,再用authenticationEntryPoint去处理
//this.requestCache.saveRequest(request, response);
// this.authenticationEntryPoint.commence(request, response, reason);
sendStartAuthentication(request, response, chain,
new InsufficientAuthenticationException(
this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
// 已登陆用户,则用accessDeniedHandler去处理
this.accessDeniedHandler.handle(request, response, exception);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 小结一下鉴权流程
1、根据请求,从security配置的权限里面,找出ConfigAttribute集合,如WebExpressionConfigAttribute (#oauth2.throwOnError(hasRole('ROLE_USER')))
2、从SecurityContextHolder里面拿登陆用户信息Authentication,至少会有一个AnonymousAuthenticationToken
3、第三步:accessDecisionManager做决策,默认策略:有一个投票器通过,就鉴权通过。如果不通过,则抛AccessDeniedException异常
4、第四步:ExceptionTranslationFilter接收到异常,如果是匿名用户的话,则先保存请求信息,再用AuthenticationEntryPoint去处理;如果是登陆用户,就用AccessDeniedHandler去处理
自定义AuthenticationEntryPoint参考文章:
Spring boot+Security OAuth2 爬坑日记(4)自定义异常处理 上_Yuit-CSDN博客 (opens new window)
# 3.配置简单的权限
这个在身份信息固定,并且不会经常变动的情况下可以按照如下配置,否则不建议这么做,这里只适用于简单的场景.
MyUserDetailsService:
private SocialUserDetails buildUser(String userId) {
// 根据用户名查找用户信息
//根据查找到的用户信息判断用户是否被冻结
/**
* 可以从数据库查出来用户名和密码进行比对,为了方便我这里就直接固定了
*/
String password = passwordEncoder.encode("123456");
logger.info("数据库密码是:"+password);
//注意这里配置角色的时候需要加ROLE_前缀
return new SocialUser(userId, password,
true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN"));
}
2
3
4
5
6
7
8
9
10
11
12
13
# 4.权限表达式
之前我们一直都有用到权限表达式,比如最常用的permitAll和Authenticated,这个权限表达式就是允许所有都能访问的意思,其他的相关的权限表达式如下所示:
这里可以改写权限的表达如下:
.antMatchers("/user/**").access("hasRole('ADMIN') and hasIpAddress('XXXXXX')")
.anyRequest() //所有请求
.authenticated() //都需身份认证
2
3
# 5.spring security控制授权代码封装

AuthorizeConfigProvider:
/**
* 这个是授权的类
*/
public interface AuthorizeConfigProvider {
void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
}
2
3
4
5
6
CityAuthorizeConfigProvider
//配置permitAll的路径
@Component
public class CityAuthorizeConfigProvider implements AuthorizeConfigProvider {
@Autowired
private SecurityProperties securityProperties;
@Override
public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
config.antMatchers(
"/static/**","/page/login","/page/failure","/page/mobilePage",
"/code/image","/code/sms","/authentication/mobile",securityProperties.getBrower().getSignUPUrl(),
"/user/register","/page/registerPage","/page/invalidSession","/page/logoutSuccess",securityProperties.getBrower().getSignOutUrl()
)
.permitAll();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
AuthorizeConfigManager: 负责将所有AuthorizeConfigProvider的实现组装起来,并且最后添加一个config.anyRequest().authenticated();
/**
* AuthorizeConfigManager
*/
public interface AuthorizeConfigManager {
void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config);
}
2
3
4
5
6
@Component
public class CityAuthorizeConfigManager implements AuthorizeConfigManager {
@Autowired
private Set<AuthorizeConfigProvider> authorizeConfigProviders;
@Override
public void config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
for (AuthorizeConfigProvider authorizeConfigProvider : authorizeConfigProviders) {
authorizeConfigProvider.config(config);
}
//除了自己配置的CityAuthorizeConfigProvider的内容外 其他的都需要认证
config.anyRequest().authenticated();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
authorizationConfigurerManager().config(http.authorizeRequests()); // 其它的请求需要认证
# 6、rbac权限
@Component
@Transactional
public class RbacUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private AdminRepository adminRepository;
/**
* (non-Javadoc)
*
* @see org.springframework.security.core.userdetails.UserDetailsService#
* loadUserByUsername(java.lang.String)
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("表单登录用户名:" + username);
Admin admin = adminRepository.findByUsername(username);
admin.getUrls();
return admin;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
@Order(Integer.MAX_VALUE)
public class RbacAuthorizeConfigProvider implements AuthorizeConfigProvider {
/** (non-Javadoc)
* @see com.imooc.security.core.authorize.AuthorizeConfigProvider#config(org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry)
*/
@Override
public boolean config(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config) {
config
.antMatchers(HttpMethod.GET, "/fonts/**").permitAll()
.antMatchers(HttpMethod.GET,
"/**/*.html",
"/admin/me",
"/resource").authenticated()
// 除了上面的请求,对于其他所有请求都使用如下的表达式判断是否有权限
.anyRequest()
.access("@rbacService.hasPermission(request, authentication)");
// 返回true表示当前配置包含了anyRequest配置
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 7、注解式授权
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().permitAll();
}
}
2
3
4
5
6
7
8
9
10
在controller或者service上添加注解, 注解式授权是基于aop
主要注解有:
- @Secured用户具有某个角色,可以访问方法
- @PreAuthorize适合进入方法前的权限认证,@PreAuthorize可以将登陆用户的roles/permissions参数传入方法中
- @PostAuthorize在方法执行之后校验,适合验证带有返回值的权限
- @PostFilter:方法返回数据进行过滤权限验证之后对数据进行过滤 留下用户名是 admin1 的数据,表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素
- @PreFilter: 传入方法数据进行过滤,进入控制器之前对数据进行过滤
参考: SpringSecurity认证授权注解使用_鲁健源的博客-CSDN博客 (opens new window)
参考文章:
Spring Security 控制授权 - 简书 (jianshu.com) (opens new window)
SpringSecurity控制授权(鉴权)功能介绍 - charlyFeng - 博客园 (cnblogs.com) (opens new window)
Mr-zhango/imooc-SpringSecurity: imooc-SpringSecurity (github.com) (opens new window)