当前位置: 首页 > 知识库问答 >
问题:

如何在Spring Boot REST服务中的@PreAuthorize注释中验证OAuth2.0令牌用户详细信息

郑俊材
2023-03-14

我需要签入@preauthorize注释。类似于:

@PreAuthorize("hasRole('ROLE_VIEWER') or hasRole('ROLE_EDITOR')")

这是可以的,但是我还需要验证存储在OAuth2.0令牌中的一些用户详细信息和请求路径中的用户详细信息,所以我需要做一些类似的事情(oauthToken.userDetails只是一个例子:

@PreAuthorize("#pathProfileId.equals(oauthToken.userDetails.profileId)")

(profileId不是userId或userName,它是我们创建OAuth令牌时添加到OAuth令牌中的用户详细信息)

使OAuth令牌属性在预先授权的注释安全表达式语言中可见的最简单方法是什么?

共有1个答案

穆鸿飞
2023-03-14

您有两种选择:

将UserDetailsService实例设置为DefaultUserAuthenticationConverter并将converter设置为JWTAccessStokenConverter,因此当spring从DefaultUserAuthenticationConverter调用extractAuthentication方法时,它会找到(UserDetailsService!=null),因此在调用以下行时,它通过调用loadUserByUsername的实现来获取整个UserDetails对象:

UserDetailsService.LoadUserByUserName((String)Map.get(USERNAME))

在spring类org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter.java中的下一个方法中实现,但添加它只是为了澄清spring如何从map中获取主体对象(首先按用户名获取,如果userDetailsService不为null,则获取整个对象):

//Note: This method implemented by spring but just putting it to show where spring exctract principal object and how extracting it
public Authentication extractAuthentication(Map<String, ?> map) {
        if (map.containsKey(USERNAME)) {
            Object principal = map.get(USERNAME);
            Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
            if (userDetailsService != null) {
                UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
                authorities = user.getAuthorities();
                principal = user;
            }
            return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
        }
        return null;
    }

因此,您需要在微服务中实现的是:

@Bean//this method just used with token store bean example: new JwtTokenStore(tokenEnhancer());
public JwtAccessTokenConverter tokenEnhancer() {
    /**
    * CustomTokenConverter is a class extends JwtAccessTokenConverter 
    * which override "enhance" to add extra information to OAuth2AccessToken after
    * authenticate the user and get it by loadUserByUsername implementation 
    * like profileId in your case
    **/  
    JwtAccessTokenConverter converter = new CustomTokenConverter();

    DefaultAccessTokenConverter datc = new DefaultAccessTokenConverter();
    datc.setUserTokenConverter(userAuthenticationConverter());
    converter.setAccessTokenConverter(datc);

    //Other method code implementation....
}

@Autowired
private UserDetailsService userDetailsService;

@Bean
public UserAuthenticationConverter userAuthenticationConverter() {
    DefaultUserAuthenticationConverter duac = new DefaultUserAuthenticationConverter();
    duac.setUserDetailsService(userDetailsService);
    return duac;
 }

注意:第一种方式将在每个请求中命中数据库,因此它按用户名加载用户,并获得UserDetails对象,因此它将其分配给身份验证中的主体对象。

如果出于任何原因,您可以看到,最好不要在每个请求中命中数据库,并且在执行所需数据时不会出现问题,比如来自传入请求中的令牌的profileId。

假设您知道生成oauth2令牌时分配给用户的旧权限将一直在令牌中,直到它无效,即使您在数据库中更改了它,对于在请求中传递令牌的用户来说,这样用户可以在提取令牌后调用一个不允许他/她使用的方法,而在提取令牌之前,该方法是允许的。

因此,这意味着如果用户权限在生成令牌后发生更改,那么@preauthorize将不会检查新的权限,因为它没有被删除或添加到令牌中,您必须等待旧令牌无效或过期,因此用户被迫再次执行服务以获得新的oauth令牌。

无论如何,在第二个选项中,您只需在CustomTokenConverter类extends JWTAccessStokenConverter中重写extractAuthentication方法,而不必在第一个选项中设置访问令牌转换器converter.setAccessStokenConverter from tokenEnhancer()方法,下面是整个CustomTokenConverter您可以使用它从令牌读取数据并返回主体对象,而不仅仅是字符串username:

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

public class CustomTokenConverter extends JwtAccessTokenConverter {

    // This is the method you need to override to read data direct from token passed in request
    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
        OAuth2Authentication authentication = super.extractAuthentication(map);

        Object userIdObj = map.get(AuthenticationUtils.USER_ID);
        UUID userId = userIdObj != null ? UUID.fromString(userIdObj.toString()) : null;
        Object profileIdObj = map.get(AuthenticationUtils.PROFILE_ID);
        UUID profileId = profileIdObj != null ? UUID.fromString(profileIdObj.toString()) : null;
        Object firstNameObj = map.get(AuthenticationUtils.FIRST_NAME);
        String firstName = firstNameObj != null ? String.valueOf(firstNameObj) : null;
        Object lastNameObj = map.get(AuthenticationUtils.LAST_NAME);
        String lastName = lastNameObj != null ? String.valueOf(lastNameObj) : null;

        JwtUser principal = new JwtUser(userId, profileId, authentication.getUserAuthentication().getName(), "N/A", authentication.getUserAuthentication().getAuthorities(), firstName, lastName);

        authentication = new OAuth2Authentication(authentication.getOAuth2Request(),
                new UsernamePasswordAuthenticationToken(principal, "N/A", authentication.getUserAuthentication().getAuthorities()));
        return authentication;
    }

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        JwtUser user = (JwtUser) authentication.getPrincipal();
        Map<String, Object> info = new LinkedHashMap<>(accessToken.getAdditionalInformation());
        if (user.getId() != null)
            info.put(AuthenticationUtils.USER_ID, user.getId());
        if (user.getProfileId() != null)
            info.put(AuthenticationUtils.PROFILE_ID, user.getProfileId());
        if (isNotNullNotEmpty(user.getFirstName()))
            info.put(AuthenticationUtils.FIRST_NAME, user.getFirstName());
        if (isNotNullNotEmpty(user.getLastName()))
            info.put(AuthenticationUtils.LAST_NAME, user.getLastName());

        DefaultOAuth2AccessToken customAccessToken = new DefaultOAuth2AccessToken(accessToken);
        customAccessToken.setAdditionalInformation(info);
        return super.enhance(customAccessToken, authentication);
    }

    private boolean isNotNullNotEmpty(String str) {
        return Optional.ofNullable(str).map(String::trim).map(string -> !str.isEmpty()).orElse(false);
    }

}

