Spring Boot Security 整合 OAuth2 设计安全API接口服务

简介: OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。本文重点讲解Spring Boot项目对OAuth2进行的实现。

文章首发于公众号《程序员果果》
地址:https://mp.weixin.qq.com/s/0PAUErDh0qmcR4SUsTn15Q

简介

OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。本文重点讲解Spring Boot项目对OAuth2进行的实现,如果你对OAuth2不是很了解,你可以先理解 OAuth 2.0 - 阮一峰,这是一篇对于oauth2很好的科普文章。

OAuth2概述

oauth2根据使用场景不同,分成了4种模式

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

在项目中我们通常使用授权码模式,也是四种模式中最复杂的,通常网站中经常出现的微博,qq第三方登录,都会采用这个形式。

Oauth2授权主要由两部分组成:

  • Authorization server:认证服务
  • Resource server:资源服务

在实际项目中以上两个服务可以在一个服务器上,也可以分开部署。下面结合spring boot来说明如何使用。

快速上手

之前的文章已经对 Spring Security 进行了讲解,这一节对涉及到 Spring Security 的配置不详细讲解。若不了解 Spring Security 先移步到 Spring Boot Security 详解

建表

客户端信息可以存储在内存、redis和数据库。在实际项目中通常使用redis和数据库存储。本文采用数据库。Spring 0Auth2 己经设计好了数据库的表,且不可变。表及字段说明参照:Oauth2数据库表说明

创建0Auth2数据库的脚本如下:

DROP TABLE IF EXISTS `clientdetails`;
DROP TABLE IF EXISTS `oauth_access_token`;
DROP TABLE IF EXISTS `oauth_approvals`;
DROP TABLE IF EXISTS `oauth_client_details`;
DROP TABLE IF EXISTS `oauth_client_token`;
DROP TABLE IF EXISTS `oauth_refresh_token`;

