springboot项目如何使用tomcat来完成Basic Auth认证
问题场景:
���口需要经过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是什么参考这篇文章