SpringBoot集成oauth2(客户端模式)

本文介绍如何使用Spring Boot实现OAuth2客户端模式,包括验证服务器与资源服务器的搭建过程及JWT Token的配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

客户端模式:
请求一个接口,咱们的后端服务A直接请求验证服务B拿到token,服务A再用token访问资源服务C。

这里用模块化开发,一个资源服务器,一个验证服务器
在这里插入图片描述

依赖

Springboot 版本为2.3.3.RELEASE

<!--Security + oauth2 + jwt -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-security</artifactId>
 </dependency>
 <dependency>
     <groupId>org.springframework.security.oauth</groupId>
     <artifactId>spring-security-oauth2</artifactId>
     <version>2.5.0.RELEASE</version>
 </dependency>
 <dependency>
     <groupId>org.springframework.security</groupId>
     <artifactId>spring-security-jwt</artifactId>
     <version>1.1.1.RELEASE</version>
 </dependency>
 <dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt</artifactId>
     <version>0.9.1</version>
 </dependency>
 <dependency>
     <groupId>com.sun.xml.ws</groupId>
     <artifactId>jaxws-rt</artifactId>
     <version>2.3.3</version>
 </dependency>

 <!--redis + 连接池-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
 <dependency>
     <groupId>org.apache.commons</groupId>
     <artifactId>commons-pool2</artifactId>
 </dependency>

yml

server:
  port: 9910
myoauth2:
  clientId: admin1
  clientSecret: 123456
  tokenEndpoint: http://localhost:9910

1.配置一个验证服务器 9910 端口

/**
 * 验证服务器;EnableAuthorizationServer注解表示是个验证服务器
 */
@Configuration
@EnableAuthorizationServer
public class Config_Authorization extends AuthorizationServerConfigurerAdapter {

    @Value("${myoauth2.clientId}")
    private String clientId;
    @Value("${myoauth2.clientSecret}")
    private String clientSecret;

    private static final String DEMO_RESOURCE_ID = "order";

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private BCryptPasswordEncoder passwordEncoder;

//============redis存储token===============
//    @Resource
//    private RedisConnectionFactory redisConnectionFactory;
//============redis存储token===============
 
 
    //=============JWT存储token==================
    @Resource
    private TokenStore tokenStore;
    @Resource
    private JwtAccessTokenConverter accessTokenConverter;
    @Resource
    private Jwt_TokenEnhancer jwtTokenEnhancer;
    //=============JWT存储token==================


    /**
     * 访问端点配置
     * 配置授权authorization以及令牌(token)的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //配置Redis存储token
        //endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory));

        //配置Jwt存储token + Jwt自定义增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtTokenEnhancer, accessTokenConverter));
        endpoints.tokenStore(tokenStore).accessTokenConverter(accessTokenConverter).tokenEnhancer(tokenEnhancerChain);

        //配置管理器允许GET和POST请求端点oauth/token获取Token
        endpoints.authenticationManager(authenticationManager).allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    }

    /**
     * 授权端点开放,配置令牌端点(Token Endpoint)的安全约束
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer
                .tokenKeyAccess("permitAll()")              //开启/oauth/token_key验证端口无权限访问
                .checkTokenAccess("isAuthenticated()")      //开启/oauth/check_token验证端口认证权限访问
                .allowFormAuthenticationForClients();       //允许表单认证
    }

    /**
     * 配置客户端详情服务
     * 客户端详情信息在这里进行初始化, 通过数据库来存储调取详情信息
     * 在验证服务器为客户端client配置resourceIds的目的是:限制某个client可以访问的资源服务。
     * 当请求发送到Resource Server的时候会携带access_token,
     * Resource Server会根据access_token找到client_id,进而找到该client可以访问的resource_ids。
     * 如果resource_ids包含ResourceServer自己设置ResourceID,这关就过去了,就可以继续进行其他的权限验证
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //使用内存模式; 也可以配置客户端存储到数据库DB,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
        clients.inMemory()
                .withClient(clientId)                                                   //client_id
                .secret(passwordEncoder.encode(clientSecret))                           //client_密码
                .resourceIds(DEMO_RESOURCE_ID)                                          //配置资源的id
                .authorizedGrantTypes("client_credentials")                             //授权类型:客户端模式
                .scopes("all")                                                          //配置申请的权限范围
                .authorities("client");                                                 //客户端可以使用的权限

    }
}

2. Security配置类


@Configuration
@EnableWebSecurity
public class Config_WebSecurity extends WebSecurityConfigurerAdapter {


    /**
     * 配置拦截保护的请求径
     * permitAll() 表示任意用户可访问
     * anyRequest() 表示所有请求
     * authenticated() 表示已登录用户才能访问
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf()                                                                 //不启用跨站请求伪造
                .disable()
                .authorizeRequests()
                .antMatchers("/oauth/**", "/login/**", "/logout/**")    //放行这些以"/login/","/oauth/"开头请求
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .permitAll();
    }

    /**
     * 配置验证管理器
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 配置密码加密器
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

验证服务器用JWT存储Token,返回JWT格式的Token

/**
 * 使用Jwt存储token的配置
 */
@Configuration
public class Jwt_TokenStore {

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        return converter;
    }

    @Bean
    public Jwt_TokenEnhancer jwtTokenEnhancer() {
        return new Jwt_TokenEnhancer();
    }
}

JWT内容自定义


/**
 * Jwt内容增强器
 */
public class Jwt_TokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> info = new HashMap<>();
        info.put("我的信息", "abcddd");
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
        return accessToken;
    }
}

