问题场景:

接口需要经过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是什么参考这篇文章

文章链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: