Spring Security OAuth2 JWT公私钥认证登录后获取登录名为client_id解决方案

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);
    }

}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security OAuth2是基于Spring Security的一个模块,用于实现OAuth2协议的认证和授权功能。JWT(JSON Web Token)是一种基于JSON的开放标准,用于在各个系统之间传递安全的信息。 要实现Spring Security OAuth2 JWT的单点登录(Single Sign-On)Demo,可以按照以下步骤进行: 1. 引入依赖:在项目的pom.xml文件中,添加Spring Security OAuth2和JWT的依赖,例如: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 配置认证服务器:在Spring Boot的配置文件中,配置OAuth2认证服务器的相关信息,包括授权类型、客户端信息、权限等。 3. 配置资源服务器:通过@EnableResourceServer注解,配置资源服务器的相关信息,包括权限配置、接口保护等。 4. 实现用户认证:创建一个自定义的UserDetailsService实现类,用于根据用户名从数据库或其他存储中获取用户信息,并返回一个实现了UserDetails接口的对象,包括用户用户名、密码和权限信息。 5. 实现JWT生成和解析:创建一个JwtUtil工具类,用于生成和解析JWT。在生成JWT时,可以将用户信息包含在JWT的负载中,并设置过期时间等信息。 6. 配置登录和授权端点:在Spring Security的配置类中,配置登录和授权的端点,包括登录页面、登录成功和登录失败的处理器等。 7. 创建前端页面:根据需求,创建相应的前端页面,用于展示登录界面和验证JWT。 8. 测试:启动应用程序,访问登录页面,输入用户名和密码进行登录。成功登录后,将会生成一个JWT,并返回给前端。在其他需要进行单点登录的应用中,只需使用该JWT进行认证即可。 通过以上步骤的实现,就可以实现Spring Security OAuth2 JWT的单点登录Demo。在其他需要进行单点登录的应用中,只需使用同一个认证服务器,并验证JWT的合法性即可实现单点登录的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值