spring security +oauth2(授权服务和资源服务分离) 解决getPrincipal只返回用户名的问题

  • Post author:
  • Post category:其他


最近用spring security + oauth2 做认证和授权,碰到个非常懊恼的问题,从安全上下文获取用户信息,永远只给你返回个用户名。因为在很多地方,要用到用户id, 不可能又用用户名去数据库查id吧,这就非常蛋腾了。

百度了人民群众的意见,折腾了老半天,终于把它搞掂了。

终极大法就是重写源码里面默认的实现方法。

源码就不贴了, 这贴修改的,供各位参考参考:

一 、 重写DefaultUserAuthenticationConverter里面的convertUserAuthentication方法:

@Component
@Slf4j
public class SetAdditionalInfoToTokenConverter extends DefaultUserAuthenticationConverter {
    
    /**
     * 这个方法把额外的信息添加进jwt token (默认只添加用户名,权限)
     * 这里额外添加了用户id
     * 方法将会在授权服务器起作用
     * @param authentication
     * @return
     */

   @Override
    public Map<String, ?> convertUserAuthentication(Authentication authentication) {
        Map<String, Object> response = new LinkedHashMap<String, Object>();
       // response.put(USERNAME, authentication.getName());   源码里面的,不知道USERNAME是什么,注释掉吧
       Object principal = authentication.getPrincipal();
       
       //不要强制转换principal, 用json转化,不然各种莫名其妙的坑
       String jsonStr=JSON.toJSONString(principal);
       JSONObject jsonObject=JSON.parseObject(jsonStr);
       
       //加上自定义的用户名和id
       long id=jsonObject.getLong("id");
        response.put("id", id);
        String username=jsonObject.getString("username");
        response.put("username",username);

        if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
            response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
        }
        return response;
    }
    
    
    //懒得写, 从源码复制过来的
    private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
        if (!map.containsKey(AUTHORITIES)) {
            return List.of();
        }
        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");
    }
    
}

二,把这个重写的类 SetAdditionalInfoToTokenConverter 设置到授权服务器配置类里面 :

SetAdditionalInfoToTokenConverter  —-> defaultAccessTokenConverter ->jwtAccessTokenConverter—>endpints.tokenEnhancer()

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Qualifier("customUserDetailService")
    @Autowired

    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    SetAdditionalInfoToTokenConverter setAdditionalInfoToTokenConverter; //定制保存在jwt里面的信息


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .accessTokenConverter(jwtAccessTokenConverter())
                .tokenStore(jwtTokenStore())
                .tokenEnhancer(jwtAccessTokenConverter());
        super.configure(endpoints);
    }

    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setAccessTokenConverter(accessTokenConverter());
        return jwtAccessTokenConverter;
    }

    @Bean
    public DefaultAccessTokenConverter accessTokenConverter(){
        DefaultAccessTokenConverter defaultAccessTokenConverter = new 
                                                  DefaultAccessTokenConverter();
     defaultAccessTokenConverter.setUserTokenConverter(setAdditionalInfoToTokenConverter);
        return defaultAccessTokenConverter;
    }


}

三, 上面两步只是把额外信息(用户id,用户名)设置进jwtoken, 还不能用get principal获取,我们再继续重写 DefaultUserAuthenticationConverte 里面的 extractAuthentication方法,这个方法实现怎样提取信息到anthentication。因为后面要把重写的类设置进资源服务器配置类里面,所以重新创建一个component: ExtractAuthenticationConverter

@Component
@Slf4j
public class ExtractAuthenticationConverter extends DefaultUserAuthenticationConverter {

    /**
     * 这个方法重新定义了安全上下文 securityContextHolder.getContext.getPrincipal 返回的信息;
     *  默认只返回用户名
     *  这里添加了返回用户id
     *  方法在资源服务器起作用
     * @param map
     * @return
     */
    @Override
    public Authentication extractAuthentication(Map<String, ?> map) {
           String username="username";
           String id="id";
           Map<String,Object> principal=new HashMap<>();
            if(map.containsKey(username)){
                 principal.put(username, map.get(username));
            }
            if(map.containsKey(id)){
                principal.put(id, map.get(id));
            }

            Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
            return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
    }

    //从源码复制过来的
    private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
        if (!map.containsKey(AUTHORITIES)) {
            return List.of();
        }
        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");
    }
    
}

四、 把重写了 extractAuthentication的类 extractAuthenticationConverter 设置进资源服务器配置类:

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    ExtractAuthenticationConverter extractAuthenticationConverter;


    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(jwtTokenStore());

    }

    private TokenStore jwtTokenStore() {
        JwtTokenStore jwtTokenStore = new JwtTokenStore(accessTokenConverter());
        return jwtTokenStore;
    }

    @Bean 
    public JwtAccessTokenConverter accessTokenConverter() {
       
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
        //如果用了公私钥,在这里解析公钥。。。 省略

        // 新建一个defaultAccessTokenConverter中介把 自定义的extractAuthenticationConverter设置进去
       DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
       defaultAccessTokenConverter.setUserTokenConverter(extractAuthenticationConverter);
       tokenConverter.setAccessTokenConverter(defaultAccessTokenConverter);
        return tokenConverter;
    }

}

至此,全部设置完,建个接口测试一下:


    @GetMapping("/principle")
    public Object getUserInfo(){
        return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }

返回  :

{

“id”: 12312154321,

“username”: “admin”

}

ok,成功。



版权声明:本文为sehor2012原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。