springboot项目如何使用tomcat来完成Basic Auth认证

小明 2025-05-01 22:41:11 4

问题场景:

���口需要经过Basic Auth认证之后才能继续访问

假如项目需要把一些接口加上安全认证访问限制,比如这里使用比较简单的Basic Auth认证,那么应该如何使用tomcat的自带的认证机制来完成访问限制呢。


解决方案

如果不借助tomcat的认证机制来完成接口访问限制,最先想到的就是自己写一个Filter类,拦截需要认证的接口,如果用户请求头中传入的Authorization字段解析后用户名和密码不对就给用户响应一个401状态码,这种自定义Filter参考之前的文章。

针对SpringBoot Actuator未授权访问端点问题

下面演示如何用tomcat提供的类完成认证,先看下代码整体思路

package com.promote.demo.config;
import lombok.SneakyThrows;
import org.apache.catalina.Context;
import org.apache.catalina.authenticator.BasicAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.context.annotation.Configuration;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class WebSecurityConfig implements TomcatContextCustomizer {
    @Value("${basic.auth.username}")
    private String username;
    @Value("${basic.auth.password}")
    private String password;
    @SneakyThrows
    @Override
    public void customize(Context context) {
        CustomAuthenticator authenticator = new CustomAuthenticator();
        authenticator.setCharset("UTF-8");
        //定义数据源realm这里提供两种方式
        //1.方式一:使用一个内存数据源来存放用户名和密码,也有很多其他的realm可以自行研究
        //MemoryRealm realm = new MemoryRealm();
        //把用户名和密码放在类路径下
        //realm.setPathname("classpath:users.xml");
        //把用户名和密码放在某个文件路径下
        //realm.setPathname("D:\data\users.xml");
        //2.方式二:自定义一个realm,可以更灵活,比如这里的用户名和密码可以自己配置在配置文件中
        CustomRealm realm = new CustomRealm();
        realm.addUser(username, password);
        context.setRealm(realm);
        //当请求过来的时候会先进入tomcat的pipeline中,每个pipeline有多个value组件来处理请求,类似于Filter链;
        //这里我们定义的CustomAuthenticator也是属于value组件,当请求在管道中的所有组中走一遍之后才会到达Filter
        context.getPipeline().addValve(authenticator);
    }
    /**
     * BasicAuthenticator里面已经做了相关的身份验证逻辑
     */
    public static class CustomAuthenticator extends BasicAuthenticator {
        /**
         * 判断哪些路径需要权限校验,可以根据实际项目进行判定
         *
         * @param request The request currently being processed
         * @return 是否需要权限校验
         */
        @Override
        protected boolean isContinuationRequired(Request request) {
            String path = request.getRequestURI();
            //这里假设/api路径开头的url都需要进行权限校验
            return path.startsWith("/api");
        }
    }
    /**
     * 可以自定义Realm需要继承抽象类RealmBase,并实现getPassword和getPrincipal方法
     */
    public static class CustomRealm extends RealmBase {
        private final Map principals = new HashMap();
        /**
         * 添加用户
         *
         * @param username 用户名
         * @param password 密码
         */
        public void addUser(String username, String password) {
            principals.put(username, new GenericPrincipal(username, password, new ArrayList()));
        }
        @Override
        protected String getPassword(String username) {
            GenericPrincipal principal = principals.get(username);
            if (principal != null) {
                return principal.getPassword();
            } else {
                return null;
            }
        }
        @Override
        protected Principal getPrincipal(String username) {
            return principals.get(username);
        }
    }
}

步骤一:

首先我们需要写一个配置类实现TomcatContextCustomizer的接口,这个接口是springboot启动阶段提供给用户操作tomcat容器的接口,可以灵活配置一些业务。

步骤二:

我们需要借助tomcat中的BasicAuthenticator类来完成Basic Auth认证,这里面有比较成熟的Basic Auth认证逻辑,直接拿来使用。需要写一个类继承BasicAuthenticator并重写isContinuationRequired方法,在这个方法里面灵活判断哪些接口需要权限认证,这里我们定义/api开头接口都需要认证,然后把这个BasicAuthenticator类加到tomcat容器的pipeline中。tomcat还提供了其他的认证方式,如表单认证,这个是基于session的,感兴趣的可以自行测试。

步骤三:

我们需要定一个数据源来存放用户名和密码,这个数据源在tomcat中叫Realm,和Shrio中的Realm有些类似,tomcat默认提供了一些Realm,有内存Realm,数据库Realm等,看下Realm的关系图。

如果使用MemoryRealm,需要配置一个xml文件,文件内容如下,其实就是和tomcat安装包confg目录下的tomcat-users.xml文件内容是一样的,需要指定这个文件的位置,可以放在类路径下,也可以放在磁盘路径下。


  

由于是使用spring的配置文件来管理用户名和密码,不想使用MemoryRealm,这里自定义了一个CustomRealm来存放用户名和密码,比较灵活。

其他详细解释参考代码注释

运行效果图

未认证界面效果

取消认证效果

结语

由于认证机制是在tomcat的pipeline中完成的,还没有走到Filter,只有认证通过请求才会继续走到Filter然后再走到springmvc的interceptor,所以一定程度上性能会更好一点

tomcat的pipeline是什么参考这篇文章

The End
微信