spring security oauth2 + jwt认证实现,认证通过返回access_token,用户相关信息可以通过access_token的解析获取。但是SecurityContextHolder.getContext().getAuthentication().getName()获取到的username是client_id。

​ 在不使用jwt的情况下,正常登录认证后,通过SecurityContextHolder.getContext().getAuthentication().getName()获取到的username就是登录用户名。

​ 一般情况下,不处理此种情况对于用户资源访问没有任何影响。但是因为在一个项目中使用activiti7的工作流,工作流执行是因为权限问题,导致工作流执行异常,最终debug发现,因为SecurityContextHolder.getContext().getAuthentication().getName()是client_id,不是真正登录后的用户名导致。

​ 解决方案:

​ 修改认证,及继承了AuthorizationServerConfigurerAdapter类的一个自定义类,比如AuthorizationServerConfig类,源码如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;

@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    @Resource(name = "keyProp")
    private KeyProperties keyProperties;


    //重点代码,实现自定义的凭证转化
    @Autowired
    private CustomUserAuthenticationConverter customUserAuthenticationConverter;

    /** 客户端配置 */
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource).clients(clientDetails());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
        .accessTokenConverter(jwtAccessTokenConverter())  //重点代码
        .authenticationManager(authenticationManager)
        .userDetailsService(userDetailsService)
        .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
        ;
    }

    /****
     * JWT令牌转换器
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();

        /**
         * getLocation  证书路径 microservice.jks
         * getSecret  证书秘钥 microservice
         * getAlias  证书别名 microservice
         * getPassword  证书密码 microservice
         */
        KeyPair keyPair = new KeyStoreKeyFactory(
                keyProperties.getKeyStore().getLocation(),
                keyProperties.getKeyStore().getSecret().toCharArray())
                .getKeyPair(
                        keyProperties.getKeyStore().getAlias(),
                        keyProperties.getKeyStore().getPassword().toCharArray());
        converter.setKeyPair(keyPair);
        /** 配置自定义的 CustomUserAuthenticationConverter */
        DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
        accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);

        return converter;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        //允许表单认证
        oauthServer.allowFormAuthenticationForClients()
                .passwordEncoder(new BCryptPasswordEncoder())
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

}

​ 对于不需要使用工作流服务,资源服务配置可使用以下源码:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;

/**
 * 描述
 *
 * @author carter
 * @version 1.0
 * @package  *
 * @since 1.0
 */
@Configuration
// 开启 资源服务器(标识他是一个oauth2中的资源服务器)
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    //公钥
    private static final String PUBLIC_KEY = "public.key";

    /***
     * 定义JwtTokenStore
     * @param jwtAccessTokenConverter
     * @return
     */
    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }

    /***
     * 定义JJwtAccessTokenConverter  用来校验令牌
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(getPubKey());
        return converter;
    }

    /**
     * 获取非对称加密公钥 Key
     *
     * @return 公钥 Key
     */
    private String getPubKey() {
        Resource resource = new ClassPathResource(PUBLIC_KEY);
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
            BufferedReader br = new BufferedReader(inputStreamReader);
            return br.lines().collect(Collectors.joining("\n"));
        } catch (IOException ioe) {
            return null;
        }
    }

    /***
     * Http安全配置,对每个到达系统的http请求链接进行校验
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {

        //放行 用户注册的请求
        //其他的请求  必须有登录之后才能访问 (校验token合法才可以访问)


        //所有请求必须认证通过
        http.authorizeRequests()
                //下边的路径放行
                .antMatchers(
                        "/user/add", "/user/login", "/**"). //配置地址放行
                permitAll()
                .anyRequest()
                .authenticated();    //其他地址需要认证授权
    }
    
}

​ 针对以activiti7为工作流服务,需要做相应的变更,并且需要自定义一个类继承JwtAccessTokenConverter,资源服务配置使用以下源码:

import com.common.jwt.CustomAccessTokenConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;

/**
 * 描述
 *
 * @author carter
 * @version 1.0
 * @package  *
 * @since 1.0
 */
@Configuration
// 开启 资源服务器(标识他是一个oauth2中的资源服务器)
//@EnableResourceServer
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    //公钥
    private static final String PUBLIC_KEY = "public.key";

    /***
     * 定义JwtTokenStore
     * @param jwtAccessTokenConverter
     * @return
     */
    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /***
     * 定义JJwtAccessTokenConverter  用来校验令牌
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
      	//重点代码,主要是为了解决jwt认证获取到登录用户名是client_id
        CustomAccessTokenConverter converter = new CustomAccessTokenConverter();
        converter.setVerifierKey(getPubKey());
        return converter;
    }

    /**
     * 获取非对称加密公钥 Key
     *
     * @return 公钥 Key
     */
    private String getPubKey() {
        Resource resource = new ClassPathResource(PUBLIC_KEY);
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
            BufferedReader br = new BufferedReader(inputStreamReader);
            return br.lines().collect(Collectors.joining("\n"));
        } catch (IOException ioe) {
            return null;
        }
    }

    /***
     * Http安全配置,对每个到达系统的http请求链接进行校验
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {

        //放行 用户注册的请求
        //其他的请求  必须有登录之后才能访问 (校验token合法才可以访问)
        //所有请求必须认证通过
        http.authorizeRequests()
        //下边的路径放行
        .antMatchers("/**"). //配置地址放行
        permitAll()
        .anyRequest()
        .authenticated();    //其他地址需要认证授权
    }
    
}

​ 此处源码主要是为了解决jwt认证获取到登录用户名是client_id,重新封装登录认证信息,最终SecurityContextHolder.getContext().getAuthentication().getName()是。

import cn.hutool.core.collection.CollectionUtil;
import com.common.constants.Constants;
import com.google.common.collect.Lists;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 项目名称:common
 * 类 名 称:CustomAccessTokenConverter
 * 类 描 述:TODO
 * 创建时间:2021/3/21 下午11:42
 * 创 建 人:chenyouhong
 */
public class CustomAccessTokenConverter extends JwtAccessTokenConverter {

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
        List<String> authorities = (List)claims.get("authorities");
        List<SimpleGrantedAuthority> simpleGrantedAuthorities = Lists.newArrayList();
        if (CollectionUtil.isNotEmpty(authorities)) {
            simpleGrantedAuthorities.addAll(authorities.stream().map(e -> new SimpleGrantedAuthority(e)).collect(Collectors.toList()));
        }

        String password = Constants.BLANK;
        if (claims.get("password") != null) {
            password = (String)claims.get("password");
        }

        UserJwt userDetails = new UserJwt((String)claims.get("userCode"), password, simpleGrantedAuthorities);
        OAuth2Request request = new OAuth2Request((Map)null, (String)claims.get("client_id"), simpleGrantedAuthorities, true, (Set)null, (Set)null, (String)null, (Set)null, (Map)null);
        //将提取的值principal作为构造函数参数
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, "N/A", simpleGrantedAuthorities);
        token.setDetails(claims);
        return new OAuth2Authentication(request, token);
    }

}

版权声明:本文为qq_21480329原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_21480329/article/details/115200047