最后:猜猜我是怎么知道您在问与oauth2一起使用的JWT的?

因为我是你公司的一员,你知道的

 类似资料:
  • 我在我的nodejs应用程序中为注册用户在keybeat上实现了keybeat,我使用的登录API是: 如果用户名和密码正确,则返回登录用户的令牌, 现在我需要传递这个令牌(通过上面的API返回),并检查这个令牌是否正确,如果令牌正确,我需要用户详细信息,是否有用于此的API。 提前谢谢

  • 当客户端请求资源服务器使用OAuth2.0访问令牌获取受保护的资源时,该服务器如何验证该令牌?OAuth 2.0刷新令牌协议?

  • 嗨,我尝试用InMemoryDao获取userDetailsService。但是我得不到,我试过@Autowired,@ Inject(userdailsservice,InMemoryDaoImpl,InMemoryManager...)但是我不能让它工作。 有公共类安全扩展WebSecurityC 只尝试了WebSecurityCon

  • 问题内容: 我获得了用于Web推送的Firebase Cloud Messaging注册令牌。然后,我将此邮件发送到服务器以保存在数据库中,以供以后推送。但是,如何验证此令牌有效或伪造的? 我已经尝试过了,但是我认为这是针对Auth令牌而不是针对Web推送的。 其他人可以将随机伪造令牌的请求发送到我的服务器。我想防止这种情况,然后再保存到数据库中。 编辑:已解决,我编写了一个简单的类来使用FCM快

  • 我现在正在尝试使用这里描述的客户端REST集成来加快PayPal Express结账(即Checkout.js)的速度。我看到,当付款完成时,我的onAuthorize函数将用“payment”对象调用。 我找不到任何关于这个对象的文档,但一些戳它揭示了以下属性(至少在今天): PaymentToken 付款ID 付款 意图 返回 现在我需要将用户重定向到我网站上的下一个步骤,在那里我展示了一张确

  • 问题内容: 我如何验证信用卡。我需要做检查。黑莓中有API可以做到吗? 问题答案: 您可以使用以下方法来验证信用卡号