jmicro

An open-source framework based on Spring Boot, helps you create applications more easily

View project on GitHub

Micro Auth

当你需要对你的 Spring Boot 应用进行权限认证和鉴权时,你可能会选择一个开源框架,比如 Spring 家族的 Spring Security 或者 Apache Shiro 又或是 SaToken 等,你需要根据他们的文档一步一步的搭建,然而假如你未选择框架,从零搭建鉴权模块,你会发现大部分情况下一个 filter 足够了。为什么?那些框架真的有必要使用么?或许我们需要回过头来重新考虑,这便是我设计这个授权模块的原因,做更少的事,仅仅是对 filter 的扩展,然后提供我们熟知的注解,这便是这个模块做的所有的事

如何使用

第一步,添加 maven 依赖到你的项目中

<dependency>
    <groupId>com.jiangtj.micro</groupId>
    <artifactId>micro-auth</artifactId>
    <version>${last-version}</version>
</dependency>

第二步,配置转换器,转换你的请求到 AuthContext,下面是一个例子,使用 jjwt 解析 bearer token

@Component
public class JsonAuthContextConverter implements AuthContextConverter {
    @Nullable
    @Override
    public AuthContext convert(AuthRequest request) {
        List<String> headers = request.getHeaders(AuthRequestAttributes.TOKEN_HEADER_NAME);
        if (headers.size() != 1) {
            return null;
        }
        String token = headers.get(0);
        JwtParser parser = Jwts.parser()
            .verifyWith(key)
            .build();
        Claims body = parser.parseSignedClaims(token).getPayload();
        Subject subject = new Subject();
        subject.setId(body.sub);
        // 如果你在 session 中存储登录信息,那么你只需从 session 中取出并转换为 AuthContext 即可
        // 获取角色和权限从你的服务中(tip: 缓存可以带来更好的性能)
        return AuthContext.create(subject, Authorization.create(roles, permissions));
    }
}

第三步,现在你可以在任意地方,使用我们的框架来进行权限控制了,我们提供了授权类(AuthService/AuthReactiveService),允许你在过滤器或者业务代码中使用,除此外还有注解(@HasLogin @HasRole @HasPermission),允许你控制方法以及类级别的权限

@Service
class ExampleService {
    @HasPermission("permission key")
    public void hasAnyPermission(){
        // authService.hasPermission("permission key") 如果使用授权类的写法,是不是和注解很相似
        // do something
    }
}

处理授权上下文

一个很常见的情况,我们的认证与授权过程可能是分开的,比如 casdoor 模块中那样,我们需要在认证之后,完善授权信息,所以 AuthContextHandler 就来了

@Order(Number)
@Component
public class ProvideRoleHandler implements AuthContextHandler{
    @Override
    public void handle(AuthContext ctx) {
        // 获取你的角色
        ctx.setAuthorization(Authorization.create(roles));
    }
}

匹配方式

默认授权的匹配规则是判断提供的 key 是否在授权上下文中存在,当然针对 permission 也提供了一个 ant-style 权限匹配规则,添加 jmicro.auth.permission-match=ant 配置选项即可,除此之外你可以覆盖默认的授权类,实现任意的鉴权方式(AuthUtils 可以帮助你简化判断权限)

import com.jiangtj.micro.auth.servlet.DefaultAuthService;

@Service
public class AuthService extends DefaultAuthService {
    @Override
    public void hasPermission(@NonNull Logic logic, @NonNull String... permissions) {
        /**
         * a:* -> a:a
         * b:** -> b:b:b
         * c:*:c -> c:c:c
         */
        AuthUtils.hasAntPermission(getContext(), logic, permissions);
    }
}

多账号体系

我们的 AuthContextConverter 返回值是可以为 Null,如果返回 Null,那么会继续使用下一个转换器转换授权上下文,所以很容易实现多体系,你只需多个转换器,处理各自的授权上下文即可,如果不支持的那么返回 Null 让其他的处理。我们建议通过 Subjectissuertype 来区分,比如 casdoor 模块中,issuer 是授权服务器地址,type 是固定的 casdoor

自定义注解

我们的注解也有很高的扩展性,你可以自定义自己的注解,通过组合注解的方式

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@HasRole(value = {"admin", "user"}, logic = Logic.OR)
public @interface HasAdminOrUser {
}
// 那么 @HasAdminOrUser 等价于 @HasRole(value = {"admin", "user"}, logic = Logic.OR)

也可以通过我们提供的注解切面类AnnotationMethodBeforeAdvice ReactiveAnnotationMethodBeforeAdviceAnnotationPointcut(不仅仅可以用于鉴权哦),下面是我们的 @HasRole 的实现过程

public class HasRoleAdvice extends AnnotationMethodBeforeAdvice<HasRole> {
    @Resource
    private AuthService authService;
    
    @Override
    public Class<HasRole> getAnnotationType() {
        return HasRole.class;
    }
    
    @Override
    public void before(List<HasRole> annotations, Method method, Object[] args, @Nullable Object target) {
        for (HasRole annotation : annotations) {
            authService.hasRole(annotation.logic(), annotation.value());
        }
    }
}

@Configuration
public class MyConfiguration {
    @Bean
    public HasRoleAdvice hasRoleAdvice() {
        return new HasRoleAdvice();
    }
    @Bean
    public Advisor hasRoleAdvisor(HasRoleAdvice advice) {
        return new DefaultPointcutAdvisor(new AnnotationPointcut<>(HasRole.class), advice);
    }
}

测试

我们同样提供了测试模块micro-test方便用户模拟用户进行集成测试,很简单使用@WithMockUser注解即可(或者@WithMockSubject @WithMockRole @WithMockPermission

@JMicroTest
@AutoConfigureWebTestClient
class Test {

    @Resource
    WebTestClient client;

    @Test
    @WithMockUser(subject = "user", roles = {"roleA", "roleB"})
    void getRole() {
        client.build().get().uri("/")
            .exchange()
            .expectStatus().isOk();
    }
}

在响应式中,service层需要权限时,可以使用AuthStepVerifier替换掉StepVerifierAuthStepVerifier会提供授权的上下文,当然你也可以通过TestAuthContextHolder获取上下文自己添加

@JMicroTest
class Test {
    @Test
    @WithMockUser(subject = "user", roles = {"roleA", "roleB"})
    void getRole() {
        AuthStepVerifier.create(someService.call())
            .expectComplete()
            .verify();
    }
}