zoukankan      html  css  js  c++  java
  • Spring Security框架下Restful Token的验证方案

    项目使用Restful的规范,权限内容的访问,考虑使用Token验证的权限解决方案。

    验证方案(简要概括):

    首先,用户需要登陆,成功登陆后返回一个Token串;

    然后用户访问有权限的内容时需要上传Token串进行权限验证

    代码方案:

    Spring MVC + Spring Security + Redis的框架下实现权限验证,此文重点谈谈Spring Security下的Token验证实现。

    首先,看看spring security的配置:

    <http pattern="/service/secure/**"
              entry-point-ref="serviceUnauthorizedEntryPoint"
              create-session="stateless">
              <!-- Added after moving to Spring Boot 1.3 + Spring Security 4.x, otherwise we could not login with basic auth because of: Expected CSRF token not found TODO: Please, mind, that I did not migrate this XML to Spring Security 4.x except for this element -->
              <csrf disabled="true"/>
              
              <intercept-url pattern="/service/secure/admin/login*" access="permitAll"/>
              
              <custom-filter ref="preTokenAuthenticationFilter" before="PRE_AUTH_FILTER" />
        </http>

    接下来详细说明配置以及访问流程:

    1. 考虑到支持Restful规范,所以spring security需要设置create-session为stateless状态

    2. 当访问权限验证失败是,根据Restful规范返回401 Unauthorized,因此需要设定entry-point-ref,重新指向一个自定义的entrypoint如下:

    public class ServiceUnauthorizedEntryPoint implements AuthenticationEntryPoint {
    
        private static final Logger logger = LoggerFactory.getLogger(ServiceTokenAuthenticationFilter.class);
        
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                AuthenticationException arg2) throws IOException, ServletException {
            // return 401 UNAUTHORIZED status code if the user is not authenticated
            logger.debug(" *** UnauthorizedEntryPoint.commence: " + request.getRequestURI());
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        }
    
    }

    3. Token的验证重点在于PRE_AUTH_FILTER的拦截器

    关于FllterChain请看官方解释security-filter-chain

    另外关于PRE_AUTH_FILTER拦截器的官方解释preauth,我们在此就采取已经被可靠的验证系统验证过的流程即验证Token的合法性,我们看一下这个拦截器的bean设置

    <b:bean id="preTokenAuthenticationFilter"
                class="com.will.security.token.PreRequestHeaderAuthenticationFilter">
                <b:property name="authenticationManager" ref="preAuthenticationManager" />
                <b:property name="authenticationFailureHandler" ref="authFailureHandler"/>
                <b:property name="principalRequestHeader" value="X-Auth-Token"/>
                <b:property name="continueFilterChainOnUnsuccessfulAuthentication" value="false" />
        </b:bean>
        
         <!-- PreAuthentication manager. -->
        <b:bean id="authFailureHandler" class="org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler" >
             <b:constructor-arg value="/service/secure/admin/login/failed" />
        </b:bean>
        
        <b:bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
            <b:property name="preAuthenticatedUserDetailsService">
                <b:bean id="userDetailsServiceWrapper"  class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
                    <b:property name="userDetailsService" ref="tokenUserDetailsService"/>
                </b:bean>
            </b:property>
            <b:property name="userDetailsChecker">
                <b:bean id="tokenUserDetailsChecker" class="com.will.security.token.TokenUserDetailsChecker" />
            </b:property>
        </b:bean>
        <authentication-manager id="preAuthenticationManager">
             <authentication-provider ref="preauthAuthProvider" />
        </authentication-manager>
    PreRequestHeaderAuthenticationFilter里,截取访问的request,然后获取上传的Token串,这里的Token串储存在“SM_UER”的header里,
    代码如下:
    public class PreRequestHeaderAuthenticationFilter extends
            AbstractCustomPreAuthenticatedProcessingFilter {
    
        private String principalRequestHeader = "SM_USER";
        private String credentialsRequestHeader;
        private boolean exceptionIfHeaderMissing = true;
        
        @Override
        protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
         /*获取principal信息*/ String principal
    = request.getHeader(principalRequestHeader); if (principal == null && exceptionIfHeaderMissing) { // 对于request进行BadException处理 request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, new BadCredentialsException("No pre-authenticated credentials found in request.")); return "N/A"; } return principal; } @Override protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { if (credentialsRequestHeader != null) { return request.getHeader(credentialsRequestHeader); } return "N/A"; } public void setPrincipalRequestHeader(String principalRequestHeader) { Assert.hasText(principalRequestHeader, "principalRequestHeader must not be empty or null"); this.principalRequestHeader = principalRequestHeader; } public void setCredentialsRequestHeader(String credentialsRequestHeader) { Assert.hasText(credentialsRequestHeader, "credentialsRequestHeader must not be empty or null"); this.credentialsRequestHeader = credentialsRequestHeader; } /** * Defines whether an exception should be raised if the principal header is missing. * Defaults to {@code true}. * * @param exceptionIfHeaderMissing set to {@code false} to override the default * behaviour and allow the request to proceed if no header is found. */ public void setExceptionIfHeaderMissing(boolean exceptionIfHeaderMissing) { this.exceptionIfHeaderMissing = exceptionIfHeaderMissing; } }

    如果需要详细了解认证流程建议查看PreAuthenticatedAuthenticationProvider的源码,对于provider的配置也就一目了然了

    TokenUserDetailsService的代码如下

    public class TokenUserDetailsService implements UserDetailsService {
    
        private TokenManager tokenManager;
        
        @Override
        public UserDetails loadUserByUsername(String token)
                throws UsernameNotFoundException {
            if (token.equalsIgnoreCase("N/A")) {
                return null;
            }
            
            return tokenManager != null ? tokenManager.getUserDetails(token) : null;
        }
        
        public void setTokenManager(TokenManager tm) {
            this.tokenManager = tm;
        }
    
    }

    TokenManager负责Token的生成,验证以及删除等等操作

    public interface TokenManager {
    
        /**
         * Creates a new token for the user and returns its {@link TokenInfo}.
         * It may add it to the token list or replace the previous one for the user. Never returns {@code null}.
         */
        TokenInfo createNewToken(UserDetails userDetails);
    
        /** Removes all tokens for user. */
        //void removeUserDetails(UserDetails userDetails);
    
        /** Removes a single token. */
        UserDetails removeToken(String token);
    
        /** Returns user details for a token. */
        UserDetails getUserDetails(String token);
        
        /** Returns user details for a username. */
        UserDetails getUserDetailsByUsername(String username);
    
        /** Returns a collection with token information for a particular user. */
        Collection<TokenInfo> getUserTokens(UserDetails userDetails);
        
        
        Boolean validateToken(String token);
        
    }

    我们可以根据自己的需要比如借助Redis做缓存,或者使用JWT等等,具体可实现自己的TokenManager

  • 相关阅读:
    css
    AcWing 145 超市 (贪心)
    AcWing 144 最长异或值路径 (Trie)
    AcWing 143 最大异或对 (Trie)
    AcWing 142 前缀统计 (Trie)
    AcWing 141 周期 (KMP)
    AcWing 139 回文子串的最大长度 (哈希+二分 / Manacher)
    AcWing 136 邻值查找 (set)
    AcWing 133 蚯蚓 (队列)
    AcWing 131 直方图中最大的矩形 (单调栈)
  • 原文地址:https://www.cnblogs.com/tyoyi/p/6879532.html
Copyright © 2011-2022 走看看