最近使用Spring security进行细粒度的动态权限管理,就再花了一些时间梳理spring security的过程。其实spring security主要分为两个部分:(1)认证,Authentication。(2)授权。说实话,如果你看过源码的话,你就会知道spring security的认证授权都是通过一系列的过滤器来实现的。
官方介绍:https://spring.io/guides/topicals/spring-security-architecture/
先来说一下认证流程。
认证拦截AbstractAuthenticationProcessingFilter,如UsernamePasswordAuthenticationFilter等都继承于它(当然我们可以自己继承实现自己的操作如验证码、短信等的验证)。
实现认证过程即调用attemptAuthentication实现认证。
先看AbstractAuthenticationProcessingFilter的doFilter。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//1.先判断是否需要进行认证
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
Authentication authResult;
try {
//2.调用实现类进行认证
authResult = attemptAuthentication(request, response);
if (authResult == null) {
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
//3.认证失败回调failureHandler
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//4.认证成功
successfulAuthentication(request, response, chain, authResult);
}
认证的基本流程如下:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/hello").permitAll()
.anyRequest().authenticated()
}
}
以UsernamePasswordAuthenticationFilter为例,对用户名和密码进行认证。
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
this.getAuthenticationManager().authenticate(authRequest);会调用具体的AuthenticationProvider进行认证(当然我们也可以添加实现自己的Provider)。
ProviderManager的authenticate
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
//调用具体的provider进行认证
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
catch (AuthenticationException e) {
lastException = e;
}
}
认证失败调用unsuccessfulAuthentication(request, response, failed);
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
SecurityContextHolder.clearContext();
rememberMeServices.loginFail(request, response);
failureHandler.onAuthenticationFailure(request, response, failed);
}
我们这里看到有个failureHandler,那么我们可以自定义failureHandler,实现认证失败返回自定义的JSON。是不是很方便
认证成功 successfulAuthentication
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
认证成功主要内容为:把认证信息存入SecurityContextHolder;通知认证成功给用户。同样我们可以自定义successHandler,返回JSON返回XML,
怎么自定义呢,在WebSecurityConfig里面,例如:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(gogUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
}
@Bean
public GogUsernamePasswordAuthenticationFilter gogUsernamePasswordAuthenticationFilter() throws Exception {
GogUsernamePasswordAuthenticationFilter myFilter = new GogUsernamePasswordAuthenticationFilter();
myFilter.setAuthenticationManager(authenticationManagerBean());
myFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
myFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return myFilter;
}
到这里整改认证过程就执行完了,后面就是授权的问题。
授权的过程采用决策器投票的形式,有一票通过则通过,否则不通过。首选看决策器AccessDecisionManager的子类AffirmativeBased,整个决策过程在decide方法里,decide方法调用决策器进行表决。AccessDecisionVoter常用的如RoleVoter,WebExpressionVoter等,另外我们当然可以自定义自己的Voter。
public void decide(Authentication authentication, Object object,
Collection configAttributes) throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
例如自定义决策器:
@Bean
public AccessDecisionManager accessDecisionManager(){
RoleVoter roleVoter = new RoleVoter();//角色决策器
AuthenticatedVoter authenticatedVoter = new AuthenticatedVoter();//认证决策器
UrlMatchVoter urlMatchVoter = new UrlMatchVoter();
List extends Object>> list = new ArrayList<>();
list.add(roleVoter);
list.add(authenticatedVoter);
list.add(urlMatchVoter);
AccessDecisionManager accessDecisionManager = new AffirmativeBased(list);
return accessDecisionManager;
}
到认证授权的过程就基本结束了,重要的还是自己DEBUG跟踪代码,一步一步来实现。