首先提供一个总体的配置文件,再分析:css
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:s="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <!-- 不要过滤图片等静态资源,其中**表明能够跨越目录,*不能够跨越目录。 --> <s:http pattern="/**/*.jpg" security="none"/> <s:http pattern="/**/*.png" security="none"/> <s:http pattern="/**/*.gif" security="none"/> <s:http pattern="/**/*.css" security="none"/> <s:http pattern="/**/*.js" security="none"/> <s:http pattern="/login.jsp" security="none"/> <s:http pattern="/user/user!login.action" security="none"/> <s:http auto-config="false" use-expressions="true" entry-point-ref="loginUrlEntryPoint" access-denied-page="/error.jsp"> <!-- 处理用户登录时,用户名和密码判断的filter(重要) --> <s:custom-filter position="FORM_LOGIN_FILTER" ref="loginFilter"/> <!-- 权限判断、处理的filter链 --> <s:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myFilter"/> <!-- 检测失效的sessionId,超时时定位到另一个URL --> <s:session-management invalid-session-url="/sessionTimeout.jsp" /> <!-- 登出 --> <s:logout logout-url="/j_spring_security_logout" invalidate-session="true" logout-success-url="/index.jsp" /> </s:http> <!-- 一个自定义的filter,必须包含authenticationManager, accessDecisionManager,securityMetadataSource三个属性。 --> <bean id="myFilter" class="com.dtds.security.MyFilterSecurityInterceptor"> <!-- 资源数据源 --> <property name="securityMetadataSource" ref="mySecurityMetadataSource" /> <!-- 认证管理器 --> <property name="authenticationManager" ref="authenticationManager" /> <!-- 访问决策器 --> <property name="accessDecisionManager" ref="accessDecisionManager" /> </bean> <!-- 资源源数据定义,将全部的资源和权限对应关系创建起来,即定义某一资源能够被哪些角色去访问。 --> <bean id="mySecurityMetadataSource" class="com.dtds.security.InvocationSecurityMetadataSourceServiceImpl"> <property name="userService" ref="userService"/> </bean> <!-- 认证配置, 使用userDetailsService提供的用户信息 --> <s:authentication-manager alias="authenticationManager"> <s:authentication-provider ref="authenticationProvider"/> </s:authentication-manager> <!-- 能够重写 --> <!-- <bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> --> <bean id="authenticationProvider" class="com.dtds.security.SecurityAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService" /> <property name="hideUserNotFoundExceptions" value="false" /> <property name="passwordEncoder" ref="passwordEncoder"/> </bean> <!-- 登录时查询用户、并加载用户所拥有的权限等 --> <bean id="userDetailsService" class="com.dtds.security.UserDetailServiceImpl"> <property name="userService" ref="userService"/> </bean> <!-- 用户的密码加密或解密 --> <bean id="passwordEncoder" class="com.dtds.security.MyPasswordEncoder" /> <!-- 访问决策器,决定某个用户具备的角色,是否有足够的权限去访问某个资源。 --> <bean id="accessDecisionManager" class="com.dtds.security.MyAccessDecisionManager"/> <!-- 重写登录验证 --> <bean id="loginFilter" class="com.dtds.security.MyUsernamePasswordAuthenticationFilter"> <!-- 认证管理 --> <property name="authenticationManager" ref="authenticationManager"></property> <!-- 验证成功后的跳转 --> <property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></property> <!-- 处理登录的Action --> <property name="filterProcessesUrl" value="/j_spring_security_check"></property> <!-- 验证失败后的处理 --> <property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></property> </bean> <!-- 登录成功 --> <!-- 这里要实现自定义 --> <!-- <bean id="loginLogAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"> --> <bean id="loginLogAuthenticationSuccessHandler" class="com.dtds.security.LoginAuthenticationSuccessHandler"> <property name="alwaysUseDefaultTargetUrl" value="true"/> <!-- 登录成功时的页面,这里设定的页面也会被spring security拦截 --> <!-- <property name="defaultTargetUrl" value="/content/select.jsp" /> <property name="targetUrlParameter" value="redirectTo" /> --> <property name="defaultTargetUrl" value="/user/user!login.action" /> </bean> <!-- 登录失败 --> <bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <!-- 能够配置相应的跳转方式。属性forwardToDestination为true采用forward false为sendRedirect --> <property name="defaultFailureUrl" value="/login.jsp"></property> </bean> <!-- 未登陆的切入点 --> <bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <property name="loginFormUrl" value="/login.jsp"></property> </bean> <!-- Spring Security 认证切入点 --> <bean id="loginUrlEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <property name="loginFormUrl" value="/login.jsp"></property> </bean> </beans>
首先看到,用户登录的时候吗请求会被这个配置拦截:java
<!-- 处理用户登录时,用户名和密码判断的filter(重要) --> <s:custom-filter position="FORM_LOGIN_FILTER" ref="loginFilter"/>
能够看到这个loginFilter是web
<!-- 重写登录验证 --> <bean id="loginFilter" class="com.dtds.security.MyUsernamePasswordAuthenticationFilter">
它包含了四个属性,首先看第一个spring
authenticationManager数据库
它最终指向的是authenticationProvider,即一个权限判断的提供者,它又包含了三个属性,主要关注的是userDetailsService和passwordEncoderexpress
即用户名和密码的校验,后面跟进代码会再次回到这两个属性上~!(其实用户名和密码都正确的话,理论上就是经过了登录权限的校验)缓存
看看这个过滤器,它主要的功能是由方法attemptAuthentication提供,最终的目的返回一个权限对象Authentication,在该方法中:session
主要是这句:app
Authentication authentication = super.attemptAuthentication(request, response);jsp
因为
MyUsernamePasswordAuthenticationFilter
是继承自UsernamePasswordAuthenticationFilter过滤器,那么这里使用了super,即实际执行的是父类的方法,源码以下:
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); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
该方法的入参就是request和response,接着执行,就获取到页面输入的用户名和密码,因为这里返回的是一个权限对象,能够看到最终返回的便是这句:
this.getAuthenticationManager().authenticate(authRequest);
在返回前,并未看到SS对用户名和密码作任何验证,那么验证确定在上述这句代码中,跟进去,进入到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; }
执行到这句:
result = provider.authenticate(authentication);
继续跟进,进入:AbstractUserDetailsAuthenticationProvider的authenticate方法,源码:
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 { 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); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (forcePrincipalAsString) { principalToReturn = user.getUsername(); } return createSuccessAuthentication(principalToReturn, authentication, user); }
这里看出ss首先去缓存中获取user对象,若是没有获取到,则继续执行,到这句
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
跟进,则进入:
DaoAuthenticationProvider的retrieveUser方法,源码:
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser; try { loadedUser = this.getUserDetailsService().loadUserByUsername(username); } catch (UsernameNotFoundException notFound) { if(authentication.getCredentials() != null) { String presentedPassword = authentication.getCredentials().toString(); passwordEncoder.isPasswordValid(userNotFoundEncodedPassword, presentedPassword, null); } 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; }
这里关键代码:
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
即便用UserDetailService的loadUserByUsername方法,而UserDetailService是一个接口,该接口正是须要咱们本身实现的关键点,它只有惟一的一个方法loadUserByUsername,该方法须要咱们本身实现,主要就是经过用户名从数据库中获取用户并判断
跟进这么多,最终获得如同上面说的结果:回到userDetailsService和passwordEncoder
首先是userDetailsService
它主要是获取用户及权限,该方法返回的是一个Spring定义的User,按要求封装便可,能够本身建立一个MyUser类,继承该User或者直接返回便可
返回以后,继续在DaoAuthenticationProvider中执行,执行完毕以后继续在AbstractUserDetailsAuthenticationProvider中执行
执行到这句:
preAuthenticationChecks.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); } } }
这是一个内部类,能够看到是判断user的一些其余属性
接着执行下面一句:
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
能够看到这个方法最终会执行咱们本身继承的类
SecurityAuthenticationProvider
方法:
@Override protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { logger.info("【3】:SecurityAuthenticationProvider"); //执行到这句话,下面就会执行MyPasswordEncoder super.additionalAuthenticationChecks(userDetails, authentication); WebAuthenticationDetails webDetail = (WebAuthenticationDetails) authentication.getDetails(); try { this.logger.warn("开始写currentLogin表"); long start = System.currentTimeMillis(); //this.POService.additionalLoginCheck(peopleInfo); long end = System.currentTimeMillis(); this.logger.warn("写currentLogin表完毕,用时:" + (end - start) + "ms"); } catch (Exception e) { this.logger.error("写currentLogin表异常", e); throw new AuthenticationServiceException(e.getMessage()); } }
这里能够添加一些其余的应用
该方法使用super又调用了父类的方法
DaoAuthenticationProvider
方法
@SuppressWarnings("deprecation") 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); } }
能够看到继续是一些验证代码
其中这句:
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); }
这里能够看到倒数第二个MyPassWordEncoder,便是咱们本身实现的密码验证类,以下:
@Override public boolean isPasswordValid(String npwd, String opwd, Object arg2) { System.out.println("3:MyPasswordEncoder.isPasswordValid().......validating password.........."); System.out.println("输入的密码:" + opwd + ",存储的密码" + npwd); if(npwd.equals(opwd)){ System.out.println("密码正确"); } else { System.out.println("密码错误"); } return npwd.equals(opwd); }
这里只是简单的验证而已,能够添加其余的
自此,DaoAuthenticationProvider已经执行完毕,代码继续回到:SecurityAuthenticationProvider中,执行完毕,继续执行AbstractUserDetailsAuthenticationProvider,知道执行最后一句返回代码:
return createSuccessAuthentication(principalToReturn, authentication, user);
它是本类里面的方法,跟进:
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { // Ensure we return the original credentials the user supplied, // so subsequent attempts are successful even with encoded passwords. // Also ensure we return the original getDetails(), so that future // authentication events after cache expiry contain the details UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result; }
从名字能够看出,如今已是符合权限的要求了create-success-Authentication
看这句
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities()));
这里的user就是咱们自定义的继承自spring User的MyUser,因为重写了
public Collection<GrantedAuthority> getAuthorities() { List list = new ArrayList(); for (String sid : this.roleInfos) { list.add(new GrantedAuthorityImpl(sid)); } return list; }
因此此时执行的是咱们本身的getAuthrities(),注意这里返回的result
其中的principal存储的是User,因此其余方法调用getPrincipal()获得的就是User
自此AbstractUserDetailsAuthenticationProvider执行完毕
继续执行ProviderManager#authenticate()
因为result不为null,则执行:
if (result != null) {
copyDetails(authentication, result);
break;
}
继续执行,则ProviderManager执行完毕,接着执行UsernamePasswordAuthenticationFilter,返回authentication,执行完毕。
再执行MyUsernamePasswordAuthenticationFilter
获得了authentication对象并返回,MyUsernamePasswordAuthenticationFilter执行完毕。
自此这个过滤器AbstractAuthenticationProcessingFilter执行到doFilter,获得正确的权限对象,最后执行:successfulAuthentication(request, response, chain, authResult);
即
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException{ successfulAuthentication(request, response, authResult); }
继续执行
@Deprecated protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException { if (logger.isDebugEnabled()) { logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult); } 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); }
这里能够看出,将权限信息set到了SecurityContextHolder中,接着执行rememberMeService。。
最后一句:
successHandler.onAuthenticationSuccess(request, response, authResult);
因为上述的权限认证已经经过,因此这里就调用successHandler,即登陆成功后的处理类,这个类已经被咱们实现了,因此就执行LoginAuthenticationSuccessHandler的onAuthenticationSuccess方法,在这里面就是执行咱们本身的流程,其中最重要的就是将用户对象set到session中,供整个web使用
后面就返回了,继续执行过滤器链