Multi-Tenancy whit JWT authentication and validation of user for tenant

huangapple go评论70阅读模式
英文:

Multi-Tenancy whit JWT autentication and validation of user for tenant

问题

我正在使用JWT身份验证运行项目,它已经运行正常,但现在我需要使用以下方法实现多租户功能:

要求:

  1. 用户可以访问一个或多个租户。
  2. 访问权限由用户和租户定义。
  3. 通过请求的 @RequestAttribute 获取子域名。
  4. 生成包含租户ID(子域名)的令牌。
  5. 在所有请求上验证租户。

已实现:

  • 创建了JWT身份验证。
  • 创建了 TenantInterceptor
  • 在请求中使用 @RequestAttribute 获取子域名。
  • 创建了 existsByUsernameAndSubdomain 验证。

我在实现这个新功能时遇到了困难,您能否指向一个实现示例或教程,可以帮助我?

非常感谢您的帮助!

如果需要,以下是我的类,或者您可以克隆它们在 GitHub 上查看。

以下是一些代码片段,这些片段是项目的一部分,用于演示上述功能。

如果您需要更多的帮助,请随时告诉我。

英文:

I have a project running with JWT authentication, it works, but now I need to implement Multi-Tenancy using the following approach:

Multi-Tenancy whit JWT authentication and validation of user for tenant

Requirements:

  1. A user can have access to one or more tenants
  2. Access permissions are defined by user and tenant
  3. Getting subdomain through @RequestAttribute in requests
  4. Generate the token containing the tenant ID (subdomain).
  5. Validate the tenant on all requests

Implemented:

  • Created JWT Autentication.
  • Created TenantInterceptor.
  • Getting subdomain using @RequestAttribute on requests.
  • Created existsByUsernameAndSubdomain validation.

I'm having trouble implementing this new feature, can you point me to an implementation example or tutorial that can help me?

I thank you for your help!

Below are my classes or if you prefer clone on GitHub!

My classes

Models:

<!-- begin snippet: js hide: true console: true babel: true -->

<!-- language: lang-js -->

/** ERole **/
    public enum ERole {
    ROLE_USER,
    ROLE_MODERATOR,
    ROLE_ADMIN
}

/** Role **/
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = &quot;roles&quot;)
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Enumerated(EnumType.STRING)
    @Column(length = 20)
    private ERole name;
}

/** Tenant **/
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = &quot;tenants&quot;,
        uniqueConstraints = {
                @UniqueConstraint(columnNames = &quot;subdomain&quot;, name = &quot;un_subdomain&quot;)
        })
public class Tenant {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @Size(max = 20)
    private String subdomain;

    @NotBlank
    private String name;

}

/** User **/
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = &quot;users&quot;,
        uniqueConstraints = {
                @UniqueConstraint(columnNames = &quot;username&quot;, name = &quot;un_username&quot;)
        })
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @Size(max = 20)
    private String username;

    @NotBlank
    @Size(max = 120)
    @JsonIgnore
    private String password;

//    Remove
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = &quot;users_roles&quot;,
            joinColumns = {@JoinColumn(name = &quot;user_id&quot;,
                    foreignKey = @ForeignKey(name = &quot;fk_users_roles_users1&quot;))},
            inverseJoinColumns = {@JoinColumn(name = &quot;role_id&quot;,
                    foreignKey = @ForeignKey(name = &quot;fk_users_roles_roles1&quot;))})
    private Set&lt;Role&gt; roles = new HashSet&lt;&gt;();

//    Include
    @EqualsAndHashCode.Exclude
    @OneToMany(mappedBy = &quot;user&quot;,
            cascade = CascadeType.ALL,
            orphanRemoval = true,
            fetch = FetchType.LAZY)
    @JsonManagedReference
    private List&lt;UserTenant&gt; tenants = new ArrayList&lt;&gt;();

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

}

