使用OAuth2实现授权服务
Posted 爱叨叨的程序狗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用OAuth2实现授权服务相关的知识,希望对你有一定的参考价值。
综述
OAuth 2
发明之初是为了解决登录认证过程中的安全性问题,使用“委托”的形式使第三方应用获得数据权限及功能。OAuth 2.0
协议中,使用访问令牌ACCESS_TOKEN代替传统的账号密码,提高了互联网环境下的安全性。
OAuth 2
共分为四种角色:
- 授权服务:功能开放平台
- 资源所有者:用户
- 受保护资源:接口提供方
- 客户端:第三方软件即接口调用方
实则授权服务和受保护资源可以部署在同一服务器上,也可以部署在不同服务上,因为两种角色是属于同一开发团队。
在微服务环境下使用Spring OAuth 2
实现授权服务流程,需要分成三个模块:
server端:授权服务端,配置OAuth 2
授权服务器信息,负责生成授权码及访问令牌等
resource端:接口提供方,负责解析授权令牌、鉴权、数据提供
client端:第三方应用,负责调用第三方数据
准备工作
一、数据库脚本
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for authorities
-- ----------------------------
DROP TABLE IF EXISTS `authorities`;
CREATE TABLE `authorities` (
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`authority` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
UNIQUE INDEX `ix_auth_username`(`username`, `authority`) USING BTREE,
CONSTRAINT `fk_authorities_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of authorities
-- ----------------------------
INSERT INTO `authorities` VALUES ('reader', 'READ');
INSERT INTO `authorities` VALUES ('writer', 'READ,WRITE');
-- ----------------------------
-- Table structure for oauth_approvals
-- ----------------------------
DROP TABLE IF EXISTS `oauth_approvals`;
CREATE TABLE `oauth_approvals` (
`userId` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`clientId` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`partnerKey` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`scope` varchar(256) 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(0) NULL DEFAULT NULL,
`lastModifiedAt` datetime(0) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of oauth_approvals
-- ----------------------------
INSERT INTO `oauth_approvals` VALUES ('reader', 'userservice3', NULL, 'FOO', 'APPROVED', '2022-09-30 06:53:34', '2022-08-31 06:53:34');
INSERT INTO `oauth_approvals` VALUES ('writer', 'userservice3', NULL, 'FOO', 'APPROVED', '2022-09-30 13:56:15', '2022-08-31 13:56:15');
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`resource_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`client_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`scope` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`authorized_grant_types` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`web_server_redirect_uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`authorities` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`access_token_validity` int(0) NULL DEFAULT NULL,
`refresh_token_validity` int(0) NULL DEFAULT NULL,
`additional_information` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`autoapprove` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('userservice1', 'userservice', '1234', 'FOO', 'password,refresh_token', '', 'READ,WRITE', 7200, NULL, NULL, 'true');
INSERT INTO `oauth_client_details` VALUES ('userservice2', 'userservice', '1234', 'FOO', 'client_credentials,refresh_token', '', 'READ,WRITE', 7200, NULL, NULL, 'true');
INSERT INTO `oauth_client_details` VALUES ('userservice3', 'userservice', '1234', 'FOO', 'authorization_code,refresh_token', 'https://baidu.com,https://tev-competition-admin.qstcloud.net,http://localhost:5083/ui/login,http://localhost:8083/ui/login,http://localhost:5082/ui/remoteCall', 'READ,WRITE', 7200, NULL, NULL, 'false');
-- ----------------------------
-- Table structure for oauth_code
-- ----------------------------
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
`code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of oauth_code
-- ----------------------------
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`enabled` tinyint(1) NOT NULL,
PRIMARY KEY (`username`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES ('reader', '$2a$04$C6pPJvC1v6.enW6ZZxX.luTdpSI/1gcgTVN7LhvQV6l/AfmzNU/3i', 1);
INSERT INTO `users` VALUES ('writer', '$2a$04$M9t2oVs3/VIreBMocOujqOaB/oziWL0SnlWdt8hV4YnlhQrORA0fS', 1);
SET FOREIGN_KEY_CHECKS = 1;
二、项目框架
搭建项目父依赖,并创建三个模块:
- cloud-oauth2-client
- cloud-oauth2-server
- cloud-oauth2-userservice
授权服务器
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
服务端配置
授权服务配置需要继承自org.springframework.security.oauth2.config.annotation.web.configuration
包下的AuthorizationServerConfigurerAdapter
类,主要配置了用户信息来源、访问权限配置、Token配置。
@Configuration
//开启授权服务器
@EnableAuthorizationServer
public class OAuth2ServerConfiguration extends AuthorizationServerConfigurerAdapter
@Autowired
private DataSource dataSource;
@Autowired
private AuthenticationManager authenticationManager;
/**
* 我们配置了使用数据库来维护客户端信息。虽然在各种Demo中我们经常看到的是在内存中维护客户端信息,通过配置直接写死在这里。
* 但是,对于实际的应用我们一般都会用数据库来维护这个信息,甚至还会建立一套工作流来允许客户端自己申请ClientID,实现OAuth客户端接入的审批。
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception
clients.jdbc(dataSource);
/**
* 这里干了两件事儿。首先,打开了验证Token的访问权限(以便之后我们演示)。
* 然后,允许ClientSecret明文方式保存,并且可以通过表单提交(而不仅仅是Basic Auth方式提交),之后会演示到这个。
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception
security.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients().passwordEncoder(NoOpPasswordEncoder.getInstance());
/**
* 干了以下4件事儿:
* 1. 配置我们的令牌存放方式为JWT方式,而不是内存、数据库或Redis方式。
* JWT是Json Web Token的缩写,也就是使用JSON数据格式包装的令牌,由.号把整个JWT分隔为头、数据体、签名三部分。
* JWT保存Token虽然易于使用但是不是那么安全,一般用于内部,且需要走HTTPS并配置比较短的失效时间。
* 2. 配置JWT Token的非对称加密来进行签名
* 3. 配置一个自定义的Token增强器,把更多信息放入Token中
* 4. 配置使用JDBC数据库方式来保存用户的授权批准记录
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(tokenEnhancer(), jwtTokenEnhancer()));
endpoints.approvalStore(approvalStore())
.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager);
/**
* 使用JDBC数据库方式来保存授权码
* @return
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices()
return new JdbcAuthorizationCodeServices(dataSource);
/**
* 使用JWT存储
* @return
*/
@Bean
public TokenStore tokenStore()
return new JwtTokenStore(jwtTokenEnhancer());
/**
* 使用JDBC数据库方式来保存用户的授权批准记录
* @return
*/
@Bean
public JdbcApprovalStore approvalStore()
return new JdbcApprovalStore(dataSource);
/**
* 自定义的Token增强器,把更多信息放入Token中
* @return
*/
@Bean
public TokenEnhancer tokenEnhancer()
return new CustomTokenEnhancer();
/**
* 配置JWT使用非对称加密方式来验证
* @return
*/
@Bean
protected JwtAccessTokenConverter jwtTokenEnhancer()
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "mySecretKey".toCharArray());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("jwt"));
return converter;
/**
* 配置登录页面的视图信息(其实可以独立一个配置类,这样会更规范)
*/
@Configuration
static class MvcConfig implements WebMvcConfigurer
@Override
public void addViewControllers(ViewControllerRegistry registry)
registry.addViewController("login").setViewName("login");
自定义Token增强器
主要用于将自定义的更多用户信息放入Token中。
public class CustomTokenEnhancer implements TokenEnhancer
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication)
Authentication userAuthentication = authentication.getUserAuthentication();
if (userAuthentication != null)
Object principal = authentication.getUserAuthentication().getPrincipal();
//把用户标识嵌入JWT Token中去(Key是userDetails)
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("userDetails", principal);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
安全配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
private DataSource dataSource;
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception
return super.authenticationManagerBean();
/**
* 配置用户账户的认证方式。显然,我们把用户存在了数据库中希望配置JDBC的方式。
* 此外,我们还配置了使用BCryptPasswordEncoder哈希来保存用户的密码(生产环境中,用户密码肯定不能是明文保存的)
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
auth.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(new BCryptPasswordEncoder());
/**
* 开放/login和/oauth/authorize两个路径的匿名访问。前者用于登录,后者用于换授权码,这两个端点访问的时机都在登录之前。
* 设置/login使用表单验证进行登录。
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception
http.authorizeRequests()
.antMatchers("/login", "/oauth/authorize")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login");
登录页html放在templates目录下:
<body class以上是关于使用OAuth2实现授权服务的主要内容,如果未能解决你的问题,请参考以下文章
在 REST 微服务上使用 Zuul Proxy、Oauth2 实现身份验证和授权
使用 Spring Security 实现 OAuth2 隐式授权