How to use custom UserDetailService in Spring OAuth2 Resource Server?

自闭症网瘾萝莉.ら 提交于 2020-12-13 03:46:53

问题


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).

How to configure Spring Method Security properly?

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.

I get the following error on reading the entity

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

I use the following Expression:

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

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...

Security configuration

@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

@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()
        );
    }
}

dependencies

<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>

回答1:


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();
}

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());
}

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 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);
}

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



来源:https://stackoverflow.com/questions/64626686/how-to-use-custom-userdetailservice-in-spring-oauth2-resource-server

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!