CREATE TABLE `clientdetails` (
  `appId` varchar(128) NOT NULL,
  `resourceIds` varchar(256) DEFAULT NULL,
  `appSecret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `grantTypes` varchar(256) DEFAULT NULL,
  `redirectUrl` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additionalInformation` varchar(4096) DEFAULT NULL,
  `autoApproveScopes` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_approvals` (
  `userId` varchar(256) DEFAULT NULL,
  `clientId` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` datetime DEFAULT NULL,
  `lastModifiedAt` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(128) NOT NULL,
  `resource_ids` varchar(256) DEFAULT NULL,
  `client_secret` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `authorized_grant_types` varchar(256) DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) DEFAULT NULL,
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` varchar(4096) DEFAULT NULL,
  `autoapprove` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_client_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(128) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

为了测试方便,我们先插入一条客户端信息。

INSERT INTO `oauth_client_details` VALUES ('dev', '', 'dev', 'app', 'password,client_credentials,authorization_code,refresh_token', 'http://www.baidu.com', '', 3600, 3600, '{\"country\":\"CN\",\"country_code\":\"086\"}', 'false');

用户、权限、角色用到的表如下:

DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `role`;
DROP TABLE IF EXISTS `user_role`;
DROP TABLE IF EXISTS `role_permission`;
DROP TABLE IF EXISTS `permission`;

CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`) 
);
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`) 
);
CREATE TABLE `user_role` (
`user_id` bigint(11) NOT NULL,
`role_id` bigint(11) NOT NULL
);
CREATE TABLE `role_permission` (
`role_id` bigint(11) NOT NULL,
`permission_id` bigint(11) NOT NULL
);
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) NULL,
`pid` bigint(11) NOT NULL,
PRIMARY KEY (`id`) 
);

INSERT INTO user (id, username, password) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e'); 
INSERT INTO user (id, username , password) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e'); 
INSERT INTO role (id, name) VALUES (1,'USER');
INSERT INTO role (id, name) VALUES (2,'ADMIN');
INSERT INTO permission (id, url, name, pid) VALUES (1,'/**','',0);
INSERT INTO permission (id, url, name, pid) VALUES (2,'/**','',0);
INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 2);
INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);

项目结构

resources
|____templates
| |____login.html
| |____application.yml
java
|____com
| |____gf
| | |____SpringbootSecurityApplication.java
| | |____config
| | | |____SecurityConfig.java
| | | |____MyFilterSecurityInterceptor.java
| | | |____MyInvocationSecurityMetadataSourceService.java
| | | |____ResourceServerConfig.java
| | | |____WebResponseExceptionTranslateConfig.java
| | | |____AuthorizationServerConfiguration.java
| | | |____MyAccessDecisionManager.java
| | |____entity
| | | |____User.java
| | | |____RolePermisson.java
| | | |____Role.java
| | |____mapper
| | | |____PermissionMapper.java
| | | |____UserMapper.java
| | | |____RoleMapper.java
| | |____controller
| | | |____HelloController.java
| | | |____MainController.java
| | |____service
| | | |____MyUserDetailsService.java

关键代码

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

SecurityConfig

支持password模式要配置AuthenticationManager

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService userService;


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        //校验用户
        auth.userDetailsService( userService ).passwordEncoder( new PasswordEncoder() {
            //对密码进行加密
            @Override
            public String encode(CharSequence charSequence) {
                System.out.println(charSequence.toString());
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }
            //对密码进行判断匹配
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                boolean res = s.equals( encode );
                return res;
            }
        } );

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.requestMatchers()
                .antMatchers("/oauth/**","/login","/login-error")
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**").authenticated()
                .and()
                .formLogin().loginPage( "/login" ).failureUrl( "/login-error" );
    }


    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception{
        return super.authenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return Objects.equals(charSequence.toString(),s);
            }
        };
    }


}

AuthorizationServerConfiguration 认证服务器配置

/**
 * 认证服务器配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {


    /**
     * 注入权限验证控制器 来支持 password grant type
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 注入userDetailsService,开启refresh_token需要用到
     */
    @Autowired
    private MyUserDetailsService userDetailsService;

    /**
     * 数据源
     */
    @Autowired
    private DataSource dataSource;

    /**
     * 设置保存token的方式,一共有五种,这里采用数据库的方式
     */
    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private WebResponseExceptionTranslator webResponseExceptionTranslator;

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore( dataSource );
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        /**
         * 配置oauth2服务跨域
         */
        CorsConfigurationSource source = new CorsConfigurationSource() {
            @Override
            public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                CorsConfiguration corsConfiguration = new CorsConfiguration();
                corsConfiguration.addAllowedHeader("*");
                corsConfiguration.addAllowedOrigin(request.getHeader( HttpHeaders.ORIGIN));
                corsConfiguration.addAllowedMethod("*");
                corsConfiguration.setAllowCredentials(true);
                corsConfiguration.setMaxAge(3600L);
                return corsConfiguration;
            }
        };

        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients()
                .addTokenEndpointAuthenticationFilter(new CorsFilter(source));
    }

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

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //开启密码授权类型
        endpoints.authenticationManager(authenticationManager);
        //配置token存储方式
        endpoints.tokenStore(tokenStore);
        //自定义登录或者鉴权失败时的返回信息
        endpoints.exceptionTranslator(webResponseExceptionTranslator);
        //要使用refresh_token的话,需要额外配置userDetailsService
        endpoints.userDetailsService( userDetailsService );

    }


}

ResourceServerConfig 资源服务器配置

/**
 * 资源提供端的配置
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    /**
     * 这里设置需要token验证的url
     * 这些url可以在WebSecurityConfigurerAdapter中排除掉,
     * 对于相同的url,如果二者都配置了验证
     * 则优先进入ResourceServerConfigurerAdapter,进行token验证。而不会进行
     * WebSecurityConfigurerAdapter 的 basic auth或表单认证。
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers("/hi")
                .and()
                .authorizeRequests()
                .antMatchers("/hi").authenticated();
    }


}

关键代码就是这些,其他类代码参照后面提供的源码地址。

验证

密码授权模式

[ 密码模式需要参数:username , password , grant_type , client_id , client_secret ]

请求token

curl -X POST -d "username=admin&password=123456&grant_type=password&client_id=dev&client_secret=dev" http://localhost:8080/oauth/token

返回

{
    "access_token": "d94ec0aa-47ee-4578-b4a0-8cf47f0e8639",
    "token_type": "bearer",
    "refresh_token": "23503bc7-4494-4795-a047-98db75053374",
    "expires_in": 3475,
    "scope": "app"
}

不携带token访问资源,

curl http://localhost:8080/hi\?name\=zhangsan

返回提示未授权

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}

携带token访问资源

curl http://localhost:8080/hi\?name\=zhangsan\&access_token\=164471f7-6fc6-4890-b5d2-eb43bda3328a

返回正确

hi , zhangsan

刷新token

curl  -X POST -d 'grant_type=refresh_token&refresh_token=23503bc7-4494-4795-a047-98db75053374&client_id=dev&client_secret=dev' http://localhost:8080/oauth/token

返回

{
    "access_token": "ef53eb01-eb9b-46d8-bd58-7a0f9f44e30b",
    "token_type": "bearer",
    "refresh_token": "23503bc7-4494-4795-a047-98db75053374",
    "expires_in": 3599,
    "scope": "app"
}

客户端授权模式

[ 客户端模式需要参数:grant_type , client_id , client_secret ]

请求token

curl -X POST -d "grant_type=client_credentials&client_id=dev&client_secret=dev" http://localhost:8080/oauth/token

返回

{
    "access_token": "a7be47b3-9dc8-473e-967a-c7267682dc66",
    "token_type": "bearer",
    "expires_in": 3564,
    "scope": "app"
}

授权码模式

获取code

浏览器中访问如下地址:

http://localhost:8080/oauth/authorize?response_type=code&client_id=dev&redirect_uri=http://www.baidu.com

跳转到登录页面,输入账号和密码进行认证:

认证后会跳转到授权确认页面(oauth_client_details 表中 “autoapprove” 字段设置为true 时,不会出授权确认页面):

2

确认后,会跳转到百度,并且地址栏中会带上我们想得到的code参数:

通过code换token

curl -X POST -d "grant_type=authorization_code&code=qS03iu&client_id=dev&client_secret=dev&redirect_uri=http://www.baidu.com" http://localhost:8080/oauth/token

返回

{
    "access_token": "90a246fa-a9ee-4117-8401-ca9c869c5be9",
    "token_type": "bearer",
    "refresh_token": "23503bc7-4494-4795-a047-98db75053374",
    "expires_in": 3319,
    "scope": "app"
}

参考

https://segmentfault.com/a/1190000012260914

https://stackoverflow.com/questions/28537181/spring-security-oauth2-which-decides-security

源码

https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-security-oauth2

相关文章
|
4月前
|
JSON 安全 Java
API 一键转换 MCP 服务!Higress 助今日投资快速上线 MCP 市场
今日投资的技术负责人介绍了如何通过Higress MCP 市场完善的解决方案,快捷地将丰富的金融数据 API 转化为 MCP 工具,帮助用户通过 MCP 的方式非常轻松地调用专业金融数据,自由快速地构建自己的金融大模型应用。
683 23
|
5月前
|
JSON 安全 API
商城API接口:上货安全与实战指南
商城上货API接口在电商运营中扮演着核心角色,本文从功能、类型、安全性及实战案例多角度剖析其应用价值。主要功能涵盖商品信息管理(如发布、图片上传、类目设置)、库存与价格管理、订单处理、物流信息管理、用户信息管理及销售数据分析等。API类型包括RESTful、SOAP、JSON和XML等,其中RESTful和JSON因高效简洁而广泛应用。安全性方面,通过身份验证、授权及数据加密等措施保障接口安全,确保数据传输的可靠性。这些内容为开发者优化商城运营提供了重要指导。
101 1
|
1月前
|
存储 监控 安全
132_API部署:FastAPI与现代安全架构深度解析与LLM服务化最佳实践
在大语言模型(LLM)部署的最后一公里,API接口的设计与安全性直接决定了模型服务的可用性、稳定性与用户信任度。随着2025年LLM应用的爆炸式增长,如何构建高性能、高安全性的REST API成为开发者面临的核心挑战。FastAPI作为Python生态中最受青睐的Web框架之一,凭借其卓越的性能、强大的类型安全支持和完善的文档生成能力,已成为LLM服务化部署的首选方案。
|
7月前
|
缓存 安全 API
API 接口开发与合理利用:构建高效、安全、可维护的数字桥梁
本文全面解析API接口的设计、优化与安全维护。API作为系统间交互的标准化契约,核心价值在于解耦系统、提升复用性和构建开放生态。设计时需遵循六大原则:明确输入输出、关注单一职责、实现自我表达、确保功能无重叠、保障幂等性及合理版本化。性能优化从批量处理、异步调用、并行执行等方面入手,同时结合缓存、池化技术和SQL优化提升效率。安全性涵盖加密传输、加签验签、Token认证、防重放攻击及限流熔断等十大要点。最后,通过文档自动生成、日志体系和版本管理确保接口可持续迭代。优秀的API应以契约优先、演进思维和防御心态为核心,成为系统的数字资产,支持内外部高效协作与生态建设。
|
3月前
|
安全 算法 API
银行卡三要素API实践指南:实现交易安全闭环
在数字化金融时代,身份真实性成为安全防线关键。银行卡三要素核验通过验证卡号、姓名与身份证一致性,提升身份识别准确率,广泛应用于金融、支付等领域。本文详解其技术原理、架构设计与工程实践,助力构建安全合规的身份认证体系。
385 1
|
3月前
|
人工智能 算法 API
国产化用于单导联和六导联的心电算法及API服务
随着智能设备普及,心电图功能逐渐应用于智能手表、体脂仪等设备。苏州唯理推出单导联及6导联心电算法API服务,由AI驱动,1分钟内快速评估心律失常、房颤、早搏等问题,已广泛用于医疗设备及三甲医院。其算法还可评估压力、疲劳、情绪状态,筛查效率远超进口设备。唯理率先实现国产医疗级心电芯片,支持快速集成与私有化部署,适用于多种智能硬件。
|
5月前
|
存储 安全 API
区块链技术:为电商API接口应用前景筑牢安全与效率之基
区块链技术凭借其去中心化、透明性、安全性和不可篡改性,为电商API接口带来了全新机遇。它可提升数据安全性、增强交易透明度、优化供应链管理,并降低运营成本。应用场景包括数据加密传输、分布式存储、智能合约权限管理、商品溯源防伪及实时结算。尽管面临性能、隐私保护与监管等挑战,随着技术进步与融合创新,区块链将在电商API中实现更智能、高效的应用,推动行业变革升级。
|
5月前
|
人工智能 自然语言处理 API
硅基流动入驻阿里云云市场,核心API服务将全面接入阿里云百炼平台💐
2025年6月18日,AI Infra企业硅基流动与阿里云达成战略合作,加入“繁花计划”并入驻云市场。其大模型推理平台SiliconCloud核心API将接入阿里云百炼平台,依托灵骏智能计算集群为客户提供高效服务。作为国内领先的MaaS平台,SiliconCloud已集成百余款开源大模型,服务600万用户及众多企业。双方将在算力协同、行业解决方案等领域深化合作,推动AI生态发展。
735 0
|
5月前
|
安全 数据挖掘 API
“电商API支付接口:安全快捷,提升结账转化”
电商API支付接口通过标准化集成提升结账效率,减少用户流失。其优势包括无需跳转的快捷支付、tokenization技术保障安全及动态货币转换降低国际客户疑虑。一键支付功能显著减少弃单率,且支持多种支付方式。风控方面,3DS2.0验证与行为分析确保交易安全,同时保持高支付成功率。技术实施建议采用RESTful API优化响应速度,并通过标准化错误代码和完整日志系统提升稳定性。数据分析可细分支付成功率,借助A/B测试优化结账体验,实时更新订单状态以完善履约流程。
264 0
|
1月前
|
缓存 监控 前端开发
顺企网 API 开发实战:搜索 / 详情接口从 0 到 1 落地(附 Elasticsearch 优化 + 错误速查)
企业API开发常陷参数、缓存、错误处理三大坑?本指南拆解顺企网双接口全流程,涵盖搜索优化、签名验证、限流应对,附可复用代码与错误速查表,助你2小时高效搞定开发,提升响应速度与稳定性。