/** UserTenant **/
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = &quot;users_tenants&quot;,
        uniqueConstraints = {
                @UniqueConstraint(columnNames = &quot;user_id&quot;, name = &quot;un_user_id&quot;),
                @UniqueConstraint(columnNames = &quot;tenant_id&quot;, name = &quot;un_tenant_id&quot;)
        })
public class UserTenant {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;user_id&quot;,
            nullable = false,
            foreignKey = @ForeignKey(
                    name = &quot;fk_users_tenants_user1&quot;))
    @JsonBackReference
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;tenant_id&quot;,
            nullable = false,
            foreignKey = @ForeignKey(
                    name = &quot;fk_users_tenants_tenant1&quot;))
    @JsonBackReference
    private Tenant tenant;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = &quot;users_tenants_roles&quot;,
            joinColumns = {@JoinColumn(name = &quot;user_tenant_id&quot;,
                    foreignKey = @ForeignKey(name = &quot;fk_users_tenants_user_tenant1&quot;))},
            inverseJoinColumns = {@JoinColumn(name = &quot;role_id&quot;,
                    foreignKey = @ForeignKey(name = &quot;fk_users_tenants_roles1&quot;))})
    private Set&lt;Role&gt; roles = new HashSet&lt;&gt;();

}

<!-- end snippet -->

Payloads:

<!-- begin snippet: js hide: true console: true babel: true -->

<!-- language: lang-js -->

/** LoginRequest **/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginRequest {
    @NotBlank
    private String username;

    @NotBlank
    private String password;

}

/** SignupRequest **/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SignupRequest {
    @NotBlank
    @Size(max = 20)
    private String username;

    @NotBlank
    @Size(max = 40)
    private String password;
    private Set&lt;String&gt; role;

}

/** JwtResponse **/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class JwtResponse {
    private Long id;
    private String username;
    private List&lt;String&gt; roles;
    private String tokenType = &quot;Bearer&quot;;
    private String accessToken;

    public JwtResponse(String accessToken, Long id, String username,
                       List&lt;String&gt; roles) {
        this.id = id;
        this.username = username;
        this.roles = roles;
        this.accessToken = accessToken;
    }

}

/** MessageResponse **/
@Data
@Builder
@NoArgsConstructor
public class MessageResponse {
    private String message;

    public MessageResponse(String message) {
        this.message = message;
    }
}

<!-- end snippet -->

Repositories:

<!-- begin snippet: js hide: true console: true babel: true -->

<!-- language: lang-js -->

/** RoleRepository **/
@Repository
public interface RoleRepository extends JpaRepository&lt;Role, Long&gt; {
    Optional&lt;Role&gt; findByName(ERole name);
}

/** UserRepository **/
@Repository
public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
    Optional&lt;User&gt; findByUsername(String username);

    Boolean existsByUsername(String username);

}

/** UserTenantRepository **/
@Repository
public interface UserTenantRepository extends JpaRepository&lt;UserTenant, Long&gt; {

    @Query(&quot;SELECT ut FROM UserTenant ut WHERE ut.user.username = :username AND ut.tenant.subdomain = :subdomain &quot;)
    Optional&lt;UserTenant&gt; findByUserAndSubdomain(String username, String subdomain);

    @Query(&quot;SELECT &quot; +
            &quot;CASE WHEN COUNT(ut) &gt; 0 THEN true ELSE false END &quot; +
            &quot;FROM UserTenant ut &quot; +
            &quot;WHERE ut.user.username = :username &quot; +
            &quot;AND ut.tenant.subdomain = :subdomain &quot;)
    Boolean existsByUsernameAndSubdomain(String subdomain, String username);

}

<!-- end snippet -->

Services:

<!-- begin snippet: js hide: true console: true babel: true -->

<!-- language: lang-js -->

/** AuthService **/
@Service
@RequiredArgsConstructor
public class AuthService {

    private final UserRepository userRepository;
    private final AuthenticationManager authenticationManager;
    private final JwtUtils jwtUtils;
    private final PasswordEncoder encoder;
    private final RoleRepository roleRepository;

