Problem:
我们有一个基于Spring MVC的RESTful API,它包含敏感信息.API应该是安全的,但是,与每个请求一起发送用户凭据(用户/密码组合)是不可取的.根据睡觉指导方针(和内部业务要求),服务器必须保持无状态.API将由另一台服务器以混搭方式使用.

Requirements:

  • 客户端使用凭据向.../authenticate(不受保护的URL)发出请求;服务器返回一个安全令牌,该令牌包含足够的信息,以便服务器验证future 的请求并保持无状态.这可能包含与Spring Security的Remember-Me Token相同的信息.

  • 客户端对各种(受保护的)URL发出后续请求,将之前获得的令牌附加为查询参数(或者,不太理想的是,附加一个HTTP请求头).

  • 不能期望客户端存储Cookie.

  • 因为我们已经使用了Spring,所以解决方案应该使用Spring Security.

我们一直在拼命工作,希望有人已经解决了这个问题.

鉴于上述情况,您将如何解决这一特殊需求?

推荐答案

我们设法让它完全按照操作中所描述的那样工作,并且希望其他人可以使用该解决方案.以下是我们所做的工作:

按如下方式设置安全上下文:

<security:http realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="CustomAuthenticationEntryPoint">
    <security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
    <security:intercept-url pattern="/authenticate" access="permitAll"/>
    <security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>

<bean id="CustomAuthenticationEntryPoint"
    class="com.demo.api.support.spring.CustomAuthenticationEntryPoint" />

<bean id="authenticationTokenProcessingFilter"
    class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter" >
    <constructor-arg ref="authenticationManager" />
</bean>

如您所见,我们创建了一个定制的AuthenticationEntryPoint,如果请求没有通过我们的AuthenticationTokenProcessingFilter在过滤器链中的身份验证,它基本上只返回401 Unauthorized.

CustomAuthenticationEntryPoint:

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Authentication token was either missing or invalid." );
    }
}

AuthenticationTokenProcessingFilter:

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {

    @Autowired UserService userService;
    @Autowired TokenUtils tokenUtils;
    AuthenticationManager authManager;
    
    public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
        this.authManager = authManager;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        @SuppressWarnings("unchecked")
        Map<String, String[]> parms = request.getParameterMap();

        if(parms.containsKey("token")) {
            String token = parms.get("token")[0]; // grab the first "token" parameter
            
            // validate the token
            if (tokenUtils.validate(token)) {
                // determine the user based on the (already validated) token
                UserDetails userDetails = tokenUtils.getUserFromToken(token);
                // build an Authentication object with the user's info
                UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
                // set the authentication into the SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));         
            }
        }
        // continue thru the filter chain
        chain.doFilter(request, response);
    }
}

显然,100包含一些保密的(非常特定于 case 的)代码,不能很容易地共享.下面是它的界面:

public interface TokenUtils {
    String getToken(UserDetails userDetails);
    String getToken(UserDetails userDetails, Long expiration);
    boolean validate(String token);
    UserDetails getUserFromToken(String token);
}

这应该会让你有一个好的开始.

Java相关问答推荐

Java加密/解密代码转换为PHP不起作用

Java 8 RDX-如何设置单个选项卡标题文本的 colored颜色

当耗时的代码完成时,Circular ProgressIndicator显示得太晚

为什么JFrame paint()多次绘制同一点(或根本不绘制)?

Jooq外键关系

如果一个子类没有构造函数,超类也没有构造函数,那么为什么我可以构造子类的实例呢?

参数值[...]与预期类型java.util.Date不匹配

如何使用Maven和Spring Boot将构建时初始化、跟踪类初始化正确传递到本机编译

无法初始化JPA实体管理器工厂:无法确定为Java类型<;类>;推荐的JdbcType

计算两个浮点数之间的距离是否对称?

解释左移在Java中的工作原理

Jolt变换JSON数组问题

未找到适用于响应类型[类java.io.InputStream]和内容类型[Text/CSV]的HttpMessageConverter

迁移到Java 17后,日期显示不准确

使用存储在字符串变量中的路径目录打开.pdf文件

有没有可能在时间范围内得到多种解决方案?

Quarkus:运行时出现EnumConstantNotPresentException

如何处理两个几乎相同的XSD文件?

在Spring Boot中使用咖啡因进行缓存

如何用Micrometer&;斯普肯