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:
-
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);
}
}
-
AuthorizationServerConfig
Configure 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 author
and weixin
two 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 accessTokenEnhancer
it will call accessTokenEnhancer
the 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 CustomJwtTokenConverter
to find ways to add additional user data such as user id and phone number it must contain this information among users.
That our custom UserDetailServiceImpl
in 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 UserDetailServiceImpl
return our custom objects, and finally in the enhance
process turn into a strong custom user object and add additional properties.
The order of implementation is as follows:
-
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;
}
}
-
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
);
}
-
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, JwtAccessTokenConverter
we will call DefaultUserAuthenticationConverter
the extractAuthentication
method 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 UserDetailService
oauth2 will only get the user name of the case user_name
. If injected UserDetailService
can return all user information.
So here we also have two corresponding implementation methods:
-
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. -
Extend
DefaultUserAuthenticationConverter
, rewrite theextractAuthentication
method, manually fetch additional data, and then inject it into the resource server configurationAccessTokenConverter
.
Here we use the second method to achieve, the implementation sequence is as follows:
-
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");
}
}
-
Write a custom token converter and inject a custom decompressor
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter{
public CustomAccessTokenConverter(){
super.setUserTokenConverter(new CustomUserAuthenticationConverter());
}
}
-
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