    public JwtResponse authenticateUser(String subdomain, LoginRequest loginRequest) {

        System.out.println(subdomain);

        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
        System.out.println(authentication);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtUtils.generateJwtToken(authentication);

        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
        List&lt;String&gt; roles = userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());

        return new JwtResponse(jwt,
                userDetails.getId(),
                userDetails.getUsername(),
                roles);
    }

    @Transactional
    public MessageResponse registerUser(SignupRequest signUpRequest) {

        // Create new user&#39;s account
        User user = new User(
                signUpRequest.getUsername(),
                encoder.encode(signUpRequest.getPassword()));

        Set&lt;String&gt; strRoles = signUpRequest.getRole();
        Set&lt;Role&gt; roles = new HashSet&lt;&gt;();

        if (strRoles == null) {
            Role userRole = roleRepository.findByName(ERole.ROLE_USER)
                    .orElseThrow(() -&gt; new RuntimeException(&quot;Error: Role is not found.&quot;));
            roles.add(userRole);
        } else {
            strRoles.forEach(role -&gt; {
                switch (role) {
                    case &quot;admin&quot;:
                        Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN)
                                .orElseThrow(() -&gt; new RuntimeException(&quot;Error: Role is not found.&quot;));
                        roles.add(adminRole);
                        break;
                    case &quot;mod&quot;:
                        Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR)
                                .orElseThrow(() -&gt; new RuntimeException(&quot;Error: Role is not found.&quot;));
                        roles.add(modRole);
                        break;
                    default:
                        Role userRole = roleRepository.findByName(ERole.ROLE_USER)
                                .orElseThrow(() -&gt; new RuntimeException(&quot;Error: Role is not found.&quot;));
                        roles.add(userRole);
                }
            });
        }
        user.setRoles(roles);
        userRepository.save(user);
        return new MessageResponse(&quot;User registered successfully!&quot;);
    }

}

/** UserDetailsImpl **/
public class UserDetailsImpl implements UserDetails {
    private static final long serialVersionUID = 1L;

    private final Long id;

    private final String username;

    @JsonIgnore
    private final String password;

    private final Collection&lt;? extends GrantedAuthority&gt; authorities;

    public UserDetailsImpl(Long id, String username, String password,
                           Collection&lt;? extends GrantedAuthority&gt; authorities) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }

    public static UserDetailsImpl build(User user) {
        List&lt;GrantedAuthority&gt; authorities = user.getRoles().stream()
                .map(role -&gt; new SimpleGrantedAuthority(role.getName().name()))
                .collect(Collectors.toList());

        return new UserDetailsImpl(
                user.getId(),
                user.getUsername(),
                user.getPassword(),
                authorities);
    }

    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        return authorities;
    }

    public Long getId() {
        return id;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        UserDetailsImpl user = (UserDetailsImpl) o;
        return Objects.equals(id, user.id);
    }
}

/** UserDetailsServiceImpl **/
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;
    private final UserTenantRepository userTenantRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -&gt; new UsernameNotFoundException(&quot;User Not Found with username: &quot; + username));


        return UserDetailsImpl.build(user);
    }

}

<!-- end snippet -->

Controller
<!-- begin snippet: js hide: true console: true babel: true -->

<!-- language: lang-js -->

/** AuthController **/
@RestController
@RequestMapping(&quot;/auth&quot;)
@RequiredArgsConstructor
public class AuthController {

    private final AuthService authService;
    private final UserRepository userRepository;
    private final UserTenantRepository userTenantRepository;

    @PostMapping(&quot;/signin&quot;)
    public ResponseEntity&lt;?&gt; authenticateUser(
            @RequestAttribute String subdomain,
            @Valid @RequestBody LoginRequest loginRequest
    ) {
        if (!userTenantRepository.existsByUsernameAndSubdomain(subdomain, loginRequest.getUsername())) {
            return ResponseEntity
                    .badRequest()
                    .body(new MessageResponse(&quot;Unauthorized: This username and tenant is not authorized!&quot;));
        }
        return ResponseEntity.ok(authService.authenticateUser(subdomain, loginRequest));
    }


