spring security实践分析

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了)

转载于:https://www.cnblogs.com/HisonShare/p/3472553.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值