如何在 Spring OAuth2 资源服务器中使用自定义 UserDetailService? [英] How to use custom UserDetailService in Spring OAuth2 Resource Server?

查看:58
本文介绍了如何在 Spring OAuth2 资源服务器中使用自定义 UserDetailService?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Spring Boot (2.3.4.RELEASE) 来实现充当 OAuth2 资源服务器的 Web 服务.到目前为止,我能够保护所有端点并确保存在有效的令牌.在下一步中,我想使用 Spring Method Security.第三步是填充自定义用户详细信息(通过 UserDetailsS​​ervice).

I'm using Spring Boot (2.3.4.RELEASE) to implement a webservice acting as a OAuth2 resource server. So far I'm able to secure all endpoints and ensure that a valid token is present. In the next step I want to use Spring Method Security. The third step would be to populate custom user details (via UserDetailsService).

我无法(正确)启用 Spring Method Security.我将实体保存在数据库中,并通过 MutableAclService 设置了权限.创建新资源没问题.

I'm not able to enable Spring Method Security (correctly). I have entities saved in database and also set the permissions via MutableAclService. Creating new resource is no problem.

我在读取实体

o.s.s.acls.AclPermissionEvaluator        : Checking permission 'OWNER' for object 'org.springframework.security.acls.domain.ObjectIdentityImpl[Type: io.mvc.webserver.repository.entity.ProjectEntity; Identifier: my-second-project]'
o.s.s.acls.AclPermissionEvaluator        : Returning false - no ACLs apply for this principal
o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter@120d62d, returned: -1
o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.access.vote.RoleVoter@429b9eb9, returned: 0
o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.access.vote.AuthenticatedVoter@65342bae, returned: 0
o.s.web.servlet.DispatcherServlet        : Failed to complete request: org.springframework.security.access.AccessDeniedException: Zugriff verweigert
o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is not anonymous); delegating to AccessDeniedHandler

我使用以下表达式:

@PreAuthorize("hasPermission(#projectKey, 'io.mvc.webserver.repository.entity.ProjectEntity', 'OWNER')")
ProjectEntity findByKey(String projectKey);

如何提供自定义用户详细信息服务?

据我所知,Spring Security 将 SecurityContext 相应地设置为经过身份验证的用户(通过 OAuth2 JWT).我想根据令牌中识别的用户设置自定义用户对象(主体).但是仅仅提供 UserDetailsS​​ervice 类型的 Bean 似乎不起作用.我的 UserDetailsS​​ervice 永远不会被调用...

How to provide Custom User Details Service?

As far as I understand Spring Security sets the SecurityContext accordingly to the authenticated user (by OAuth2 JWT). I want to set a custom user object (principal) based on the identified user from the token. But just providing a Bean of type UserDetailsService does not seem to work. My UserDetailsService is never invoked...

安全配置

@Configuration
@EnableWebSecurity
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .cors().and()
                .httpBasic().disable()
                .formLogin().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests(authorize -> authorize
                    .antMatchers("/actuator/**").permitAll() // TODO: Enable basic auth for actuator
                    .anyRequest().authenticated()
                )
                .oauth2ResourceServer().jwt();
    }
}

ACL 配置

@Configuration
public class AclConfiguration {
    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
        DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(permissionEvaluator);