    @PostMapping(&quot;/signup&quot;)
    public ResponseEntity&lt;?&gt; registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
        if (userRepository.existsByUsername(signUpRequest.getUsername())) {
            return ResponseEntity
                    .badRequest()
                    .body(new MessageResponse(&quot;Error: Username is already taken!&quot;));
        }
        return ResponseEntity.ok(authService.registerUser(signUpRequest));
    }
}

<!-- end snippet -->

JWT:
<!-- begin snippet: js hide: true console: true babel: true -->

<!-- language: lang-js -->

/** AuthEntryPointJwt **/
@Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        logger.error(&quot;Unauthorized error: {}&quot;, authException.getMessage());
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, &quot;Unauthorized: incorrect username or password&quot;);
    }

}

/** AuthTokenFilter **/
public class AuthTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response
            , FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String jwt = parseJwt(request);
            if (jwt != null &amp;&amp; jwtUtils.validateJwtToken(jwt)) {
                String username = jwtUtils.getUserNameFromJwtToken(jwt);

                String serverName = request.getServerName();
                String subdomain = serverName.substring(0, serverName.indexOf(&quot;.&quot;));

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                System.out.println(userDetails);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        filterChain.doFilter(request, response);
    }

    private String parseJwt(HttpServletRequest request) {
        String headerAuth = request.getHeader(&quot;Authorization&quot;);

        if (StringUtils.hasText(headerAuth) &amp;&amp; headerAuth.startsWith(&quot;Bearer &quot;)) {
            return headerAuth.substring(7);
        }
        return null;
    }
}

/** JwtUtils **/
@Component
public class JwtUtils {
    private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

    @Value(&quot;${example.app.jwtSecret}&quot;)
    private String jwtSecret;

    @Value(&quot;${example.app.jwtExpirationMs}&quot;)
    private int jwtExpirationMs;

    public String generateJwtToken(Authentication authentication) {

        UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();

        return Jwts.builder()
                .setSubject((userPrincipal.getUsername()))
                .setIssuedAt(new Date())
                .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String getUserNameFromJwtToken(String token) {
        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
    }

    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            logger.error(&quot;Invalid JWT signature: {}&quot;, e.getMessage());
        } catch (MalformedJwtException e) {
            logger.error(&quot;Invalid JWT token: {}&quot;, e.getMessage());
        } catch (ExpiredJwtException e) {
            logger.error(&quot;JWT token is expired: {}&quot;, e.getMessage());
        } catch (UnsupportedJwtException e) {
            logger.error(&quot;JWT token is unsupported: {}&quot;, e.getMessage());
        } catch (IllegalArgumentException e) {
            logger.error(&quot;JWT claims string is empty: {}&quot;, e.getMessage());
        }

        return false;
    }
}

<!-- end snippet -->

Utils:
<!-- begin snippet: js hide: true console: true babel: true -->

<!-- language: lang-js -->

/** TenantInterceptor **/
public class TenantInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String serverName = request.getServerName();
        String tenantId = serverName.substring(0, serverName.indexOf(&quot;.&quot;));

        request.setAttribute(&quot;subdomain&quot;, tenantId);

        return true;
    }
}

