SpringCloud Alibaba microservice combat 21-JWT enhancement

Today's content is mainly to solve a problem raised by a fan: how to add additional user information in jwt and obtain these data in the resource server.

There are three knowledge points involved:

  • How to add custom data in the returned jwt

  • How to add additional user data in jwt, such as user id and mobile phone number

  • How to retrieve these custom data from the resource server

Let's take a look at how to achieve it.

How to add custom data in the returned jwt

This question is relatively simple, just follow the following two steps:

  1. Write a custom token enhancer

package com.javadaily.auth.security;

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;

import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * <code>JwtTokenEnhancer</code>
 * </p>
 * Description:
 * 自定义Token增强
 * @author javadaily
 * @date 2020/7/4 15:56
 */
public class CustomJwtTokenConverter extends JwtAccessTokenConverter{
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication authentication) {
        Object principal = authentication.getUserAuthentication().getPrincipal();
        final Map<String,Object> additionalInformation = new HashMap<>(4);
        additionalInformation.put("author","java日知录");
        additionalInformation.put("weixin","javadaily");
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInformation);
        return super.enhance(oAuth2AccessToken,authentication);
    }
}

  1. AuthorizationServerConfigConfigure a custom token enhancer in the authentication server

@Bean
public JwtAccessTokenConverter jwtTokenEnhancer(){
 //自定义jwt 输出内容,若不需要就直接使用JwtAccessTokenConverter
 JwtAccessTokenConverter converter = new CustomJwtTokenConverter();
 // 设置对称签名
 converter.setSigningKey("javadaily");
 return converter;
}

By the above-described two-stage configuration, jwt token generated we can take authorand weixintwo properties, the effect is as follows:

Some students may want to ask, why does this enhancer generate extra attributes?

This is because we will use DefaultTokenServices#createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken)the following piece of code when the method:

private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
 DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
 int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
 if (validitySeconds > 0) {
  token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
 }
 token.setRefreshToken(refreshToken);
 token.setScope(authentication.getOAuth2Request().getScope());

 return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}

If the system is configured accessTokenEnhancerit will call accessTokenEnhancerthe enhance()method token enhanced. We have inherited JwtAccessTokenConverter, so we will add additional information on the basis of jwt token.

How to add user's extra data in jwt

To add additional data we want CustomJwtTokenConverterto find ways to add additional user data such as user id and phone number it must contain this information among users.

That our custom UserDetailServiceImplin return is the default UserDetails. It only contains the username attribute, which is username, and the code debugging effect is as follows:

So here we need a custom UserDetails, contain additional attributes of users, and then UserDetailServiceImplreturn our custom objects, and finally in the enhanceprocess turn into a strong custom user object and add additional properties.

The order of implementation is as follows:

  1. Custom UserDetails

import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

/**
 * <p>
 * <code>CustomUser</code>
 * </p>
 * Description:
 * 自定义用户信息
 * @author jianzh5
 * @date 2020/11/17 15:05
 */
public class SecurityUser extends User {
    @Getter
    private Integer id;

    @Getter
    private String mobile;

    public SecurityUser(Integer id, String mobile,
                        String username, String password,
                        Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
        this.id = id;
        this.mobile = mobile;
    }
}
  1. Return a custom object in UserDetailServiceImpl

private UserDetails buildUserDetails(SysUser sysUser) {
 Set<String> authSet = new HashSet<>();
 List<String> roles = sysUser.getRoles();
 if(!CollectionUtils.isEmpty(roles)){
  roles.forEach(item -> authSet.add(CloudConstant.ROLE_PREFIX + item));
  authSet.addAll(sysUser.getPermissions());
 }

 List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(authSet.toArray(new String[0]));

 return new SecurityUser(
   sysUser.getId(),
   sysUser.getMobile(),
   sysUser.getUsername(),
   sysUser.getPassword(),
   authorityList
 );
}
  1. Get the current user and set user information in the ehance method

public class CustomJwtTokenConverter extends JwtAccessTokenConverter{
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication authentication) {
        SecurityUser securityUser = (SecurityUser) authentication.getUserAuthentication().getPrincipal();
        final Map<String,Object> additionalInformation = new HashMap<>(4);
        additionalInformation.put("userId", securityUser.getId());
        additionalInformation.put("mobile", securityUser.getMobile());
  ...
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInformation);
        return super.enhance(oAuth2AccessToken,authentication);
    }
}

How to get these custom information in the resource server

Through the above configuration, we can add user data information to the jwt token, but it is still not available in the resource server, through

SecurityContextHolder.getContext().getAuthentication().getPrincipal()The obtained user information still only contains the user name.