再配置一个资源服务器 9911 端口

@Slf4j
@Configuration
@EnableResourceServer
public class Config_Resource extends ResourceServerConfigurerAdapter {

    @Value("${uaa.clientId}")
    private String clientId;
    @Value("${uaa.clientSecret}")
    private String clientSecret;
    @Value("${uaa.tokenEndpoint}")
    private String tokenEndpoint;

    /**
     * ResourceID资源的标识
     */
    private static final String DEMO_RESOURCE_ID = "order";

    /**
     * 在每个ResourceServer实例上设置resourceId,该resourceId作为该资源的唯一标识
     * 验证服务器给Client第三方客户端授权时,可以设置这个Client可以访问哪些Resource-Server资源服务
     * 如没有设置就是对所有的Resource-Server都有访问权限
     *
     * resources.resourceId(DEMO_RESOURCE_ID)     //为每个ResourceServer(一个微服务实例)设置一个ResourceID
     * resources.stateless(true)                  //标记以指示在这些资源上仅允许基于令牌的身份验证
     * resources.tokenStore(xxx)                  //token的存储方式
     * resources.tokenExtractor(xxx)              //token获取方式,默认为BearerTokenExtractor
     * resources.authenticationEntryPoint(xxx);   //配置自定义的认证异常处理返回类
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
    }

    /**
     * 用来配置拦截保护的请求
     * 这里可以代替Spring Security同名方法配置
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()                       //禁用了csrf功能,关跨域保护
                .authorizeRequests()                //限定签名成功的请求
                .antMatchers("/order/**").authenticated()  //必须认证过后才可以访问
                .anyRequest().permitAll()       //其他没有限定的请求允许随意访问
                .and().anonymous();             //对于没有配置权限的其他请求允许匿名访问

    }

    /**
     * RemoteTokenServices是用于向远程认证服务器验证token,同时获取token对应的用户的信息
     * 通过RestTemplate调用远程服务,我们在使用这个类时,要设置checkTokenEndpointUrl、clientId、clientSecret等。
     * 只需要显示注入RemoteTokenServices remoteTokenServices()的Bean就可以调用授权服务器的/oauth/check_token端点查询token的合法性,之后返回其信息
     * 设置客户端配置的值
     */
    @Primary
    @Bean
    public RemoteTokenServices remoteTokenServices() {
        final RemoteTokenServices tokenServices = new RemoteTokenServices();
        //设置授权服务器check_token(验证token)端点完整地址
        tokenServices.setCheckTokenEndpointUrl(tokenEndpoint+"/oauth/check_token");
        //设置client_id与secret,注意client_secret值不能使用passwordEncoder加密
        tokenServices.setClientId(clientId);
        tokenServices.setClientSecret(clientSecret);
        return tokenServices;
    }
}

已经配好了,启动两个服务
在这里插入图片描述
验证服务器9910,资源服务器9911
上面的保护资源的是order,要请求资源服务器order接口时需要token验证
这里先访问资源服务器的接口
在这里插入图片描述

首先用户要访问order资源,
我们后台服务器A 通过过滤器 先去验证服务器B拿Token
http://localhost:9910/oauth/token?grant_type=client_credentials&scope=select&client_id=admin1&client_secret=123456

这就要求验证服务器B对我们的服务器A很信任
请求Token返回的结果中有access_token
这时服务A再访问资源服务器C,请求头中自动添加这个参数:Authorization 值为access_token 或者 "Bearer "+access_token;就可以访问order了

当然资源服务器C收到了带Token的请求先会去验证服务器B验证一下Token对不对
就是RemoteTokenServices 这个方法,远程调用验证端点check_token了

这种模式不常用,一般用密码模式和授权码模式

### 实现OAuth2客户端模式 #### 授权码模式概述 授权码模式OAuth 2.0中最常用的一种授权方式,适用于服务器端应用程序。该模式的安全性和灵活性较高,适合处理敏感数据的应用程序[^2]。 #### 使用`oauth2-client`库集成OAuth 2.0服务提供商 为了简化与OAuth 2.0服务提供商的集成过程,可以使用`oauth2-client`库。这个库提供了简单易用的方法来获取访问令牌并与其他支持OAuth 2.0的服务交互[^1]。 ```python from authlib.integrations.requests_client import OAuth2Session client_id = 'your_client_id' client_secret = 'your_client_secret' redirect_uri = 'http://localhost/callback' # 创建会话实例 session = OAuth2Session(client_id, client_secret, redirect_uri=redirect_uri) authorization_url, state = session.create_authorization_url( 'https://example.com/oauth/authorize', scope='profile email offline_access' ) print('Please go to %s and authorize access.' % authorization_url) ``` 上述代码展示了如何创建一个OAuth 2.0会话,并生成用于引导用户到第三方服务商网站进行身份验证的URL。当用户完成登录操作后,将会被重定向回指定回调地址,并携带授权码作为参数传递给应用。 接着,在接收到用户的返回请求之后,可以通过下面的方式交换获得访问令牌: ```python token_endpoint = 'https://example.com/oauth/token' auth_code = request.args.get('code') token = session.fetch_token(token_endpoint, code=auth_code) print("Access token:", token['access_token']) ``` 这段脚本负责接收来自浏览器中的授权响应(即授权码),并通过POST请求向Token Endpoint发送此授权码以换取实际可用的访问令牌。 对于某些特定场景下可能还需要考虑私钥签名JWT认证机制(private_key_jwt),这通常涉及到更复杂的密钥管理和加密算法配置。具体实现细节可参见相关文档说明[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值