/** WebSecurityConfig **/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig implements WebMvcConfigurer {

    final
    UserDetailsServiceImpl userDetailsService;

    private final AuthEntryPointJwt unauthorizedHandler;

    public WebSecurityConfig(UserDetailsServiceImpl userDetailsService, AuthEntryPointJwt unauthorizedHandler) {
        this.userDetailsService = userDetailsService;
        this.unauthorizedHandler = unauthorizedHandler;
    }

    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());

        return authProvider;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers(
                        &quot;/auth/**&quot;,
                        &quot;/v3/api-docs/**&quot;,
                        &quot;/swagger-ui/**&quot;,
                        &quot;/swagger-ui.html&quot;,
                        &quot;/configuration/**&quot;,
                        &quot;/swagger-resources/**&quot;,
                        &quot;/webjars/**&quot;,
                        &quot;/api-docs/**&quot;).permitAll()
                .antMatchers(&quot;/api/**&quot;).authenticated()
                .anyRequest().authenticated();

        http.authenticationProvider(authenticationProvider());

        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TenantInterceptor());
    }

}

<!-- end snippet -->

答案1

得分: 0

我通过修改UserDetailsServiceImpl中的loadUserByUsername方法来解决了这个问题。

在**GitHub**上查看实现细节!

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserTenantRepository userTenantRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 从请求属性中获取子域名
        HttpServletRequest request =
                ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))
                        .getRequest();

        String serverName = request.getServerName();
        String subdomain = serverName.substring(0, serverName.indexOf("."));
        UserTenant userTenant = userTenantRepository.findByUserAndSubdomain(username, subdomain)
                .orElseThrow(() -> new UsernameNotFoundException(
                        "UserTenant Not Found with username: " + username + " and " + subdomain));

        // 从UserTenant获取权限
        List<GrantedAuthority> authorities = userTenant.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority(role.getName().name()))
                .collect(Collectors.toList());

        return new UserDetailsImpl(
                userTenant.getUser().getId(),
                userTenant.getUser().getUsername(),
                userTenant.getUser().getPassword(),
                authorities
        );
    }
}

数据库插入

INSERT INTO roles(id, name)
VALUES (1, 'ROLE_USER'),
       (2, 'ROLE_MODERATOR'),
       (3, 'ROLE_ADMIN');

INSERT INTO tenants (id, name, subdomain)
VALUES (1, 'Tenant 1', 'tenant1'),
       (2, 'Tenant 2', 'tenant2');

# 用户,密码
# user1, user1
# user2, user2
INSERT INTO users (id, username, password)
VALUES (1, 'user1', '$2a$10$wFMJLxdXKGRa8lJO6k2DAOnW9HstAPoHecXUNkDyYNeaNnZJAz.hy'),
       (2, 'user2', '$2a$10$Z9/wLkmf5IwfjJqIQU6X.OBFg3TCBUyk3bdfgkGjU0.HI5kVibZxG');

INSERT INTO users_tenants (id,  tenant_id, user_id)
VALUES (1, 1, 1),
       (2, 2, 2);

INSERT INTO users_tenants_roles (user_tenant_id,  role_id)
VALUES (1, 2),
       (1, 3),
       (2, 1);

INSERT INTO items (id,  tenant_id, name)
VALUES (1, 1, 'Tenant 1中的产品1'),
       (2, 1, 'Tenant 1中的产品2'),
       (3, 2, 'Tenant 2中的产品1'),
       (4, 2, 'Tenant 2中的产品2');

Postman中的验证

在Postman中创建token变量:
Multi-Tenancy whit JWT authentication and validation of user for tenant

在Postman变量中设置token值:
Multi-Tenancy whit JWT authentication and validation of user for tenant

在请求头中添加Authorization变量:
Multi-Tenancy whit JWT authentication and validation of user for tenant

在登录时验证域是否存在:
Multi-Tenancy whit JWT authentication and validation of user for tenant

验证租户上的用户访问权限:
Multi-Tenancy whit JWT authentication and validation of user for tenant

授权登录:
Multi-Tenancy whit JWT authentication and validation of user for tenant

获取tenant1的项目:
Multi-Tenancy whit JWT authentication and validation of user for tenant

尝试在未登录tenant2并没有访问权限的情况下获取tenant2的项目列表:
Multi-Tenancy whit JWT authentication and validation of user for tenant

使用user2在tenant2中登录:
Multi-Tenancy whit JWT authentication and validation of user for tenant

获取tenant2的项目列表:
Multi-Tenancy whit JWT authentication and validation of user for tenant

尝试在未登录tenant1且没有访问权限的情况下获取tenant1的项目列表:
Multi-Tenancy whit JWT authentication and validation of user for tenant

英文:

I was able to solve the problem by modifying the loadUserByUsername method in the UserDetailsServiceImpl.

See the implementation details on GitHub!

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserTenantRepository userTenantRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // Getting subdomain from request attributes
        HttpServletRequest request =
                ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))
                        .getRequest();

        String serverName = request.getServerName();
        String subdomain = serverName.substring(0, serverName.indexOf(&quot;.&quot;));
        UserTenant userTenant = userTenantRepository.findByUserAndSubdomain(username, subdomain)
                .orElseThrow(() -&gt; new UsernameNotFoundException(
                        &quot;UserTenant Not Found with username: &quot; + username + &quot; and &quot; + subdomain));

        // Getting Rules from the UserTenant
        List&lt;GrantedAuthority&gt; authorities = userTenant.getRoles().stream()
                .map(role -&gt; new SimpleGrantedAuthority(role.getName().name()))
                .collect(Collectors.toList());

        return new UserDetailsImpl(
                userTenant.getUser().getId(),
                userTenant.getUser().getUsername(),
                userTenant.getUser().getPassword(),
                authorities
        );
    }
}

Inserts in Database

INSERT INTO roles(id, name)
VALUES (1 ,&#39;ROLE_USER&#39;),
       (2, &#39;ROLE_MODERATOR&#39;),
       (3, &#39;ROLE_ADMIN&#39;);

INSERT INTO tenants (id, name, subdomain)
VALUES (1, &#39;Tenant 1&#39;, &#39;tenant1&#39;),
       (2, &#39;Tenant 2&#39;, &#39;tenant2&#39;);

# user, password
# user1, user1
# user2, user2
INSERT INTO users (id, username, password)
VALUES (1, &#39;user1&#39;, &#39;$2a$10$wFMJLxdXKGRa8lJO6k2DAOnW9HstAPoHecXUNkDyYNeaNnZJAz.hy&#39;),
       (2, &#39;user2&#39;, &#39;$2a$10$Z9/wLkmf5IwfjJqIQU6X.OBFg3TCBUyk3bdfgkGjU0.HI5kVibZxG&#39;);

INSERT INTO users_tenants (id,  tenant_id, user_id)
VALUES (1, 1, 1),
       (2, 2, 2);

INSERT INTO users_tenants_roles (user_tenant_id,  role_id)
VALUES (1, 2),
       (1, 3),
       (2, 1);


INSERT INTO items (id,  tenant_id, name)
VALUES (1, 1, &#39;Product 1 in Tenant 1&#39;),
       (2, 1, &#39;Product 2 in Tenant 1&#39;),
       (3, 2, &#39;Product 1 in Tenant 2&#39;),
       (4, 2, &#39;Product 2 in Tenant 2&#39;);

Validations in Postman

Created token variable in Postman:
Multi-Tenancy whit JWT authentication and validation of user for tenant

Set token value in Postman variable:
Multi-Tenancy whit JWT authentication and validation of user for tenant

Added Authorization variable in requests headers:
Multi-Tenancy whit JWT authentication and validation of user for tenant

Validating if the domain exists in sign in:
Multi-Tenancy whit JWT authentication and validation of user for tenant

Validating user access permission on tenant:
Multi-Tenancy whit JWT authentication and validation of user for tenant

Authorized login:
Multi-Tenancy whit JWT authentication and validation of user for tenant

Getting items of tenant1:
Multi-Tenancy whit JWT authentication and validation of user for tenant

Trying to get tenant2 list of items without being logged in tenant2 and having access permissions:
Multi-Tenancy whit JWT authentication and validation of user for tenant

Logging in with user2 in tenant2:
Multi-Tenancy whit JWT authentication and validation of user for tenant

Getting items list tenant2
Multi-Tenancy whit JWT authentication and validation of user for tenant

Trying to get tenant1 list of items without being logged in tenant1 and having access permissions:
Multi-Tenancy whit JWT authentication and validation of user for tenant

huangapple
  • 本文由 发表于 2023年2月7日 04:17:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/75366149.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定