        return expressionHandler;
    }

    @Bean
    public PermissionEvaluator permissionEvaluator(PermissionFactory permissionFactory, AclService aclService) {
        AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService);
        permissionEvaluator.setPermissionFactory(permissionFactory);

        return permissionEvaluator;
    }

    @Bean
    public PermissionFactory permissionFactory() {
        return new DefaultPermissionFactory(MvcPermission.class);
    }

    @Bean
    public MutableAclService aclService(LookupStrategy lookupStrategy, AclCache aclCache, AclRepository aclRepository) {
        return new MongoDBMutableAclService(aclRepository, lookupStrategy, aclCache);
    }

    @Bean
    public AclAuthorizationStrategy aclAuthorizationStrategy() {
        return new AclAuthorizationStrategyImpl(
                new SimpleGrantedAuthority("ROLE_ADMIN"));
    }

    @Bean
    public PermissionGrantingStrategy permissionGrantingStrategy() {
        return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
    }

    @Bean
    public AclCache aclCache(PermissionGrantingStrategy permissionGrantingStrategy,
                             AclAuthorizationStrategy aclAuthorizationStrategy,
                             EhCacheFactoryBean ehCacheFactoryBean) {
        return new EhCacheBasedAclCache(
                ehCacheFactoryBean.getObject(),
                permissionGrantingStrategy,
                aclAuthorizationStrategy
        );
    }

    @Bean
    public EhCacheFactoryBean aclEhCacheFactoryBean(EhCacheManagerFactoryBean ehCacheManagerFactoryBean) {
        EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
        ehCacheFactoryBean.setCacheManager(ehCacheManagerFactoryBean.getObject());
        ehCacheFactoryBean.setCacheName("aclCache");
        return ehCacheFactoryBean;
    }

    @Bean
    public EhCacheManagerFactoryBean aclCacheManager() {
        EhCacheManagerFactoryBean cacheManagerFactory = new EhCacheManagerFactoryBean();
        cacheManagerFactory.setShared(true);
        return cacheManagerFactory;
    }

    @Bean
    public LookupStrategy lookupStrategy(MongoTemplate mongoTemplate,
                                         AclCache aclCache,
                                         AclAuthorizationStrategy aclAuthorizationStrategy) {
        return new BasicMongoLookupStrategy(
                mongoTemplate,
                aclCache,
                aclAuthorizationStrategy,
                new ConsoleAuditLogger()
        );
    }
}

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-acl</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.6.11</version>
</dependency>

推荐答案

在您的 ResourceServerConfig 类中,您应该覆盖 configureGlobal 和 authenticationManagerBean 方法,并提供 passwordEncoderBean 以调用您的 userDeatailsS​​ervice:

In your ResourceServerConfig class you should override configureGlobal and authenticationManagerBean methods, as well as providing passwordEncoderBean in order to invoke your userDeatailsService:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoderBean());
}

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

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

configureGlobal 中的变量 userDetailsS​​ervice 应该持有对 org.springframework.security.core.userdetails.UserDetailsS​​ervice 实现的引用(通过类中的依赖注入 @Autowird),在实现中,您应该覆盖方法 loasUserByUsername 以获取数据库中的实际用户并将所需的值传递给 UserDetails 用户,此用户或主体将在身份验证管理器中使用:

The variable userDetailsService in configureGlobal should hold a reference (through dependency injection @Autowird in your class) to your implementation of org.springframework.security.core.userdetails.UserDetailsService, in the implementation you should override the method loasUserByUsername to get the actual user in your database and pass the required values to UserDetails user, this user or Principal is what will be used in the authentication manager:

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    Optional<UserFromDb> user = userRepository.findByUsername(username);
    if (!user.isPresent()) {
        throw new UsernameNotFoundException("User not found!");
    }
    return new MyUser(user.get());
}

类 MyUser 应该实现 org.springframework.security.core.userdetails.UserDetails 并将所需的值传递给 MyUser,如示例所示.如何传递所需的值取决于您,这里我从数据库中传递了用户,并在实现内部提取了所需的任何值.

The class MyUser should implement org.springframework.security.core.userdetails.UserDetails and pass the required values to MyUser as the example shows. How to pass the required values is up to you, here I passed the user from database and internally in the implementation I extracted whatever values are needed.

您应该在配置方法的末尾添加以下行

You should add the following line to the end of configure method

http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

authenticationTokenFilter 是实现OncePerRequestFilter 的类型,您应该重写doFilterInternal 方法:

authenticationTokenFilter is of a type that implements OncePerRequestFilter, you should override the method doFilterInternal:

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse, FilterChain filterChain)
        throws ServletException, IOException {
    
    
    final String requestTokenHeader = httpServletRequest.getHeader("Authorization");//sometime it's lowercase: authorization

    String username = getUserName(requestTokenHeader);
    String jwtToken = getJwtToken(requestTokenHeader);
    
    if (username != null) {
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
        if (isValidToken(jwtToken, userDetails)) {
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                    new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
            usernamePasswordAuthenticationToken
                    .setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        }
    }

    filterChain.doFilter(httpServletRequest, httpServletResponse);
}

当然要写getUserName、getJwtToken和isValidToken方法的逻辑,这需要了解JWT token和http headers...

Of course you should write the logic of getUserName, getJwtToken and isValidToken methods, which require understanding of JWT token and http headers...

这篇关于如何在 Spring OAuth2 资源服务器中使用自定义 UserDetailService?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