Oauth2.0 认证授权
1. 导入相关依赖
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
2. 搭建授权服务器
- 继承AuthorizationServerConfigurerAdapter重写授权服务器端点相关配置
package com.intretech.open.config;
import com.intretech.open.service.OauthUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
/**
* 授权服务器配置
*
* <p>授权服务器配置主要做令牌端点的安全约束、令牌访问端点配置、管理令牌、配置客户端详细信息.
*
* @author Andrew
* @date 2021/10/6
*/
@Configuration
@EnableAuthorizationServer
public class CodeAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 数据库连接池对象
*/
@Autowired
private DataSource dataSource;
/**
* 认证业务对象
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 用户信息
*/
@Autowired
private OauthUserDetailsService userDetailsService;
/**
* 从数据库中查询出客户端信息
*
* @return {@link JdbcClientDetailsService}
*/
@Bean
public JdbcClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
/**
* token保存策略
*
* @return {@link TokenStore}
*/
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
/**
* 授权信息保存策略
*
* @return {@link ApprovalStore}
*/
@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
}
/**
* 授权码模式专用对象
*
* @return {@link AuthorizationCodeServices}
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
/**
* 指定客户端登录信息来源
*
* @param clients {@link ClientDetailsServiceConfigurer}
* @throws Exception Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}
/**
* 检查token的策略
*
* @param oauthServer {@link AuthorizationServerSecurityConfigurer}
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
// 允许表单认证
oauthServer.allowFormAuthenticationForClients()
// 开启 /oauth/check_token 资源服务器请求端点来检查令牌是否有效
.checkTokenAccess("isAuthenticated()");
}
/**
* OAuth2的配置信息
*
* @param endpoints {@link AuthorizationServerEndpointsConfigurer}
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
// 授权策略
.approvalStore(approvalStore())
// 认证策略
.authenticationManager(authenticationManager)
// 授权码生成消费策略
.authorizationCodeServices(authorizationCodeServices())
// token 保存策略
.tokenStore(tokenStore())
// 用户信息存储策略,不注入将无法使用 refresh_token
.userDetailsService(userDetailsService);
}
}
- 继承WebSecurityConfigurerAdapter对用户登录的相关端点进行配置
package com.intretech.open.config;
import com.intretech.open.service.OauthUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 安全配置类
*
* <p>默认比 ResourceServerConfigurerAdapter 执行顺序低,保护 oauth 相关的 endpoints,同时主要作用于用户的登录.
*
* @author Andrew
* @date 2021/10/6
*/
@Order(2)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CodeWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private OauthUserDetailsService userService;
/**
* 访问权限设置
*
* @param http {@link HttpSecurity}
* @throws Exception Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 以下请求无须认证
.antMatchers("/get/**").permitAll()
// 所有请求均需登录认证
.anyRequest().authenticated()
.and()
// 允许表单提交
.formLogin()
// 登录成功后跳转URL,即提交表单信息到此
.loginProcessingUrl("/login")
.permitAll()
.and()
// 关闭跨站请求伪造保护
.csrf()
.disable();
}
/**
* 密码加密方式
*
* @return {@link PasswordEncoder}
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 指定认证对象的来源
*
* @param auth {@link AuthenticationManagerBuilder}
* @throws Exception Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
/**
* AuthenticationManager对象在Oauth2认证服务中要使用,提取放到IOC容器中
*
* @return {@link AuthenticationManager}
* @throws Exception Exception
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
3. 搭建资源服务器
继承ResourceServerConfigurerAdapter对资源服务器开放资源进行权限控制
package com.intretech.open.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
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.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
/**
* 资源服务器配置
*
* <p>默认比 ResourceServerConfigurerAdapter 执行顺序高,用于保护oauth要开放的资源,主要作用于client端以及token的认证,配置需要 access_token 访问的url.
*
* @author Andrew
* @date 2021/10/6
*/
@Order(3)
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true)
public class CodeResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "ning_de_elementary_school_open_server";
@Autowired
private DataSource dataSource;
/**
* 指定token的持久化策略
* InMemoryTokenStore表示将token存储在内存中
* Redis表示将token存储在redis中
* JdbcTokenStore表示将token存储在数据库中
*
* @return {@link TokenStore}
*/
@Bean
public TokenStore jdbcTokenStore() {
return new JdbcTokenStore(dataSource);
}
/**
* 指定当前的资源 ID 和存储方案
*
* @param resources {@link ResourceServerSecurityConfigurer}
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).tokenStore(jdbcTokenStore());
}
/**
* 配置访问权限
*
* @param http {@link HttpSecurity}
* @throws Exception Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 指定不同请求方式访问资源所需要的权限,一般查询是read,其余是write。
.antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
.and()
.headers().addHeaderWriter((request, response) -> {
// 允许跨域
response.addHeader("Access-Control-Allow-Origin", "*");
// 如果是跨域的预检请求,则原封不动向下传达请求头信息
if ("OPTIONS".equals(request.getMethod())) {
response.setHeader("Access-Control-Allow-Methods", request.getHeader("AccessControl-Request-Method"));
response.setHeader("Access-Control-Allow-Headers", request.getHeader("AccessControl-Request-Headers"));
}
});
}
}
4. 导入相关数据表
/*
Navicat MySQL Data Transfer
Source Server : native
Source Server Type : MySQL
Source Server Version : 50732
Source Host : localhost:3306
Source Schema : security
Target Server Type : MySQL
Target Server Version : 50732
File Encoding : 65001
Date: 11/10/2021 16:23:27
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token` (
`token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`token` longblob NULL,
`authentication_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authentication` longblob NULL,
`refresh_token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for oauth_approvals
-- ----------------------------
DROP TABLE IF EXISTS `oauth_approvals`;
CREATE TABLE `oauth_approvals` (
`userId` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`clientId` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`expiresAt` datetime NULL DEFAULT NULL,
`lastModifiedAt` datetime NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorized_grant_types` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`web_server_redirect_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authorities` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`access_token_validity` int(11) NULL DEFAULT NULL,
`refresh_token_validity` int(11) NULL DEFAULT NULL,
`additional_information` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`autoapprove` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for oauth_client_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_token`;
CREATE TABLE `oauth_client_token` (
`token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`token` longblob NULL,
`authentication_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for oauth_code
-- ----------------------------
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`authentication` varbinary(2550) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`token` longblob NULL,
`authentication` longblob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for oauth_role
-- ----------------------------
DROP TABLE IF EXISTS `oauth_role`;
CREATE TABLE `oauth_role` (
`id` int(11) NOT NULL COMMENT '编号',
`role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
`role_desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for oauth_user
-- ----------------------------
DROP TABLE IF EXISTS `oauth_user`;
CREATE TABLE `oauth_user` (
`id` int(11) NOT NULL,
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名称',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
`status` int(255) NULL DEFAULT NULL COMMENT '1开启0关闭',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for oauth_user_role
-- ----------------------------
DROP TABLE IF EXISTS `oauth_user_role`;
CREATE TABLE `oauth_user_role` (
`UID` int(11) NOT NULL COMMENT '用户编号',
`RID` int(11) NOT NULL COMMENT '角色编号',
PRIMARY KEY (`UID`, `RID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
SET FOREIGN_KEY_CHECKS = 1;
5. Security 相关知识介绍
- 常用访问接口
/oauth/authorize:申请授权码code
/oauth/token:获取令牌token
/oauth/check_token:用于资源服务器请求端点来检查令牌是否有效
/oauth/confirm_access:用户确认授权提交
/oauth/error:授权服务错误信息
/oauth/token_key:提供公有密钥的端点,使用JWT令牌时会使用
- 数据表字段解释
官方指定表:https://andaily.com/spring-oauth-server/db_table_description.html
自定义用户表:https://blog.csdn.net/weixin_45457922/article/details/113119571 (博客里有介绍)
- 授权码模式访问流程
授权四模式访问流程请参考此博客:https://blog.csdn.net/lizhiqiang1217/article/details/90523727
- 参数解释
参数名 | 含义 | 类型 | 是否必须 | 描述 |
---|---|---|---|---|
grant_type | 授权类型 | String | 是 | 固定值:client_credentials |
client_id | 客户端ID | String | 是 | 固定值,由服务提供方生成授予 |
client_secret | 客户端密码 | String | 是 | 固定值,由服务提供方生成授予 |
scope | 权限范围 | Integer | 否 | 客户端可访问的资源范围 |
access_token | 权限码 | String | 是 | 客户端访问资源服务器权限码 |
token_type | token 类型 | String | 是 | 生成和呈现access_token的方式 |
expires_in | token过期时间 | Integer | 是 | token失效时间 |
scope | 权限范围 | String | 否 | 客户端可访问的资源范围 |
refresh_token | 刷新token | String | 否 | 授权码/密码模式才有 |
6. 细节点
- 若认证服务器与资源服务器为同一个,需要注意 WebSecurityConfigurerAdapter 与 ResourceServerConfigurerAdapter 加载顺序,避免出现请求被资源服务器拦截。
- 配置无须登录认证请求,避免请求被拦截。
- 可以支持多种模式同时存在。
- 授权码模式请求授权码客户端与请求 access_token 客户端要保持一致,否则无法请求 access_token。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程