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

时间: 2024-10-25 07:12:46

Spring Security框架下Restful Token的验证方案的相关文章

在Spring Boot框架下使用WebSocket实现消息推送

Spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的支持(使用Spring Boot开发Web项目(二)之添加HTTPS支持),在这两篇文章的基础上,我们今天来看看如何在Spring Boot中使用WebSocket. 什么是WebSocket WebSocket为浏览器和服务器之间提供了双工异步通信功能,也就是说我们可以利用浏览器给服务器发送消息,

手把手写一个基于Spring Boot框架下的参数校验组件

手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303) 前言 之前参与的新开放平台研发的过程中,由于不同的接口需要对不同的入参进行校验,这就涉及到通用参数的校验封装,如果不进行封装,那么写出来的校验代码将会风格不统一.校验工具类不一致.维护风险高等其它因素,于是我对其公共的校验做了一个封装,达到了通过注解的方式即可实现参数统一校验. 遇到的问题                    在封装的时候就发现了一个问题,我们是开放平台,返回的报文都必须是统一风格,也就是类似于{co

spring security使用hibernate进行查询数据库验证

前面查询数据库采用的都是jdbc方式,如果系统使用的是hibernate,该如何进行呢,下面就是实现步骤,关键还是实现自定义的UserDetailsService 项目结构如下: 使用hibernate,pom.xml文件如下: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLo

spring mvc框架下使用kaptcha生成验证码

1.下载jar包并导入. kaptcha-2.3.2.jar 2.spring 配置文件 applicationContext.xml. <bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha"> <property name="config"> <bean class="com.google.code.ka

Spring+Hibernate框架下Mysql读写分离、主从数据库配置

1. 配置AOP切面类 DataSourceAdvice.java package until; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.ThrowsAdvice; public class DataSo

Spring MVC框架下在java代码中访问applicationContext.xml文件中配置的文件(可以用于读取配置文件内容)

<bean id="propertyConfigurer" class="com.****.framework.core.SpringPropertiesUtil" lazy-init="false"> <property name="locations"> <list> <value>classpath:config/sys.properties</value> &

手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303)

前言 之前参与的新开放平台研发的过程中,由于不同的接口需要对不同的入参进行校验,这就涉及到通用参数的校验封装,如果不进行封装,那么写出来的校验代码将会风格不统一.校验工具类不一致.维护风险高等其它因素,于是我对其公共的校验做了一个封装,达到了通过注解的方式即可实现参数统一校验. 遇到的问题                     在封装的时候就发现了一个问题,我们是开放平台,返回的报文都必须是统一风格,也就是类似于{code:999,msg:"参数校验失败",data:null} 这种

redis jwt spring boot spring security 实现api token 验证

文章地址:http://www.haha174.top/article/details/258083 项目源码:https://github.com/haha174/jwt-token.git 具体的实际效果可以看考这里 目前已经部署一个 个人测试机器上面: http://cloud.codeguoj.cn/api-cloud-server/swagger-ui.html#!/token45controller/loginUsingPOST 相信很多人都调用过api, 一般的大致基本步骤都是先用

springboot集成spring security实现restful风格的登录认证 附代码

一.文章简介 本文简要介绍了spring security的基本原理和实现,并基于springboot整合了spring security实现了基于数据库管理的用户的登录和登出,登录过程实现了验证码的校验功能. 完整代码地址:https://github.com/Dreamshf/spring-security.git 二.spring security框架简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.主要包括:用户认证