1. 开发环境
spring 3.1.2(spring mvc ,spring core ,spring security) ,hibernate3.1.6 final
2. 需要的jar包
由于本项目采用maven进行jar包管理,pom.xml的配置如下:
3. 配置文件分析
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans:beans xmlns="http://www.springframework.org/schema/security" 3 xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 6 http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> 7 8 <!-- 身份验证配置 --> 9 <authentication-manager alias="authenticationManagerBean"> 10 <authentication-provider ref="authenticationProvider" /> 11 </authentication-manager> 12 13 <beans:bean id="authenticationProvider" 14 class="com.edu.security.service.AuthenticationProvider"> 15 <!-- 用户身份鉴权 --> 16 <beans:property name="userDetailsService" ref="userDetailsService" /> 17 <!-- 密码加密方式 --> 18 <beans:property name="passwordEncoder" ref="passwordEncoder" /> 19 <!-- 用户密码加盐处理 --> 20 <beans:property name="saltSource"> 21 <beans:bean class="org.springframework.security.authentication.dao.ReflectionSaltSource"> 22 <beans:property name="userPropertyToUse" value="username" /> 23 </beans:bean> 24 </beans:property> 25 </beans:bean> 26 27 <!-- 用户的密码加密方式 --> 28 <beans:bean id="passwordEncoder" 29 class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"> 30 <beans:constructor-arg index="0" value="256" /> 31 <beans:property name="encodeHashAsBase64" value="true" /> 32 </beans:bean> 33 34 <!-- userDetailsService的配置 --> 35 <beans:bean id="userDetailsService" class="com.edu.security.service.UserDetailsServiceImpl" /> 36 37 <!-- 访问决策器,决定某个用户(具有的角色)是否有足够的权限去访问某个资源 --> 38 <beans:bean id="accessDecisionManagerBean" class="com.edu.security.service.CustomizedAccessDecisionManager"> 39 <!-- 没有显式定义的资源都保护起来。该属性默认值为false --> 40 <beans:property name="allowIfAllAbstainDecisions" 41 value="false" /> 42 </beans:bean> 43 44 <!-- 安全资源定义,即定义某一安全资源可以被哪些角色访问 --> 45 <beans:bean id="securityMetadataSourceBean" class="com.edu.security.service.CustomizedInvocationSecurityMetadataSource"> 46 <beans:constructor-arg index="0" ref="querier" /> 47 <beans:property name="rejectPublicInvocations" value="true" /> 48 </beans:bean> 49 50 <!-- 一个自定义的filter,必须包含authenticationManager,accessDecisionManager,securityMetadataSource三个属性 --> 51 <beans:bean id="customizedFilter" 52 class="com.edu.security.service.CustomizedFilterSecurityInterceptor"> 53 <beans:property name="authenticationManager" ref="authenticationManagerBean" /> 54 <beans:property name="accessDecisionManager" ref="accessDecisionManagerBean" /> 55 <beans:property name="securityMetadataSource" ref="securityMetadataSourceBean" /> 56 </beans:bean> 57 58 <!-- 认证异常处理 --> 59 <beans:bean id="exceptionMappingAuthenticationFailureHandler" 60 class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler"> 61 <beans:property name="exceptionMappings"> 62 <beans:map> 63 <!-- 用户不存在 --> 64 <beans:entry 65 key="org.springframework.security.core.userdetails.UsernameNotFoundException" 66 value="/login.jsp?sign=NoUser" /> 67 <!-- 凭证错误(密码不正确) --> 68 <beans:entry 69 key="org.springframework.security.authentication.BadCredentialsException" 70 value="/login.jsp?sign=BadCredentials" /> 71 <!-- 用户不可用 --> 72 <beans:entry 73 key="org.springframework.security.authentication.DisabledException" 74 value="/login.jsp?sign=UserIsDisabled" /> 75 <!-- 登陆凭证错误 --> 76 <beans:entry 77 key="org.springframework.security.core.AuthenticationException" 78 value="/login.jsp?sign=AuthenticationFailure" /> 79 </beans:map> 80 </beans:property> 81 </beans:bean> 82 83 <!-- 当访问被拒绝时,会转到403.jsp --> 84 <http access-denied-page="/WEB-INF/error/403.jsp" pattern="/*.htm*"> 85 <!-- 登陆设置 --> 86 <form-login login-page="/login.jsp" username-parameter="loginName" 87 password-parameter="password" login-processing-url="/login.htm" 88 authentication-failure-url="/login.jsp?sign=BadCredentials" 89 default-target-url="/index.htm" always-use-default-target="true" 90 authentication-failure-handler-ref="exceptionMappingAuthenticationFailureHandler" /> 91 <!-- 匿名用户访问控制,这里设置不允许匿名用户登陆 --> 92 <anonymous enabled="false" /> 93 <!-- 登出设置 --> 94 <logout logout-success-url="/login.jsp" logout-url="/logout.htm" /> 95 <http-basic /> 96 <!-- 增加一个filter,位于FILTER_SECURITY_INTERCEPTOR之前 --> 97 <custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="customizedFilter" /> 98 </http> 99 100 <!-- 安全资源白名单(URL) --> 101 <beans:bean id="securityMetadataSourceTrustListHolder" 102 class="com.edu.security.service.util.SecurityMetadataSourceTrustListHolder"> 103 <beans:property name="trustList"> 104 <beans:list> 105 <beans:value>/index.htm</beans:value> 106 <beans:value>/hello.htm</beans:value> 107 </beans:list> 108 </beans:property> 109 </beans:bean> 110 111 <!-- 安全用户白名单 --> 112 <beans:bean id="securityUserTrustListHolder" 113 class="com.edu.security.service.util.SecurityUserTrustListHolder"> 114 <beans:property name="trustList"> 115 <beans:list> 116 <beans:value>administrator</beans:value> 117 </beans:list> 118 </beans:property> 119 </beans:bean> 120 121 <!-- 开启Spring Security3认证和授权日志 --> 122 <beans:bean 123 class="org.springframework.security.authentication.event.LoggerListener" /> 124 <beans:bean class="org.springframework.security.access.event.LoggerListener" /> 125 126 </beans:beans>
4. 流程分析
1) 在web.xml 中配置了org.springframework.web.filter.DelegatingFilterProxy
1 <filter> 2 <filter-name>springSecurityFilterChain</filter-name> 3 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>springSecurityFilterChain</filter-name> 7 <url-pattern>/*</url-pattern> 8 </filter-mapping>
2)请求会被AbstractAuthenticationProcessingFilter拦截:
1 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 2 throws IOException, ServletException { 3 4 HttpServletRequest request = (HttpServletRequest) req; 5 HttpServletResponse response = (HttpServletResponse) res; 6 7 if (!requiresAuthentication(request, response)) { 8 chain.doFilter(request, response); 9 10 return; 11 } 12 13 if (logger.isDebugEnabled()) { 14 logger.debug("Request is to process authentication"); 15 } 16 17 Authentication authResult; 18 19 try { 20 //用户验证 21 authResult = attemptAuthentication(request, response); 22 if (authResult == null) { 23 // return immediately as subclass has indicated that it hasn't completed authentication 24 return; 25 } 26 sessionStrategy.onAuthentication(authResult, request, response); 27 } catch(InternalAuthenticationServiceException failed) { 28 logger.error("An internal error occurred while trying to authenticate the user.", failed); 29 unsuccessfulAuthentication(request, response, failed); 30 31 return; 32 } 33 catch (AuthenticationException failed) { 34 // Authentication failed 35 unsuccessfulAuthentication(request, response, failed); 36 37 return; 38 } 39 40 // Authentication success 41 if (continueChainBeforeSuccessfulAuthentication) { 42 chain.doFilter(request, response); 43 } 44 //认证成功之后执行的方法 45 successfulAuthentication(request, response, chain, authResult); 46 }
3) 跳转到 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 { 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 (AuthenticationException e) { lastException = e; } } if (result == null && parent != null) { // Allow the parent to try. try { //用户名、密码、权限验证 result = parent.authenticate(authentication); } catch (ProviderNotFoundException e) { // ignore as we will throw below if no other exception occurred prior to calling parent and the parent // may throw ProviderNotFound even though a provider in the child already handled the request } catch (AuthenticationException e) { lastException = e; } } if (result != null) { if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) { // Authentication is complete. Remove credentials and other secret data from authentication ((CredentialsContainer)result).eraseCredentials(); } //抛出登录成功事件 eventPublisher.publishAuthenticationSuccess(result); return result; } // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null) { lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound", new Object[] {toTest.getName()}, "No AuthenticationProvider found for {0}")); } prepareException(lastException, authentication); throw lastException; }
4) DaoAuthenticationProvider 执行retrieveUser()方法
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; try { loadedUser = this.getUserDetailsService().loadUserByUsername(username); } catch (UsernameNotFoundException notFound) { throw notFound; } catch (Exception repositoryProblem) { throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); } if (loadedUser == null) { throw new AuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; }
5) UserDetailsService 加载UserDetails
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // Determine username String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { //加载UserDetails 对象,可以实现UserDetailsService和UserDetails接口自定义自己 //UserDetails加载机制 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { logger.debug("User '" + username + "' not found"); if (hideUserNotFoundExceptions) { throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else { throw notFound; } } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); } try { preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } catch (AuthenticationException exception) { if (cacheWasUsed) { // There was a problem, so try again after checking // we're using latest data (i.e. not from the cache) cacheWasUsed = false; user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); } else { throw exception; } } postAuthenticationChecks.check(user); //缓存到UserCache中 if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (forcePrincipalAsString) { principalToReturn = user.getUsername(); } return createSuccessAuthentication(principalToReturn, authentication, user); }
6) check()检查user 是否可用
private class DefaultPreAuthenticationChecks implements UserDetailsChecker { public void check(UserDetails user) { if (!user.isAccountNonLocked()) { logger.debug("User account is locked"); throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"), user); } if (!user.isEnabled()) { logger.debug("User account is disabled"); throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"), user); } if (!user.isAccountNonExpired()) { logger.debug("User account is expired"); throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"), user); } } }
7) 验证用户名和密码(根据配置文件中配置的加密方式和是否加盐)additionalAuthenticationChecks()
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { Object salt = null; if (this.saltSource != null) { salt = this.saltSource.getSalt(userDetails); } if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails); } String presentedPassword = authentication.getCredentials().toString(); if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails); } }
8) 放入到userCash中方便下次过来
9)执行自定义的Controller(中间省略其他的filter了)