Here still have to start from the token converter, by default, JwtAccessTokenConverterwe will call DefaultUserAuthenticationConverterthe extractAuthenticationmethod to obtain user information from the token.

Let's first look at the specific implementation logic:

public class DefaultUserAuthenticationConverter implements UserAuthenticationConverter {
 ...
 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;
 }
 ...
}

In no injection UserDetailServiceoauth2 will only get the user name of the case user_name. If injected UserDetailServicecan return all user information.

So here we also have two corresponding implementation methods:

  1. It is also injected into the resource server UserDetailService. This method is not recommended. If the resource server is separated from the authentication server, the user authentication function needs to be added.

  2. Extend DefaultUserAuthenticationConverter, rewrite the extractAuthenticationmethod, manually fetch additional data, and then inject it into the resource server configuration AccessTokenConverter.

Here we use the second method to achieve, the implementation sequence is as follows:

  1. Custom token parser to parse user information from jwt token.

package com.javadaily.common.security.component;

import com.javadaily.common.security.user.SecurityUser;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.Map;

public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {

    /**
     * 重写抽取用户数据方法
     * @author javadaily
     * @date 2020/11/18 10:56
     * @param map 用户认证信息
     * @return Authentication
     */
    @Override
    public Authentication extractAuthentication(Map<String, ?> map) {
        if (map.containsKey(USERNAME)) {
            Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
            String username = (String) map.get(USERNAME);
            Integer id = (Integer) map.get("userId");
            String mobile  = (String) map.get("mobile");
            SecurityUser user = new SecurityUser(id, mobile, username,"N/A", authorities);
            return new UsernamePasswordAuthenticationToken(user, "N/A", authorities);
        }
        return null;
    }

    private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
        Object authorities = map.get(AUTHORITIES);
        if (authorities instanceof String) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
        }
        if (authorities instanceof Collection) {
            return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
                    .collectionToCommaDelimitedString((Collection<?>) authorities));
        }
        throw new IllegalArgumentException("Authorities must be either a String or a Collection");
    }

}
  1. Write a custom token converter and inject a custom decompressor

public class CustomAccessTokenConverter extends DefaultAccessTokenConverter{

    public CustomAccessTokenConverter(){
        super.setUserTokenConverter(new CustomUserAuthenticationConverter());
    }
}
  1. Inject a custom token converter in the resource server configuration class ResourceServerConfig

@Bean
public JwtAccessTokenConverter jwtTokenConverter(){
 JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
 jwtAccessTokenConverter.setSigningKey("javadaily");
 jwtAccessTokenConverter.setAccessTokenConverter(new CustomAccessTokenConverter());

 return jwtAccessTokenConverter;
}

Through the above three steps configuration we then call SecurityContextHolder.getContext().getAuthentication().getPrincipal()when the method can be used to obtain additional information about the user.

Of course, we can come to another tool class to obtain user information directly from the context:

@UtilityClass
public class SecurityUtils {
    /**
     * 获取Authentication
     */
    public Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    public SecurityUser getUser(){
        Authentication authentication = getAuthentication();
        if (authentication == null) {
            return null;
        }
        return getUser(authentication);
    }

    /**
     * 获取当前用户
     * @param authentication 认证信息
     * @return 当前用户
     */
    private static SecurityUser getUser(Authentication authentication) {
        Object principal = authentication.getPrincipal();
        if(principal instanceof SecurityUser){
            return (SecurityUser) principal;
        }
        return null;
    }
}

If this article is helpful to you,

Don't forget to come to a three-link:

Like, repost, comment

See you next time!

Favorite  equal to the white prostitute , thumbs up  is the truth!

End

Dry goods sharing

Here is a small gift for everyone, follow the official account, enter the following code, you can get the Baidu network disk address, no routines!

001: "A must-read book for programmers"
002: "Building back-end service architecture and operation and maintenance architecture for small and medium-sized Internet companies from scratch"
003: "High Concurrency Solutions for Internet Enterprises"
004: "Internet Architecture Teaching Video"
006: " SpringBoot Realization of
Ordering System" 007: "SpringSecurity actual combat video"
008: "Hadoop actual combat teaching video"
009: "Tencent 2019 Techo Developer Conference PPT"

010: WeChat exchange group

Recent hot articles top

1. Solutions for automatic renewal of JWT Token

2. SpringBoot development cheats-asynchronous event processing

3. The road of architects-server hardware literacy

4. Architect's Road-Microservice Technology Selection

5. RocketMQ Advanced-Transaction Message

 

Guess you like

Origin blog.csdn.net/jianzhang11/article/details/109831635